Skip to content

Facilities and networks

PetroScript models surface production systems as semantic objects: named network nodes backed by groups, directed branches routed through typed vertical-flow-performance (VFP) tables, node efficiency factors, and a network-balancing convergence policy. The compiler owns every keyword layout; authors describe the production system, not simulator records.

OPM Flow’s network balancing requires all three phases (oil, water, gas) to be active — the model gate rejects a scheduled network on two-phase fluids with issue code schedule.networks.requires_three_phases instead of letting the simulator abort.

import petroscript as ps
field = ps.Group(name="FIELD")
plat_a = ps.Group(name="PLATA", parent=field)
plat_b = ps.Group(name="PLATB", parent=field)
injectors = ps.Group(name="INJE", parent=field)
table = ps.lift.ProductionTable(
number=1,
bhp_reference_depth=2510.0,
rate_kind="liquid",
rate=[100.0, 1000.0, 6000.0],
thp=[100.0, 300.0],
artificial_lift=[0.0, 100.0],
artificial_lift_kind="gas_lift_rate",
# one pressure per (thp, water fraction, gas fraction, artificial lift, rate)
bhp=[
[[[[1200.0, 1700.0, 2300.0], [1200.0, 1700.0, 2300.0]]]],
[[[[1300.0, 1800.0, 2400.0], [1300.0, 1800.0, 2400.0]]]],
],
)
field_node = ps.groups.Node(name="FIELD", group=field, terminal_pressure=200.0)
plat_a_node = ps.groups.Node(name="PLATA", group=plat_a, efficiency=0.95)
plat_b_node = ps.groups.Node(name="PLATB", group=plat_b)
network = ps.groups.Network(
name="production-network",
nodes=[field_node, plat_a_node, plat_b_node],
branches=[
ps.groups.Branch(
name="plata-to-field",
from_node=plat_a_node,
to_node=field_node,
lift_table=table,
artificial_lift_quantity=50.0,
),
ps.groups.Branch(
name="platb-to-field",
from_node=plat_b_node,
to_node=field_node,
uses_no_pressure_loss=True,
),
],
)
model = ps.Model(
name="PLATFORM_NETWORK",
units="field",
start="2020-01-01",
capacity=ps.Capacity(wells=3, connections_per_well=1, groups=3, wells_per_group=3),
grid=ps.grid.Cartesian(shape=(2, 2, 1), dx=500.0, dy=500.0, dz=40.0, top=2500.0),
rock=ps.Rock(porosity=0.25, permeability=100.0, vertical_ratio=1.0),
fluid=ps.fluid.BlackOil(
oil=ps.pvt.DeadOil(pressure=[14.7, 3500.0], oil_fvf=[1.06, 1.0], oil_viscosity=[1.0, 1.2]),
gas=ps.pvt.DryGas(pressure=[14.7, 3500.0], gas_fvf=[200.0, 0.9], gas_viscosity=[0.008, 0.02]),
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,
gas_surface_density=0.053,
water_surface_density=64.0,
),
relperm=[
ps.relperm.OilWater(sw=[0.20, 0.80], krw=[0.0, 1.0], krow=[1.0, 0.0], pcow=[0.0, 0.0]),
ps.relperm.GasOil(
sg=[0.0, 0.75, 0.80], krg=[0.0, 0.9, 1.0], krog=[1.0, 0.0, 0.0], pcog=[0.0, 0.0, 0.0]
),
],
initial=ps.initial.Equilibrium(
datum_depth=2510.0, datum_pressure=3000.0, oil_water_contact=2600.0, gas_oil_contact=2400.0
),
numerics=ps.Numerics(
network_balance_interval=-1,
network_pressure_tolerance=1.0,
maximum_network_iterations=10,
network_thp_tolerance=0.01,
maximum_network_thp_iterations=10,
),
groups=[field, plat_a, plat_b, injectors],
wells=[
ps.Well(name="PRODA", i=1, j=2, group=plat_a),
ps.Well(name="PRODB", i=2, j=2, group=plat_b),
ps.Well(name="INJ1", i=1, j=1, group=injectors),
],
lift_tables=[table],
network=network,
schedule=ps.Schedule(
report=[ps.Every(30.0)],
events=[
ps.Event(
"2020-01-01",
ps.Perforate(well="PRODA", k=(1, 1), diameter=0.3),
ps.Perforate(well="PRODB", k=(1, 1), diameter=0.3),
ps.Perforate(well="INJ1", k=(1, 1), diameter=0.3),
ps.Produce(
well="PRODA",
oil_rate=150.0,
bhp_limit=1000.0,
thp_limit=150.0,
lift_table=table,
artificial_lift_quantity=0.0,
),
ps.Produce(well="PRODB", oil_rate=120.0, bhp_limit=1000.0),
ps.Inject(well="INJ1", phase="water", rate=300.0, bhp_limit=4500.0),
)
],
),
)
  • ps.groups.Node(name=..., group=..., ...) is a node for a group, or an explicit junction by its name alone. Terminal nodes carry a fixed terminal_pressure; non-terminal nodes leave it unset.
  • ps.groups.Branch(name=..., from_node=..., to_node=..., ...) connects nodes downstream-to-upstream. Route through a production lift table (lift_table=), declare a lossless route with uses_no_pressure_loss=True, or take a route out of service with is_disabled=True.
  • ps.groups.Network(name=..., nodes=..., branches=...) assembles the extended network, held by the model’s network=; kind="standard" builds the standard group network instead (no branches — routing follows the group tree).
  • The compiler sizes the network dimensions from the declared topology (node count first, then branch count), with each node and branch a dated branch adds.

