ps
ps.Adjust
Section titled “ps.Adjust”ps.Adjust(*, well, oil_rate: Union[ScalarPropertyInput, Expression, NoneType] = None, water_rate: Union[ScalarPropertyInput, Expression, NoneType] = None, gas_rate: Union[ScalarPropertyInput, Expression, NoneType] = None, liquid_rate: Union[ScalarPropertyInput, Expression, NoneType] = None, reservoir_volume: Union[ScalarPropertyInput, Expression, NoneType] = None, bhp: Union[ScalarPropertyInput, Expression, NoneType] = None, thp: Union[ScalarPropertyInput, Expression, NoneType] = None, rate: Union[ScalarPropertyInput, Expression, NoneType] = None, artificial_lift_quantity: Union[ScalarPropertyInput, Expression, NoneType] = None, lift_table: ProductionTable | InjectionTable | None = None) -> None
Change one target of the selected wells and keep their control (WELTARG).
ps.Adjust(well="P1", oil_rate=1200.0) moves P1’s oil-rate target to 1200 from this
date; the rest of its control stands. Exactly one quantity is stated, in the words the
well’s control states it: oil_rate, water_rate, gas_rate, liquid_rate,
reservoir_volume, bhp, thp, or an injector’s rate, in the model’s units (a
surface rate, a reservoir volume rate or a pressure). Or it changes a lift setting, as
ps.Produce spells it: a producer’s artificial_lift_quantity (WELTARG LIFT, a bare
number its lift table reads), or the lift_table the well flows through (WELTARG VFP, a
production table for a producer, an injection table for an injector). It is a dated change,
held at schedule/<date>/<well>/adjust/<n>; m.control(well, at=) reads the control it
leaves.
Examples
Section titled “Examples”import petroscript as ps m = ps.load(“tests/results_fixtures/QUICKSTART/MERLE.DATA”) # the quickstart’s field m = m.at(“2010-03-01”, ps.Adjust(well=“M-1”, gas_rate=450_000.0)) # sm3/day: metric m.find(“schedule/2010-03-01//”) [‘schedule/2010-03-01/M-1/adjust/1’] m.control(“M-1”, at=“2010-03-01”).bhp_limit # barsa, kept 50.0
ps.Box
Section titled “ps.Box”ps.Box(i: tuple[int, int] | None = None, j: tuple[int, int] | None = None, k: tuple[int, int] | None = None) -> None
A box of cells: the deck’s 1-based, inclusive ranges along i, j and k.
i, j and k are each a range (start, end) of cell indices along their axis,
counted from 1 with both ends in the box; an axis left out spans the grid.
ps.Box(i=(1, 3), k=(2, 2)) is the cells 1 to 3 along i, every row along j, layer 2
along k. array[box.index] reads the box’s cells out of an (nx, ny, nz) array. A grid
statement names its cells with one (cells=), and so do the aquifers and boundaries.
Examples
Section titled “Examples”import petroscript as ps m = ps.load(“tests/results_fixtures/QUICKSTART/MERLE.DATA”) # 10 x 10 x 5 cells top_two = m.array(“porosity”)[ps.Box(k=(1, 2)).index] # layers 1 and 2 top_two.shape (10, 10, 2)
ps.Capacity
Section titled “ps.Capacity”ps.Capacity(*, wells: int | None = None, connections_per_well: int | None = None, groups: int | None = None, wells_per_group: int | None = None, well_lists_per_well: int | None = None, dynamic_well_lists: int | None = None, multisegment_wells: int | None = None, segments_per_well: int | None = None, branches_per_well: int | None = None, rows_per_relperm_table: int | None = None, pressures_per_pvt_table: int | None = None, ratios_per_pvt_table: int | None = None, fluid_in_place_regions: int | None = None, fluid_in_place_sets: int | None = None, independent_regions: int | None = None, flux_regions: int | None = None, operation_regions: int | None = None, polymer_mixing_regions: int | None = None, transmissibility_regions: int | None = None, rows_per_depth_table: int | None = None, rows_per_tracer_depth_table: int | None = None, rows_per_miscible_table: int | None = None, production_lift_tables: int | None = None, production_lift_rate_values: int | None = None, production_lift_thp_values: int | None = None, production_lift_water_fraction_values: int | None = None, production_lift_gas_fraction_values: int | None = None, production_lift_artificial_lift_values: int | None = None, injection_lift_tables: int | None = None, injection_lift_rate_values: int | None = None, injection_lift_thp_values: int | None = None, fault_segments: int | None = None, numerical_aquifer_cells: int | None = None, numerical_aquifer_connections: int | None = None, analytic_aquifers: int | None = None, connections_per_analytic_aquifer: int | None = None, influence_tables: int | None = None, rows_per_influence_table: int | None = None, network_nodes: int | None = None, network_branches: int | None = None, actions: int | None = None, lines_per_action: int | None = None, characters_per_action_line: int | None = None, conditions_per_action: int | None = None, functions_per_user_quantity: int | None = None, terms_per_user_quantity: int | None = None, field_user_quantities: int | None = None, group_user_quantities: int | None = None, well_user_quantities: int | None = None, connection_user_quantities: int | None = None, segment_user_quantities: int | None = None, region_user_quantities: int | None = None, aquifer_user_quantities: int | None = None, cell_user_quantities: int | None = None, user_arguments: int | None = None, distinct_user_arguments: int | None = None, user_tables: int | None = None, rows_per_user_table: int | None = None, interpolation_points_per_user_table: int | None = None, water_skin_pressure_tables: int | None = None, polymer_skin_pressure_tables: int | None = None, polymer_molecular_weight_tables: int | None = None, rows_per_polymer_viscosity_mixing_table: int | None = None, oil_tracers: int | None = None, water_tracers: int | None = None, gas_tracers: int | None = None, summary_vectors: int | None = None) -> None
The sizes a run allocates: ps.Capacity(wells=130, connections_per_well=36).
A model never has to state one: the target counts what the model needs (its wells and
their connections, its groups, its tables’ rows) and writes that. An item a model states
is written as stated, raised to the need where the model needs more; an item it leaves as
None on a card it states others of is Flow’s default, raised to the model’s count alike
(the wells, groups and connections; the tables; the equilibration regions; the
multi-segment wells; the aquifers; the operating rules; the tracers; the network; the
polymer mixing regions; the miscibility tables’ rows). A loaded deck keeps the sizes it states
(m.capacity.wells == 130 for Norne).
m.set("capacity/wells", 50) states one. Every item is a whole number, zero or more;
a table’s rows, a multi-segment size and the summary vectors are one or more. Each item
is a slot of a RUNSPEC size card (WELLDIMS, TABDIMS, REGDIMS, EQLDIMS, WSEGDIMS, VFPPDIMS,
AQUDIMS, …), held at capacity/<item>.
ps.CompileError
Section titled “ps.CompileError”ps.CompileError(issues: Iterable[Issue]) -> None
A refused compile, or a run refused before it starts: issues holds the error rows.
The rows are the model’s own, the target’s acceptance rows, its own refusal of a sound model
such as an unknown target, or deck.check()’s before a run.
except ps.CompileError as error: catches a refused compile; error.issues lists each
error row with its code, address and fix.
ps.DeckImportError
Section titled “ps.DeckImportError”ps.DeckImportError(keyword: str, *, file: str, line: int, hint: str, reason: str | None = None) -> None
A deck keyword the importer cannot read onto the model.
except ps.DeckImportError as error: catches a deck ps.load refuses; error.at is the
file and line, error.fix what to do.
address is the keyword, fix what to do, file and line where the deck states
it (at); code the refusal’s kind: a file that cannot be read
(deck.file.unreadable), text that is no deck (deck.text.malformed), an include where
one file was asked for (deck.include.present), a card the deck never states
(deck.card.missing), a keyword with no import (deck.keyword.unsupported), a record
Flow itself refuses (deck.record.malformed), a record Flow runs that its import cannot
read or has no statement for (deck.record.unsupported), a keyword stating fewer regions
than another (deck.regions.short) and a card before its prerequisite
(deck.order.prerequisite).
ps.Event
Section titled “ps.Event”ps.Event(date: Any = None, *changes: Any) -> None
The changes the schedule states at one report time, in the order stated.
ps.Event("2021-01-01", ps.Perforate(...), ps.Produce(...)). The date is a report time
of the schedule: a date or its text, which is how it prints. The text is 'YYYY-MM-DD',
and 'YYYY-MM-DDTHH:MM' (‘2021-01-01T06:00’) where a step ends during a day, its seconds
only where they are not zero; no other form is read. Midnight is the day. The deck writes
the changes’ cards after the time card that reaches the date: the date’s DATES record, or
the TSTEP step that ends there in a ps.Every run; an event at the start has no time card
before it, so its cards come before the first one. The event is held at
schedule/<date> and each change below it (schedule/<date>/<well>/control).
m.at(date, *changes) adds changes to a model’s event.
Examples
Section titled “Examples”import petroscript as ps event = ps.Event(“2009-06-01”, … ps.Perforate(well=“M-1”, k=(1, 2), diameter=0.2), # m … ps.Produce(well=“M-1”, gas_rate=500_000.0, # sm3/day … bhp_limit=50.0)) # barsa str(event.date) ‘2009-06-01’ [type(change).name for change in event.changes] [‘Perforate’, ‘Produce’]
ps.Every
Section titled “ps.Every”ps.Every(*steps: Any) -> None
A run of report steps of the stated lengths, in days: ps.Every(31, 28, 31).
Each length ends one report step after the time the schedule has reached; the deck writes
the run as one TSTEP card, cut where an event is dated at the end of a step inside it. The
lengths are days in every unit system (a “lab” deck’s TSTEP states them in hours). It is one
of ps.Schedule(report=[...])’s items, held at schedule/report.
ps.Fault
Section titled “ps.Fault”ps.Fault(*, name: str, segments) -> None
A named fault: the cell faces it runs along (FAULTS).
ps.Fault(name="F1", segments=[((1, 1), (1, 4), (1, 3), "plus_i")]): each segment is a
row (i, j, k, face), each of i, j and k a 1-based, inclusive (first, last) range of
cells, as ps.Box takes them, and face the cell face the fault runs along
("plus_i" is the cells’ positive i-face, "minus_i" the negative one; "plus_j",
"minus_j", "plus_k", "minus_k" alike). A model’s faults are held at
faults/<n>, in the order stated; ps.grid.FaultMultiplier scales a fault’s
transmissibility (MULTFLT).
ps.Group
Section titled “ps.Group”ps.Group(*, name: str, parent: Group | str | None = None) -> None
A group of wells in the field’s group tree (GRUPTREE), under an optional parent group.
ps.Group(name="G1", parent=north) states group G1 under north, itself a
ps.Group; a well joins it with ps.Well(group=), and a dated ps.Group in an Event
moves it to another parent. The model’s groups are held at groups/<name>.
Groups organize wells for the group controls (ps.groups.ProductionTarget,
ps.groups.InjectionTarget) and for summary reporting; the production network over them
is the model’s ps.groups.Network.
ps.History
Section titled “ps.History”ps.History(*, well, oil_rate: Optional[ScalarPropertyInput] = None, water_rate: Optional[ScalarPropertyInput] = None, gas_rate: Optional[ScalarPropertyInput] = None, control: Optional[Literal['oil_rate', 'water_rate', 'gas_rate', 'liquid_rate', 'reservoir_volume', 'bottom_hole_pressure']] = None, thp: Optional[ScalarPropertyInput] = None, bhp_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, lift_table: ProductionTable | None = None, status: Literal['open', 'shut', 'stop'] = 'open') -> None
Hold the selected producers to observed rates, as a history match does (WCONHIST).
ps.History(well="P1", oil_rate=1200.0, water_rate=300.0, control="oil_rate") holds P1 to its
observed oil rate from this date and records the water rate observed beside it.
Every rate is an observation, so more than one is normal; control names the one the
simulator holds the well to (control="gas_rate"), and it is required when more than
one rate is given. A single rate is the control. Each rate is a surface volume rate in the
model’s units, thp and bhp_limit pressures. It is a dated change, held at
schedule/<date>/<well>/control as a producer’s control is; ps.History.from_frame
builds the dated rows of a whole history from a frame.
lift_table is the vertical-lift table the observed record binds to the
well, WCONHIST 12.3.252 item 7. It is not a control: measured on both
pinned releases, unbinding the table drives the reported tubing-head
pressure to exactly zero (WTHP:PROD1 82.3713 -> 0.0000 barsa) and moves no
bottom-hole pressure at all, which is manual 12.3.252 item 9’s “only used
for comparing” read back out of the simulator. So the binding rides the
observed record rather than the reservoir-control restatement a lift
binding carries — a history well has no control row to restate, and the
measurement says there is nothing to restate.
Left unstated the record defaults the slot, which the target resolves as the table it last declared for this well. That resolution is the target’s; an unstated slot stays unstated here and comes back out defaulted.
ps.Inject
Section titled “ps.Inject”ps.Inject(*, well, phase: Literal['oil', 'water', 'gas'], rate: Union[ScalarPropertyInput, Expression, NoneType] = None, reservoir_volume: Union[ScalarPropertyInput, Expression, NoneType] = None, bhp: Union[ScalarPropertyInput, Expression, NoneType] = None, control: Optional[Literal['group']] = None, rate_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, reservoir_volume_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, bhp_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, thp_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, lift_table: InjectionTable | None = None, bhp_adjustment: Optional[ScalarPropertyInput] = None, pressure_loss_multiplier: Optional[ScalarPropertyInput] = None, lift_calculation: Optional[Literal['implicit', 'explicit']] = None, unstable_thp: Optional[Literal['allow', 'prevent_report_once', 'prevent_report_always']] = None, status: Literal['open', 'shut', 'stop'] = 'open', phase_word: str | None = None) -> None
Control the selected injectors of phase from this date (WCONINJE).
ps.Inject(well="I1", phase="water", rate=500.0, bhp_limit=300.0) injects 500 of water
a day while the bottom-hole pressure stays below 300. The one quantity given bare is the
target; every other quantity is a limit (rate_limit, bhp_limit, …).
well: the well’s name (or theps.Well).phase:"water","gas"or"oil", required.- The target, one of:
rate, the injected phase’s surface rate (stb/day of water or Mscf/day of gas in FIELD, sm3/day in METRIC);reservoir_volume(rb/day, rm3/day); orbhp(psia, barsa). - The limits:
rate_limit,reservoir_volume_limit,bhp_limitandthp_limit.
Common mistakes: the rate is rate=, never water_rate= or gas_rate= (refused,
naming rate=); a gas rate in FIELD is Mscf/day, not scf/day. A new well needs its
control in the same m.set as the well, or it is refused ([well.control.missing]).
A control is a dated change, held at schedule/<date>/<well>/control; ps.Adjust
changes one target or limit and keeps the rest.
control="group" puts the well under its group’s control, with rate_limit,
reservoir_volume_limit and bhp_limit constraining it. lift_table names the
ps.lift.InjectionTable the well injects through, and thp_limit the tubing-head
pressure it holds the well below, read through that table; a table that reuses a number
comes into force at the date of the first control that names it, as ps.Produce’s does.
Examples
Section titled “Examples”import petroscript as ps control = ps.Inject(well=“I1”, phase=“gas”, rate=100_000.0, # sm3/day in METRIC … bhp_limit=300.0) # barsa control.rate 100000.0
On a model m that holds the well, m = m.at("2011-04-01", control) puts it in force
from that date, and m.control("I1", at="2011-04-01") reads it back.
ps.InjectionHistory
Section titled “ps.InjectionHistory”ps.InjectionHistory(*, well, phase: Literal['oil', 'water', 'gas'], rate: ScalarPropertyInput, control: Literal['rate', 'bottom_hole_pressure'] = 'rate', thp: Optional[ScalarPropertyInput] = None, bhp_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, lift_table: InjectionTable | None = None, status: Literal['open', 'shut', 'stop'] = 'open') -> None
Hold the selected injectors to an observed injection rate (WCONINJH).
ps.InjectionHistory(well="I1", phase="water", rate=2000.0) holds I1 to its observed water
injection rate from this date.
control is "rate", the observed rate, or "bottom_hole_pressure", the observed
bottom-hole pressure. The rate is a surface volume rate in the model’s units. It is a dated
change, held at schedule/<date>/<well>/control as an injector’s control is.
lift_table is WCONINJH 12.3.255 item 7, the injection twin of the
binding described on History, and it reads the same way
on both pinned releases: unbinding it takes WTHP:INJ1 from 209.1630 to
0.0000 barsa and moves no bottom-hole pressure. The table is typed, so an
injection record cannot name a production table — which is the refusal
both releases make in their own words when a deck tries it.
ps.Issue
Section titled “ps.Issue”ps.Issue(*, code: str, severity: Literal['error', 'warning', 'info'], address: str | None = None, message: str, fix: str | None = None, value: float | str | None = None, unit: str | None = None, held_against: float | str | None = None, basis: Optional[Literal['flow', 'ill_posed', 'contradiction', 'context']] = None, facts: tuple[str, ...] = ()) -> None
One row of what PetroScript found: [code] message, then fix:.
ps.Issue(code="acme.pressure_floor", severity="warning", message="the limit is below the floor", value=40.0, unit="bar", held_against=50.0) is a row a registered check returns.
Parameters
Section titled “Parameters”code : str
The registered code, "well.control.missing".
severity : {“error”, “warning”, “info”}
An error refuses; a warning prints once at compile; an info row only informs.
address : str, optional
The address of what the model states that the row is about,
"schedule/2021-01-01/P1/control".
message : str
What is wrong, in a sentence.
fix : str, optional
What to type; an error’s comes from the catalog when the check states none.
value, unit, held_against : optional
A plugin check’s measured quantity, its unit and the bound it was held against.
basis : {“flow”, “ill_posed”, “contradiction”, “context”}, optional
Why an error refuses, from the catalog: flow, OPM Flow refuses the deck;
ill_posed, the arithmetic has no answer; contradiction, two statements of the
model that together break a law of physics or state one fact twice; context, a
gate on the authoring. None for a warning or info row.
facts : tuple of str, optional
The derived facts the row rests on, each "facts/<group>/<subject>/<name>", read in
m.describe()["facts"].
ps.Model
Section titled “ps.Model”ps.Model(*, name: str | None = None, title: str | None = None, units: Literal['field', 'metric', 'lab'], start: DayInput, capacity: Capacity = <factory>, dimensions: Dimensions = <factory>, grid: Cartesian | CornerPoint | Radial | Spider, rock: Rock, fluid: dict[int, BlackOil | OilWater | GasWater] | BlackOil | OilWater | GasWater | Compositional | None = None, relperm: dict[int, tuple[OilWater | GasOil | Gas | Water | Oil | GasWater | HydrocarbonRelPerm | LETRelPerm | MaxCapillaryPressure | ThreePhase | EndpointScaling | CriticalTolerance | Hysteresis | JFunction | SaturationOptions, ...]] | tuple[OilWater | GasOil | Gas | Water | Oil | GasWater | HydrocarbonRelPerm | LETRelPerm | MaxCapillaryPressure | ThreePhase | EndpointScaling | CriticalTolerance | Hysteresis | JFunction | SaturationOptions, ...] | None = None, regions: Regions | None = None, passive_tracers: tuple[Tracer, ...] = (), physics: tuple[Package | Package | Package | Package | Package | Package | Package | Package | Package, ...] = (), initial: dict[int, Equilibrium] | Equilibrium | ExplicitState | Restart | None = None, summary: Summary = <factory>, output: Output = <factory>, numerics: Numerics | None = None, edits: tuple[Assign | Scale | Add | Clip | Operate | Copy | CellDepthOverride | PoreVolumeOverride | PoreVolumeScale | FaultMultiplier | NonNeighbor | RegionPairMultiplier, ...] = (), transmissibility: tuple[CellTransmissibilityField | DirectionalTransmissibilityMultiplier | RegionalPoreVolumeMultiplier | RegionPairMultiplier, ...] = (), faults: tuple[Fault | FaultMultiplier, ...] = (), non_neighbor_connections: tuple[NonNeighbor | ConnectionTransmissibilityEdit | NonNeighborConnectionPolicy, ...] = (), wells: tuple[Well, ...] = (), groups: tuple[Group, ...] = (), lift_tables: tuple[ProductionTable | InjectionTable, ...] = (), network: Network | None = None, schedule: Schedule = <factory>, aquifers: tuple[Fetkovich | CarterTracy | Numerical | ConstantFlux | InfluenceTable, ...] = (), boundary_conditions: tuple[BoundaryCondition, ...] = (), acknowledged_warnings: tuple[str, ...] = ()) -> None
A reservoir model: one frozen value, built by keyword, that compiles to a simulator’s deck.
It is the whole case a deck states: ps.compile(m) writes it as one deck, RUNSPEC to
SCHEDULE, and ps.load reads a deck back into one. Every bare number is in the system
units names (“metric”, “field” or “lab”), apart from a duration that times the run (a
report step, a time step’s length or limit, the summary’s write interval, a schedule
event’s interval, wait or period), which is days in every system: a “lab” deck writes it in
hours. Any other time, a table’s value (a polymer’s half-life), is in the system’s own time
unit, hours in “lab”. Its fields are the parts of the reservoir, each read and written by
its address (m.get("rock/porosity")): the case’s name, title, units and
start, then capacity, grid, rock, fluid, relperm, regions,
passive_tracers, physics (the family packages), initial, summary,
output, numerics, edits, transmissibility, faults,
non_neighbor_connections, the wells, groups, lift_tables and network,
the aquifers and boundary_conditions, the acknowledged_warnings, and the dated
changes in schedule.
A model that contradicts itself is refused as it is built (ps.ModelError, holding every
error row); m.check() returns the same rows without raising. Nothing is edited in place:
m.at, m.set, m.until and m.replace return a new model.
Examples
Section titled “Examples”import petroscript as ps pond = ps.Model( … name=“POND”, … units=“metric”, # m, barsa, mD, rm3/sm3, cP, kg/m3 … start=“2020-01-01”, … grid=ps.grid.Cartesian(shape=(5, 5, 2), dx=100.0, dy=100.0, dz=10.0, top=2000.0), … rock=ps.Rock(porosity=0.2, permeability=100.0, vertical_ratio=0.1, … compressibility=4.5e-5, reference_pressure=200.0), # 1/bar at barsa … fluid=ps.fluid.BlackOil( … water=ps.pvt.Water(reference_pressure=200.0, fvf=1.02, compressibility=4.4e-5, … viscosity=0.5, viscosibility=0.0), … water_surface_density=1000.0), … relperm=[ps.relperm.Water(sw=[0.0, 1.0], krw=[0.0, 1.0], pc=[0.0, 0.0])], … initial=ps.initial.Equilibrium(datum_depth=2000.0, datum_pressure=200.0), … schedule=ps.Schedule(end=“2021-01-01”), … ) pond.get(“rock/porosity”) 0.2 pond.grid.shape (5, 5, 2)
ps.ModelError
Section titled “ps.ModelError”ps.ModelError(issues: Iterable[Issue]) -> None
A model that contradicts itself, or a construction refused: issues holds the rows.
except ps.ModelError as error: catches a model refused as it is built; error.issues
holds its rows, each with its code and fix.
It is not a ValueError, so pydantic does not fold its rows into a generic wrapper.
ps.Numerics
Section titled “ps.Numerics”ps.Numerics(*, initial_timestep: Optional[ScalarPropertyInput] = None, maximum_timestep: Optional[ScalarPropertyInput] = None, minimum_timestep: Optional[ScalarPropertyInput] = None, minimum_choppable_timestep: Optional[ScalarPropertyInput] = None, maximum_timestep_growth: Optional[ScalarPropertyInput] = None, maximum_timestep_decay: Optional[ScalarPropertyInput] = None, convergence_failure_decay: Optional[ScalarPropertyInput] = None, convergence_failure_growth: Optional[ScalarPropertyInput] = None, maximum_timestep_after_well_event: Optional[ScalarPropertyInput] = None, target_truncation_error: Optional[ScalarPropertyInput] = None, target_nonlinear_convergence_error: Optional[ScalarPropertyInput] = None, mass_balance_error_target: Optional[ScalarPropertyInput] = None, target_linear_convergence_error: Optional[ScalarPropertyInput] = None, maximum_truncation_error: Optional[ScalarPropertyInput] = None, maximum_nonlinear_convergence_error: Optional[ScalarPropertyInput] = None, maximum_mass_balance_error: Optional[ScalarPropertyInput] = None, maximum_linear_convergence_error: Optional[ScalarPropertyInput] = None, maximum_well_flow_convergence_error: Optional[ScalarPropertyInput] = None, target_local_fluid_in_place_error: Optional[ScalarPropertyInput] = None, target_surfactant_change: Optional[ScalarPropertyInput] = None, ion_exchange_damping_threshold: Optional[ScalarPropertyInput] = None, tracer_implicitness: int | None = None, maximum_newton_iterations: int | None = None, minimum_newton_iterations: int | None = None, maximum_linear_iterations: int | None = None, minimum_linear_iterations: int | None = None, maximum_well_iterations: int | None = None, maximum_thp_iterations: int | None = None, maximum_pressure_change: Optional[ScalarPropertyInput] = None, maximum_saturation_change: Optional[ScalarPropertyInput] = None, target_pressure_change: Optional[ScalarPropertyInput] = None, maximum_tolerable_pressure_change: Optional[ScalarPropertyInput] = None, minimum_newton_iterations_before_bisection: int | None = None, pressure_residual_solver=None, well_target_update: int | None = None, maximum_segment_iterations: int | None = None, maximum_segment_solves: int | None = None, segment_tolerance_reduction: float | None = None, segment_tolerance_growth: float | None = None, user_quantity_magnitude_limit: float | None = None, user_quantity_undefined_value: float | None = None, user_quantity_equality_tolerance: float | None = None, network_balance_interval: int | None = None, network_pressure_tolerance: Optional[ScalarPropertyInput] = None, maximum_network_iterations: int | None = None, network_thp_tolerance: Optional[ScalarPropertyInput] = None, maximum_network_thp_iterations: int | None = None, stack_depth: int | None = None, constrained_pressure_residual: bool | None = None, linear_solver: str | None = None, fully_implicit: bool | None = None, data_check_only: bool | None = None, parallel_domains: int | None = None, parallel_method: Optional[Literal['distributed', 'serial']] = None, next_step: Optional[ScalarPropertyInput] = None, next_step_persists: bool = False) -> None
The run’s numerical settings: ps.Numerics(maximum_timestep=5.0, ...).
The time-step lengths and their growth and decay factors, the target and maximum convergence
errors and the iteration ceilings a run may spend on a step (TUNING, manual 12.3.231); the
pressure-residual preconditioner and the Newton iterations in which well targets update (CPR,
NUPCOL); the multisegment-well solver’s iterations (WSEGITER); how user quantities
evaluate (UDQPARAM); when the production network is balanced and how its iterations converge
(NETBALAN); the solver hints (stack_depth, constrained_pressure_residual,
fully_implicit: NSTACK, CPR, FULLIMP; linear_solver, which Flow takes only on its
command line: deck.run() does not pass it, and the compile warns) and the run mode
(data_check_only, parallel_domains, parallel_method: NOSIM, PARALLEL). OPM Flow
honours TUNING’s time-step controls only when it runs with --enable-tuning=true (off by
default), which deck.run() does not pass; the compile warns where the model states them.
A model states its start settings in Model.numerics. Inside ps.Event(date, ...) a
ps.Numerics is a dated change: a re-tuning (the TUNING fields it states replace those in
force), a re-balanced network (the NETBALAN fields it states), a limit on the next time step
(next_step, NEXTSTEP; next_step_persists keeps it applying), or a restated
well-target update (well_target_update, NUPCOL).
A start-only setting stated at a date is refused. The time-step lengths are days in every
unit system (a “lab” deck’s TUNING states them in hours). The start settings are held at
numerics/<field>, a dated one at schedule/<date>/numerics.
ps.Open
Section titled “ps.Open”ps.Open(*, well, completions=None, cell: tuple[int | None, int | None, int | None] | None = None) -> None
Open the selected wells from this date: ps.Open(well="P1") (WELOPEN, OPEN).
A dated change, held at schedule/<date>/<well>/status; completions= or cell=
opens connections instead of the well (ps.Shut says how the three scopes differ). A
reopened well flows under the control it held.
ps.Output
Section titled “ps.Output”ps.Output(*, static_file: bool = True, static_file_endpoints: bool = False, static_file_minus_multipliers: bool = False, restart=None, initial_restart=None, print=None, print_run_specification: bool | None = None, print_grid: tuple[str, ...] = (), print_solution: tuple[str, ...] = (), print_fluid_in_place: Optional[Literal[1, 2, 3]] = None, print_restart_output: Optional[Literal[1, 2, 3]] = None, print_properties: tuple[str, ...] = (), print_regions: tuple[str, ...] = (), grid_file: Optional[Literal['none', 'standard', 'extended']] = None, egrid_file: bool | None = None, static_index: bool | None = None, restart_index: bool | None = None, message_file: bool | None = None, formatted_input: bool | None = None, formatted_output: bool | None = None, spreadsheet_summary: bool | None = None, summary_dates: bool | None = None, message_limits=None, table_extrapolation: Optional[Literal['off', 'pvt', 'vfp', 'all', 'detailed']] = None, monitoring: bool | None = None) -> None
What a run writes beside its answers: ps.Output(static_file=True, print=...).
static_file writes the static file, the grid’s static arrays (INIT, manual 6.3.97; the
file run.static reads), and static_file_endpoints adds the saturation end points the
target derives to it (FILLEPS, manual 8.3.68), for a post-processor to read;
static_file_minus_multipliers adds the minus-direction transmissibility multipliers
(MULTX-, MULTY-, MULTZ-; each 1.0 where the grid states none) to it (GRIDOPTS item 1, TRANMULT
YES, manual 5.3.59). restart is
what the restart files hold from the schedule’s start (RPTRST in SCHEDULE, manual 12.3.207)
and initial_restart what the initial restart holds (RPTRST in SOLUTION, manual 10.3.79).
print is the schedule’s printed report (RPTSCHED, manual 12.3.208); the print_*
fields are the other sections’ printed reports: print_run_specification (RPTRUNSP),
print_grid (RPTGRID), print_solution with its print_fluid_in_place and
print_restart_output levels (RPTSOL), print_properties (RPTPROPS) and
print_regions (RPTREGS), each item a semantic name (these cards’ older integer form Flow
answers as if the record were not there, measured on both supported releases, so a deck’s
integers are read past). print and restart carry their cards’
integer form as stated, each position the request the card’s own table gives it (RPTSCHED,
manual 12.3.208; RPTRST, 12.3.207). The file fields choose
the output files’ forms (grid_file, egrid_file, static_index, restart_index,
message_file, formatted_input, formatted_output, spreadsheet_summary,
summary_dates); message_limits, table_extrapolation and
monitoring the run’s messages. The summary vectors a run allocates (SMRYDIMS) are
ps.Capacity(summary_vectors=).
Inside ps.Event(date, ...) a ps.Output states print or restart (or both) from
that report time on; the other fields are the run’s own and are refused at a date. The
model’s own is held at output/<field> (m.set("output/static_file", True)).
ps.Perforate
Section titled “ps.Perforate”ps.Perforate(*, well, k: tuple[int, int], i: int | None = None, j: int | None = None, diameter: Optional[ScalarPropertyInput] = None, status: Literal['open', 'shut', 'stop'] = 'open', saturation_region: int | None = None, connection_factor: Optional[ScalarPropertyInput] = None, kh: Optional[ScalarPropertyInput] = None, skin_factor: float | None = None, non_darcy_factor: Optional[ScalarPropertyInput] = None, direction: Optional[Literal['x', 'y', 'z']] = None, pressure_equivalent_radius: Optional[ScalarPropertyInput] = None) -> None
One dated connection of a well to the grid (COMPDAT): a run of layers in one column.
ps.Perforate(well="P1", k=(1, 3), diameter=0.2) connects P1 to layers 1 to 3 of its
head’s column; i and j state another column (a deviated well). A perforation is a
change the schedule dates, never a field of the well: a well is an identity, and what
changes with time lives in the schedule. It is held at
schedule/<date>/<well>/perforation/<n>, the well’s n-th perforation at that date.
Parameters
Section titled “Parameters”well : str or Well
The well’s name, or the well.
k : tuple of int
The first and last layer, 1-based and inclusive.
i, j : int, optional
The column, 1-based; the well’s head when left out.
diameter : float, optional
The wellbore diameter, a length in the model’s units.
status : {“open”, “shut”, “stop”}
Whether the connection is open; open by default.
saturation_region : int, optional
The saturation region whose tables give the connection’s mobilities (COMPDAT item 7):
the well function, which governs the inflow alone; the cell’s own region still moves
the fluids around it. Left out, the cell’s.
connection_factor : float, optional
The connection’s transmissibility factor between the wellbore and the cell (COMPDAT
item 8), a viscosity times a reservoir volume per time per pressure in the model’s
units, used as stated. Left out, or zero or less, the simulator computes it by
Peaceman’s formula from kh, the diameter, skin_factor and the cell’s
radius across direction (or pressure_equivalent_radius).
kh : float, optional
The connection’s permeability-thickness product (COMPDAT item 10), a permeability times
a length in the model’s units. With no stated connection_factor, a kh above zero
gives the factor; left out, or zero or less, the simulator takes it from the cell: its
permeability across the bore times its length along direction. With a stated factor,
kh does not change the factor but is still read: it sets the connection’s length
(kh over the cell’s permeability across the bore, which the non-Darcy correlation
and a filter cake’s flow area read) and the Peaceman denominator a later
ps.wells.Skin rescales the factor by. There, left out or negative, the simulator
derives it from the factor, the diameter, the cell’s radius (or
pressure_equivalent_radius) and skin_factor; zero, it takes it from the cell.
skin_factor : float, optional
The connection’s skin factor, dimensionless (COMPDAT item 11); left out, 0. With no
stated connection_factor it enters the computed factor’s Peaceman denominator.
With a stated factor the factor stays as stated, but the skin enters the kh
derived from it when kh is left out, and a later ps.wells.Skin rescales the
factor by its change from this skin.
non_darcy_factor : float, optional
The non-Darcy D factor of a gas well’s rate-dependent skin (COMPDAT item 12), a time per
surface gas volume in the model’s units: zero or more states the well’s, which the
simulator shares among its connections; negative states this connection’s own, its sign
reversed. Left out, none.
direction : str, optional
The direction the wellbore crosses the cell (COMPDAT item 13): "x", "y" or
"z". It orients the cell for the connection: the cell’s permeabilities and sizes
across the bore give its Peaceman radius, and their permeability (the geometric mean of
the two) times the cell’s length along the bore gives the cell’s kh; the
connection’s length is kh over that permeability, whether or not the factor is
stated. Left out, "z".
pressure_equivalent_radius : float, optional
The connection’s pressure-equivalent radius, a length (COMPDAT item 14), in place of
the one Peaceman’s formula gives from the cell. Left out, Peaceman’s.
Examples
Section titled “Examples”import petroscript as ps m = ps.load(“tests/results_fixtures/QUICKSTART/MERLE.DATA”) # the quickstart’s field m = m.at(“2010-01-01”, ps.Perforate(well=“M-1”, k=(3, 4), diameter=0.2)) # m m.get(“schedule/2010-01-01/M-1/perforation/1”).k (3, 4)
ps.PetroScriptError
Section titled “ps.PetroScriptError”ps.PetroScriptError(*args: Any, code: str | None = None, address: str | None = None, fix: str | None = None, at: str | None = None) -> None
A refusal PetroScript raises: code, address, message, fix and at.
except ps.PetroScriptError as error: catches every refusal PetroScript raises,
error.code naming its kind.
message is the text the error prints; at is the author’s script line where the
refusal was met (an import’s is the deck’s file and line).
ps.PetroScriptWarning
Section titled “ps.PetroScriptWarning”The one category PetroScript prints its warnings through, one line per code.
ps.compile prints a model’s and its deck’s warning rows through it, once each.
warnings.filterwarnings("ignore", category=ps.PetroScriptWarning) silences them for the
session; a model that means what a physics warning flags states its code in
acknowledged_warnings.
ps.Produce
Section titled “ps.Produce”ps.Produce(*, well, oil_rate: Union[ScalarPropertyInput, Expression, NoneType] = None, water_rate: Union[ScalarPropertyInput, Expression, NoneType] = None, gas_rate: Union[ScalarPropertyInput, Expression, NoneType] = None, liquid_rate: Union[ScalarPropertyInput, Expression, NoneType] = None, reservoir_volume: Union[ScalarPropertyInput, Expression, NoneType] = None, bhp: Union[ScalarPropertyInput, Expression, NoneType] = None, thp: Union[ScalarPropertyInput, Expression, NoneType] = None, control: Optional[Literal['group']] = None, oil_rate_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, water_rate_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, gas_rate_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, liquid_rate_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, reservoir_volume_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, bhp_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, thp_limit: Union[ScalarPropertyInput, Expression, NoneType] = None, lift_table: ProductionTable | None = None, artificial_lift_quantity: Union[ScalarPropertyInput, Expression, NoneType] = None, bhp_adjustment: Optional[ScalarPropertyInput] = None, pressure_loss_multiplier: Optional[ScalarPropertyInput] = None, lift_calculation: Optional[Literal['implicit', 'explicit']] = None, unstable_thp: Optional[Literal['allow', 'prevent_report_once', 'prevent_report_always']] = None, status: Literal['open', 'shut', 'stop'] = 'open') -> None
Control the selected producers from this date (WCONPROD).
The one quantity given bare is the target; every other quantity is a limit
(bhp_limit, liquid_rate_limit, …) and constrains the well beside it::
ps.Produce(well="P1", oil_rate=1500.0, liquid_rate_limit=3000.0, bhp_limit=100.0)The target is one of oil_rate, water_rate, gas_rate, liquid_rate,
reservoir_volume, bhp or thp (a tubing-head pressure, which the well’s lift
table turns into a rate). Each is in the model’s units: a rate is a surface volume rate (a
reservoir volume rate for reservoir_volume), bhp and thp are pressures. A control
is a dated change, held at schedule/<date>/<well>/control; a well holds one control
per date, and m.control(well, at=) reads the one in force.
control="group" puts the well under its group’s control: the
group sets the target, so every stated quantity is a limit. lift_table names the
ps.lift.ProductionTable the well flows through, and thp_limit the tubing-head
pressure it holds the well above, read through that table::
ps.Produce(well="P1", gas_rate=5.0e5, bhp_limit=50.0, thp_limit=30.0, lift_table=t1)A producer opened, shut or stopped with its targets cleared states its status and no target
(WCONPROD with its control mode defaulted): ps.Produce(well="P1", status="shut"); “open”
is stated (a producer stating no status and no target is refused, its target forgotten). The
limits it states are written where the deck writes them and hold no target (measured: they
leave the run as it is). ps.Shut changes the status alone and leaves the targets standing.
The lift settings ride with the table: artificial_lift_quantity (the value on the
table’s lift axis), bhp_adjustment and pressure_loss_multiplier (WVFPDP),
lift_calculation and unstable_thp (WVFPEXP).
A table the model does not hold yet joins Model.lift_tables. A control at a later date
may name a different table under a number (number) in use: the well flows through it
from that date and the deck writes the table there, which is refused while another well’s
control or a network member still names the earlier table (give those wells a control at
that date naming the new table, or give the table its own number).
Examples
Section titled “Examples”import petroscript as ps m = ps.load(“tests/results_fixtures/QUICKSTART/MERLE.DATA”) # the quickstart’s field held = ps.Produce(well=“M-1”, gas_rate=450_000.0, bhp_limit=60.0) # sm3/day, barsa m = m.at(“2010-03-01”, held) m.get(“schedule/2010-03-01/M-1/control”) == held True
ps.Quantity
Section titled “ps.Quantity”ps.Quantity(value: float, unit: str) -> None
A number with its own unit: ps.Quantity(30.0, "barsa").
value is the number and unit the word of the unit it is counted in; kind is what
it is read as (a pressure, a length, a time, …), a unit’s own kind until a slot says, and is
never stated.
The slot it is given to says what it is: a pressure slot reads barsa as a pressure and
converts it to the model’s unit system, and refuses a unit of another kind; a unit that names
one kind (barsa, m, day) is that kind wherever it stands. A field that states no
kind (a fraction, a factor, a compressibility), a grid statement’s number and a condition’s
or a formula’s number take a bare number in the model’s units and refuse a Quantity
([units.wrong_kind]). ps.help("units") lists the units each kind takes. A unit’s case is
not read (PSIA, degc), but in the pascal words, which are read as written: Pa,
kPa, MPa (mPa is a millipascal, and no pressure a model states).
Examples
Section titled “Examples”import petroscript as ps m = ps.load(“tests/results_fixtures/QUICKSTART/MERLE.DATA”) # metric: barsa m = m.set(“initial/1/datum_pressure”, ps.Quantity(3626.0, “psia”)) # read as a pressure m.get(“initial/1/datum_pressure”) ps.Quantity(value=3626.0, unit=‘psia’)
ps.Regions
Section titled “ps.Regions”ps.Regions(*, saturation=None, relperm_x=None, relperm_y=None, relperm_z=None, pvt=None, equilibration=None, rock=None, flux=None, transmissibility=None, operation=None, fluid_in_place=None, fluid_in_place_sets=<factory>, imbibition=None, imbibition_x=None, imbibition_y=None, imbibition_z=None, miscible=None, polymer_mixing=None) -> None
The model’s region maps (SATNUM, PVTNUM, …): which region of each kind each cell is in.
ps.Regions(pvt=[1, 1, 2], saturation=1) puts the first two layers in pvt region 1 and the
third in region 2, and every cell in saturation region 1.
saturation assigns saturation-function region IDs to cells (SATNUM); relperm_x,
relperm_y and relperm_z the tables a cell’s flow reads in each direction. pvt,
equilibration, and rock select PVT, equilibration, and rock/compaction
families (PVTNUM, EQLNUM, ROCKNUM); fluid_in_place the regions the fluid in place is
reported by (FIPNUM), and fluid_in_place_sets further named sets of them,
{name: map}
(FIP<name>, the name 1 to 5 upper-case letters or digits); flux,
transmissibility and operation the regions grid statements
(ps.grid.InRegion) and region-pair multipliers select; miscible the miscible region
whose tables a cell reads (MISCNUM) and polymer_mixing its polymer mixing region
(PLMIXNUM). Every map is one region
everywhere (a positive integer), one region per layer (pvt=[1, 1, 2], each held in
every cell of its layer) or an explicit (nx, ny, nz) NumPy tensor of them.
The sizes a deck states beside the maps (REGDIMS items 4 and 7, GRIDOPTS item 2)
are ps.Capacity’s flux_regions, operation_regions and
transmissibility_regions; the maps carry only the regions they assign. The maps are
held at regions/<map> (regions/fluid_in_place). m.array reads most as a cell
array named by the map’s name joined to region with an underscore
(m.array("fluid_in_place_region")), with two other spellings: transmissibility is
"multiplier_region" (MULTNUM) and relperm_x, relperm_y and relperm_z are
"relperm_region_x" and its kin; imbibition_x, imbibition_y, imbibition_z,
miscible, polymer_mixing and fluid_in_place_sets have no m.array name.
Examples
Section titled “Examples”import numpy as np import petroscript as ps m = ps.load(“tests/results_fixtures/QUICKSTART/MERLE.DATA”) # 10 x 10 x 5 cells layers = np.ones(m.grid.shape, dtype=int) layers[:, :, 3:] = 2 # layers 4 and 5 m = m.set(“regions”, ps.Regions(fluid_in_place=layers)) m.array(“fluid_in_place_region”)[0, 0, :].tolist() [1, 1, 1, 2, 2]
ps.Rock
Section titled “ps.Rock”ps.Rock() -> None
The rock’s porosity, permeability and pore-volume compressibility (PORO, PERMX, ROCK).
Each cell property is in the model’s units: porosity a fraction, each permeability a
permeability, compressibility a compressibility (per unit of pressure) and
reference_pressure a pressure. The deck writes PORO and PERMX, PERMY and PERMZ in the
GRID section and ROCK in PROPS; the rock is held at rock/<field>
(m.get("rock/porosity")), and m.array("permeability_z") reads a cell array after
the grid program.
The permeability is stated in one of two forms, direction by direction, and the model holds
the form stated: permeability=150.0, vertical_ratio=0.1 (the horizontal permeability,
along x and y, and the vertical permeability as a share of it: permeability_z is
vertical_ratio times the permeability along x), or permeability_x,
permeability_y and permeability_z each on its own. The forms mix across directions
(permeability=150.0, permeability_z=15.0); one direction stated in both is refused.
Each cell property (porosity, permeability, permeability_x, permeability_y,
permeability_z, dispersivity) is a single value, one value per layer
(porosity=[0.20, 0.25, 0.30], the model spreading each over its layer’s cells), or a full
(nx, ny, nz) array matching the model grid. vertical_ratio is one number, zero or
more, read of the permeability the rock states, before the model’s grid statements: a later
ps.grid.Scale("permeability_x", by=0.5) leaves permeability_z where the ratio put it.
compressibility is the pore volume’s compressibility at reference_pressure
(ROCK), stated together: ps.Rock(..., compressibility=4.5e-5, reference_pressure=250.0),
or one value each per PVT region as two lists of one length (per rock region with
table_regions="rock_regions"). porosity is the porosity
at reference_pressure: the simulator grows the pore volume from there with the pressure,
and m.describe()["facts"]["rock"] shows each rock region’s pore_volume_factor at its
datum pressure.
compaction is the pore volume’s response to pressure beyond compressibility: a
ps.grid.RockPressureCompaction (multipliers tabulated against pressure) or a
ps.grid.RockCompaction (its water-induced form); None states none.
table_regions names the region family that allocates the rock property
tables to cells. It stands on its own: a model may choose the allocation
while stating only pore-volume compressibility, which is what the manual
means by allocating “the various ROCK and ROCKTAB property tables”
(8.3.259 item 3). Left unstated, the target’s own default applies.
Examples
Section titled “Examples”import petroscript as ps rock = ps.Rock(porosity=0.22, permeability=150.0, vertical_ratio=0.1, # mD: metric … compressibility=4.5e-5, reference_pressure=250.0) # 1/bar, barsa m = ps.load(“tests/results_fixtures/QUICKSTART/MERLE.DATA”) # metric m = m.set(“rock”, rock) float(m.array(“permeability_z”)[0, 0, 0]) # 0.1 x 150 mD 15.0
ps.RunError
Section titled “ps.RunError”ps.RunError(message: str, *, folder: str | None = None, exit_code: int | None = None) -> None
Raised when a simulator could not run a deck to its end, or none was found to run it.
except ps.RunError as error: catches a run that stopped short; error.folder holds its
files and error.exit_code the simulator’s exit status.
The message carries what the simulator said: the first error lines of its print file and
the last lines of its terminal output. folder is where its files are and exit_code
what it exited with (both None when no simulator started).
ps.Schedule
Section titled “ps.Schedule”ps.Schedule(*, end: Optional[DateInput] = None, report: Union[Literal['monthly'], tuple[Union[DateInput, Every], ...]] = (), initial_timesteps=(), events: tuple[Event, ...] = ()) -> None
The run’s report times and what changes at each: ps.Schedule(end=, report=, events=).
It is the deck’s SCHEDULE section: each report time a DATES record (a ps.Every run a
TSTEP card), each event’s changes the cards after the time card that reaches its date (an
event at the start: before the first time card). events holds ps.Event(date, *changes), each at a report time, its changes (a well’s perforations and controls among
them) in the order stated. report states the report times: each a date (a date and its
time of day where the deck states one) or a ps.Every(...) run of steps in days, or
"monthly", the first of each month to end. An action rule is one of an event’s
changes, and so are the start’s dissolution limits, Python hooks, user tables and
quantities, well equipment and gas lift (written at the deck’s head). The wells, groups and
network are the model’s. Collection fields are tuples so a constructed schedule cannot be
mutated in place. It is held at schedule: schedule/end, schedule/report and each
event at schedule/<date>; m.until moves the end and m.at dates a change.
Examples
Section titled “Examples”import petroscript as ps schedule = ps.Schedule( … end=“2011-06-01”, … report=“monthly”, # the first of each month, and the end … events=[ps.Event(“2009-06-01”, … ps.Perforate(well=“M-1”, k=(1, 2), diameter=0.2), # m … ps.Produce(well=“M-1”, gas_rate=500_000.0, # sm3/day: metric … bhp_limit=50.0))], # barsa … ) m = ps.load(“tests/results_fixtures/QUICKSTART/MERLE.DATA”) # the quickstart’s field m.replace(schedule=schedule).find(“schedule///*”) [‘schedule/2009-06-01/M-1/perforation/1’, ‘schedule/2009-06-01/M-1/control’]
ps.Shut
Section titled “ps.Shut”ps.Shut(*, well, completions=None, cell: tuple[int | None, int | None, int | None] | None = None) -> None
Shut the selected wells at the surface from this date: ps.Shut(well="P1"), WELOPEN SHUT.
A dated change, held at schedule/<date>/<well>/status; the well’s control and targets
stand, and ps.Open reopens it. The change reaches the well itself (no completions,
no cell), every connection (completions="all"), the connections of a range of
completion numbers (completions=(2, 3); one number is that one completion), or the
connections in a cell (cell=(10, 10, 3), a range beside it narrowing it; None on an
axis is any index there, so cell=(None, None, 3) shuts every connection in layer 3).
The three scopes are three different runs.
Examples
Section titled “Examples”import petroscript as ps m = ps.load(“tests/results_fixtures/QUICKSTART/MERLE.DATA”) # the quickstart’s field m = m.at(“2011-05-01”, ps.Shut(well=“M-2”)) m.find(“schedule/2011-05-01//”) [‘schedule/2011-05-01/M-2/status’] m.control(“M-2”, at=“2011-05-01”).status ‘shut’
ps.Stop
Section titled “ps.Stop”ps.Stop(*, well, completions=None, cell: tuple[int | None, int | None, int | None] | None = None) -> None
Stop the selected wells from this date, open to the reservoir: ps.Stop(well="P1").
It writes WELOPEN with STOP, held at schedule/<date>/<well>/status. OPM Flow leaves a
flowing well flowing after STOP, a producer producing and an injector injecting;
ps.Shut closes a well at the surface.
ps.Summary
Section titled “ps.Summary”ps.Summary(*, field: tuple[str, ...] = (), wells: dict[str, tuple[str, ...]] = {}, groups: dict[str, tuple[str, ...]] = {}, completions: dict[tuple[str, int, int, int, int], tuple[str, ...]] = {}, blocks: dict[tuple[int, int, int], tuple[str, ...]] = {}, aquifers: dict[typing.Union[int, typing.Literal['*']], tuple[str, ...]] = {}, numerical_aquifers: dict[typing.Union[int, typing.Literal['*']], tuple[str, ...]] = {}, regions: dict[typing.Union[int, typing.Literal['*'], tuple[str, typing.Union[int, typing.Literal['*']]]], tuple[str, ...]] = {}, region_pairs: dict[tuple[int, int], tuple[str, ...]] = {}, segments: dict[str | tuple[str, int], tuple[str, ...]] = {}, connections: dict[str | tuple[str, int, int, int], tuple[str, ...]] = {}, undeclared_wells: dict[str, tuple[str, ...]] = {}, undeclared_groups: dict[str, tuple[str, ...]] = {}, undeclared_connections: dict[str, tuple[str, ...]] = {}, user_defined: dict[str, tuple[str, ...]] = {}, tracers: dict[str, dict[typing.Literal['field', 'wells', 'groups', 'undeclared_wells', 'undeclared_groups'], tuple[str, ...] | dict[str, tuple[str, ...]]]] = {}, undeclared_tracers: dict[str, dict[typing.Literal['field', 'wells', 'groups', 'undeclared_wells', 'undeclared_groups'], tuple[str, ...] | dict[str, tuple[str, ...]]]] = {}, history: dict[typing.Literal['field', 'wells', 'groups', 'undeclared_wells', 'undeclared_groups'], tuple[str, ...] | dict[str, tuple[str, ...]]] = {}, column_words: tuple[str, ...] = (), performance: bool = False, run_summary: bool = False, separate_reports: bool = False, well_status_vectors: bool = False, write_frequency: Optional[Literal['report_steps_only', 'every_timestep']] = None, write_interval: float | Quantity | None = None) -> None
The vectors a run records, by member: ps.Summary(field=[...], wells={...}, ...).
You ask for each result by the name you later read it by:
ps.Summary(field=["oil_production_rate"], wells={"*": ["bottom_hole_pressure"]}), then
run.field("oil_production_rate") and run.wells("bottom_hole_pressure"). field
takes a list of names; wells, groups and regions take {member: [names]}, with
"*" for every well or group. Common names: oil_production_rate,
gas_production_rate, water_production_rate, oil_production_total,
water_cut, gas_oil_ratio, bottom_hole_pressure, pressure. ps.help("WBHP")
turns a Flow mnemonic into its name.
Common mistakes: a Flow mnemonic ("FOPR") is refused, naming its name; wells=["P1"]
is refused (a map is needed: wells={"P1": [...]}); and m.summary.replace(wells=...)
replaces every well’s requests, as m.set("summary/field", [...]) replaces the field’s
list. To add one without dropping the others, keep what is there:
m.set("summary/field", [*m.summary.field, "water_cut"]), or set the one quantity’s own
address, m.set("summary/field/water_cut", "water_cut"); for one well,
m.set("summary/wells/P1", [*m.summary.wells.get("P1", ()), "water_cut"]).
It is the deck’s SUMMARY section, one card per vector (gas_production_rate is FGPR),
and what run.field, run.wells, run.groups and run.regions read back by the
same names. It is held at summary.
field takes the field quantities by their names; every other scope takes
{member: [names]}, the quantities asked for each member, a bare string one quantity
(wells={"P1": "water_cut"}):
wells: a well’s name, a pattern ("P*", the wells it matches when Flow reads it) or"*";groups: a group’s name or"*". A well or group the model does not declare is refused (summary.wells.unknown);undeclared_wells,undeclared_groupsandundeclared_connections(a well’s name, for its connections) keep the names a deck asks for and declares nothing behind (Flow runs it and writes no column);blocks: an(i, j, k)cell;completions:(well, i, j, k1, k2), a perforated interval;connections:(well, i, j, k)or a well’s name for each of its connections (one entry per well);segments:(well, number), a well’s name for each of its segments or"*";regions: a region number or"*", another fluid-in-place set’s region as("LAY", 2);region_pairs:(from, to);aquifersandnumerical_aquifers: an aquifer’s number or"*";user_defined: a user quantity’s export name, holding the members asked for (none: every member);tracers: a tracer’s quantities (production_rate,production_total,injection_rate,injection_total,production_concentration,injection_concentration) by scope, shaped ashistoryis:tracers={"WT1": {"field": ["production_rate"], "wells": {"P1": ["production_total"]}}}asks for FTPRWT1 and WTPTWT1; a tracer’s"*"and well patterns are Flow’s, stated inundeclared_wells(undeclared_groupsfor"*"), as a deck’s record loads;undeclared_tracerskeeps a tracer the model declares none of by the name a deck asks for it (Flow runs it and writes no column).
The polymer and MICP quantities are the scopes’ own names (polymer_injection_rate,
microbes_in_place, oxygen_injection_rate_per_completion).
The names are the ones a run’s results are read by (oil_in_place,
water_production_rate); a deck’s mnemonic ("FOPR") is refused naming its name.
history takes the observed vectors the same way, by scope:
history={"wells": {"*": ["oil_production_total"]}} requests WOPTH. The model reads its
summary by address: summary/field/<quantity>, summary/<scope>/<member>
(m.set("summary/wells/M-3", m.get("summary/wells/M-1"))), the observed ones under
summary/history/....
column_words names a column by the second word Flow reads its vector by:
column_words=["BOKR"] writes a block’s oil_relative_permeability as BOKR, and the
column carries that word; the vector’s own word listed beside it
(column_words=["FGSR", "FSGR"]) writes both cards, one column each. A word is written
while its vector is asked for; one Flow reads no vector by is refused listing the words.
The target writes one card per vector and word, the vectors in its own order first (FOPR,
FOPT, the wells’ and blocks’ it lists) and the rest in the order they first appear walking
the members (each member’s quantities in the order stated, the observed after them), each
card’s members in member order. A summary read from a deck keeps the deck’s own card order
while it is what the deck states (an edit writes the walk).
The policies say what else the summary file carries: performance (PERFORMA),
run_summary (RUNSUM), separate_reports (SEPARATE), well_status_vectors (FMWSET),
write_frequency (RPTONLY or RPTONLYO) and write_interval (SUMTHIN: a number of days,
or a time ps.Quantity, above 0; a LAB deck writes it in hours).
Examples
Section titled “Examples”import petroscript as ps summary = ps.Summary(field=[“gas_production_rate”, “pressure”], # sm3/day, barsa … wells={"": [“bottom_hole_pressure”]}) # every well’s summary.field (‘gas_production_rate’, ‘pressure’) summary.wells[""] (‘bottom_hole_pressure’,)
On a model m, m.set("summary", summary) replaces its requests, and
m.get("summary/wells/*") reads one member’s back.
ps.Well
Section titled “ps.Well”ps.Well() -> None
A well: an identity. Its name, its head’s column, its group and its declared properties.
ps.Well(name="P1", i=5, j=5) declares P1 with its head in column (5, 5). What a well
does over time is not a field of the well: its perforations (ps.Perforate) and its
controls are changes the schedule dates, and a well’s role, producer or injector, is the
control it is given. The deck declares it with WELSPECS, at the start or with its first
change at introduced; it is held at wells/<name>.
Parameters
Section titled “Parameters”name : str
The well’s name; no * or ?.
i, j : int, optional
The head’s column, 1-based. Left out on a well with a trajectory, Flow takes that axis
from the first cell the trajectory crosses (WELSPECS item 3 or 4 defaulted), each axis
on its own, and PetroScript never states one for it.
group : Group, optional
The group the well belongs to; none states no group.
bhp_reference_depth : float, optional
The depth the bottom-hole pressure is referred to, a length in the model’s units; the
target derives it when left out.
preferred_phase : {“oil”, “water”, “gas”}, optional
The phase the well is declared to prefer. Left out, it is derived from its controls: an
injector’s is the phase it injects, a producer’s the phase its target names (a gas or a
water rate), else the first of oil, gas and water the model holds.
The deck’s WELSPECS always writes a phase, the derived one when this is left out, and
the well loaded back from that deck holds it stated: a well that leaves it out does
not compare equal to its own round trip. Stating it is not enough on its own: a well
that leaves group out loads back in ps.Group(name="FIELD").
introduced : date, datetime or its text, optional
The report time the well is introduced at ("2011-03-01"): its declaration is written
with its first change on that date (a ps.Perforate, or its control). None declares
it at the start.
connection_order : str, optional
The order the well’s connections are numbered in: "input_sequence", the order its
perforations are stated (COMPORD’s INPUT, written in the block that declares the well).
None leaves Flow’s own order.
trajectories : list of ps.wells.Trajectory, optional
The well’s path, one ps.wells.Trajectory per branch (WELTRAJ), with the connections
along it (COMPTRAJ), written in the block that declares the well.
Examples
Section titled “Examples”import petroscript as ps m = ps.load(“tests/results_fixtures/QUICKSTART/MERLE.DATA”) # the quickstart’s field m = m.set({ # a new well mid-run … “wells/M-3”: ps.Well(name=“M-3”, i=5, j=5, introduced=“2010-01-01”), … “schedule/2010-01-01/M-3/perforation/1”: … ps.Perforate(well=“M-3”, k=(1, 2), diameter=0.2), # m … “schedule/2010-01-01/M-3/control”: … ps.Produce(well=“M-3”, gas_rate=300_000.0, bhp_limit=50.0), # sm3/day, barsa … }) m.find(“wells/*”) [‘wells/M-1’, ‘wells/M-2’, ‘wells/M-3’]
ps.compile
Section titled “ps.compile”ps.compile(model: Model, target: str = 'opm-flow') -> Deck
Compile a model for one simulator and return the Deck: its input files, in memory.
ps.compile(m) is the deck of m for opm-flow, one <NAME>.DATA file;
deck.write("out") writes its files there and deck.run() runs it.
The Deck is held in memory: nothing is written and nothing is run.
deck.write(folder) writes its files; deck.issues holds the rows the
compile attached. The model’s warnings and the deck’s are printed here, once:
one line per code, through ps.PetroScriptWarning.
Parameters
Section titled “Parameters”model : Model The model to compile. target : str The simulator’s one name.
Returns
Section titled “Returns”Deck The target’s files for the model.
Raises
Section titled “Raises”TypeError
When model is not a ps.Model.
CompileError
target.unknown when no target has the name target; else the error rows: the
model’s own (m.check()), what the target cannot run of it, and
schedule.report.missing for a schedule with no report time (state its end).
Examples
Section titled “Examples”import petroscript as ps m = ps.load(“tests/results_fixtures/QUICKSTART/MERLE.DATA”) # the quickstart’s field deck = ps.compile(m) # for opm-flow list(deck.files) [‘MERLE.DATA’] deck.files[“MERLE.DATA”].count(“WCONPROD”) # one per control date 2
Targets (the default is opm-flow): opm-flow measured on release 2026.04 jutuldarcy measured on release 0.4.0
ps.help
Section titled “ps.help”ps.help(subject: object = None) -> None
Print what PetroScript knows about subject, or the quickstart index.
subject is anything a script can name: a class or a value, a deck keyword
("WCONINJE"), a summary mnemonic ("FGPR") or a result’s name
("gas_production_total"), a deck word ("RATE"), a report mnemonic
("ALLPROPS"), an issue code ("well.control.missing"), a topic ("addresses",
"facts", "units") or a public path ("ps.aquifers.CarterTracy"). An unknown name is
answered with the nearest.
Parameters
Section titled “Parameters”subject : object, optional What to answer; nothing prints the quickstart index.
Examples
Section titled “Examples”import petroscript as ps ps.help(“FGPR”) # a summary mnemonic: the name it is read by FGPR: the field quantity gas_production_rate ps.Summary(field=[“gas_production_rate”]); run.field(“gas_production_rate”)
ps.load
Section titled “ps.load”ps.load(path: str | Path) -> Model
Read a deck into a ps.Model: its output and numerics are the model’s.
ps.load("model/CASE.DATA") reads the deck and its includes.
A deck with something the model cannot mean is refused whole, every gap listed at once: the
first refusal’s line, then N more gaps: and one line per other gap in deck order, each
led by its code and ended by (K times) when the deck states it more than once.
Parameters
Section titled “Parameters”path : str or Path
The deck’s main file (CASE.DATA); its includes are read where it names them.
Returns
Section titled “Returns”Model The model the deck states.
Raises
Section titled “Raises”DeckImportError
When the deck states something the model cannot mean. The message is the first gap,
at its file and line, its code on error.code (deck.keyword.unsupported,
deck.record.malformed, …; ps.DeckImportError lists them); each further gap
follows on a line of its own, led by its code in brackets.
ModelError
When what the deck states contradicts itself, the rows located at the deck’s file and
line.
Examples
Section titled “Examples”import petroscript as ps m = ps.load(“tests/results_fixtures/QUICKSTART/MERLE.DATA”) # the quickstart’s deck m.name, m.units, m.grid.shape (‘MERLE’, ‘metric’, (10, 10, 5)) m.find(“wells/*”) [‘wells/M-1’, ‘wells/M-2’]
ps.simulators
Section titled “ps.simulators”ps.simulators() -> list[Simulator]
List the simulators the targets find to run a deck, each target’s in its own order.
ps.simulators() lists every simulator found, each with the target it runs for
(found.name); a deck’s deck.run() takes the first one for the deck’s own target.
For opm-flow: the PETROSCRIPT_FLOW environment variable, then flow on PATH. Each is
asked its version; one that does not answer, or whose first line is not OPM Flow’s banner
(flow and a release, flow 2026.04), is left out. deck.run() takes the first rung
present and raises RunError when it is missing or not OPM Flow; deck.check() does the
same for PETROSCRIPT_FLOW, and with it unset leaves out a flow on PATH that is not OPM
Flow (its pre-flight is then not computed).
For jutuldarcy: the PETROSCRIPT_JUTULDARCY environment variable, naming a runner (a
program that runs Julia with JutulDarcy and GeoEnergyIO loadable) whose driver.jl --version first prints jutuldarcy and a release; one that does not is left out.
Returns
Section titled “Returns”list
One Simulator per executable found: its target, its version line and its path.
PetroScript is licensed under the Apache License 2.0. © 2026 Blauweiss EDV LLC. PetroScript by Blauweiss AI.For AI agents: llms.txt