The model’s network is the start’s. A ps.groups.Branch or ps.groups.Node stated in a later ps.Event changes it at that date, as OPM Flow applies the card there:

  • a dated branch is a BRANPROP record. It adds a branch, replaces the branch above its from_node (a re-route), or drops one (is_disabled=True, item 3 = 0). A node it names that the network does not hold yet is added;
  • a dated node is a NODEPROP record in an extended network, and a GRUPNET record in a standard one, restating that node from its date. A standard network’s pipeline follows a dated ps.Group that moves the group, as Flow’s does.

The start’s network says which card a dated node is, so a dated change needs ps.Model(network=...). A branch in a standard network, and a dated node’s efficiency (NEFAC is the start’s), are refused (schedule.networks.dated_change).

artificial_lift_quantity on a branch models a pump or compressor working the route: it is interpolated on the routing table’s artificial-lift axis, so it requires lift_table=... and a table whose lift axis spans the value. OPM Flow can also read the branch lift value as a surface-density equivalence (BRANPROP item 5); PetroScript does not map that yet, and refuses a deck stating it.

ps.groups.Node(name=..., group=..., efficiency=0.95) declares an extended-network node efficiency factor (1.0 minus downtime fraction, in (0, 1]). Standard networks reject node efficiencies — state a dated ps.groups.Efficiency(group=..., factor=...) for group efficiency factors instead.

On a standard network, a group node may carry a pipeline artificial_lift_quantity (pump/compressor duty on the pipeline to the parent group) and add_gas_lift=True to include well lift gas in the pipeline flow. uses_no_pressure_loss=True states a pipeline with no pressure drop (GRUPNET item 3 = 9999): the group’s node is at its parent’s pressure. An extended network states that on the branch instead. The tubing-pressure manifold balancing option and pipeline density resets are rejected by OPM Flow for non-default values and stay non-public.

ps.Numerics(...) states when the network is balanced and how it converges (NETBALAN): network_balance_interval (0 at the start of each time step, a number of days, or -1 in each step’s first NUPCOL Newton iterations); network_pressure_tolerance and maximum_network_iterations bound the nodal-pressure iteration, while network_thp_tolerance and maximum_network_thp_iterations bound the tubing-head-pressure iteration for wells balancing against a common manifold and for automatic chokes. The model’s numerics write it at the schedule’s head; a ps.Numerics in a later ps.Event balances the network differently from that date. The branch-error timestep controls are flagged unsupported by OPM Flow and are not exposed.

ps.lift.ProductionTable(...) states what each fraction axis holds: water_fraction_kind is water cut ("water_cut"), water-oil ratio ("water_oil_ratio"), or water-gas ratio ("water_gas_ratio" — gas-rate flow axis only); gas_fraction_kind is gas-oil ratio ("gas_oil_ratio"), gas-liquid ratio ("gas_liquid_ratio"), or oil-gas ratio ("oil_gas_ratio" — gas-rate flow axis only). The compiler derives the table’s fraction tokens from the axis kinds. artificial_lift_kind declares what the lift axis quantifies ("gas_lift_rate", "injection_gas_liquid_ratio", "total_gas_liquid_ratio" or "pump_rating"): the kinds OPM Flow reads on VFPPROD. Flow 2026.04 refuses a compressor (COMP), choke (BEAN) or surface-density (DENO, DENG) lift axis, so none is written.

Terminal pressures, tolerances, and lift quantities use the active unit system; the branch lift quantity’s unit follows the routing table’s artificial-lift axis. Node efficiency is a dimensionless fraction in (0, 1]. THP tolerance is a dimensionless fraction of the group rate target.

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