Describe the fluids
The fluid is the model’s fluid=: one object that says which phases flow and gives each phase’s
PVT table and surface density. Its class is the phase set:
| The reservoir holds | Class | Tables |
|---|---|---|
| Oil and water (a dead oil) | ps.fluid.OilWater | oil=ps.pvt.DeadOil, water=ps.pvt.Water |
| Gas and water | ps.fluid.GasWater | gas=ps.pvt.DryGas, water=ps.pvt.Water |
| Oil, gas and water, or oil and gas (a live oil is always here) | ps.fluid.BlackOil | oil=, gas=, water= as they flow |
| CO2 or H2 in brine | ps.fluid.BlackOil plus a ps.storage.Package in physics= | storage, brine and salt |
| Components (equation of state) | ps.fluid.Compositional | loads, but no target runs it yet (below) |
A table is named columns, one value per row, each a list, a tuple or a NumPy array. Every number is in the model’s units: here METRIC (barsa, rm3/sm3, sm3/sm3, cP, kg/m3). In FIELD a gas-oil ratio is Mscf/stb and a gas formation volume factor rb/Mscf (units).
The examples share one small model around the fluid:
import petroscript as ps
water = ps.pvt.Water(reference_pressure=200.0, fvf=1.01, compressibility=4.5e-5, viscosity=0.4, viscosibility=0.0)oil_water_curves = ps.relperm.OilWater(sw=[0.2, 0.8], krw=[0.0, 1.0], krow=[1.0, 0.0], pcow=[0.0, 0.0])gas_oil_curves = ps.relperm.GasOil(sg=[0.0, 0.8], krg=[0.0, 1.0], krog=[1.0, 0.0], pcog=[0.0, 0.0])
def model(fluid, initial, relperm): """A 2 x 2 x 2 box with one producer, around the fluid being shown.""" return ps.Model( name="FLUID", units="metric", start="2025-01-01", grid=ps.grid.Cartesian(shape=(2, 2, 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=5.0e-5, reference_pressure=200.0), fluid=fluid, initial=initial, relperm=relperm, wells=[ps.Well(name="P", i=1, j=1)], schedule=ps.Schedule(end="2025-03-01", report="monthly", events=[ ps.Event("2025-01-01", ps.Perforate(well="P", k=(1, 2), diameter=0.2), ps.Produce(well="P", oil_rate=100.0, bhp_limit=50.0))]), )Dead oil and water
Section titled “Dead oil and water”A dead oil has no gas in it: its formation volume factor and viscosity against pressure.
dead = ps.fluid.OilWater( oil=ps.pvt.DeadOil(pressure=[50.0, 200.0, 300.0], # barsa oil_fvf=[1.12, 1.10, 1.09], # rm3/sm3 oil_viscosity=[1.9, 2.0, 2.1]), # cP water=water, oil_surface_density=850.0, water_surface_density=1020.0, # kg/m3)m = model(dead, ps.initial.Equilibrium(datum_depth=2000.0, datum_pressure=200.0, oil_water_contact=2030.0), [oil_water_curves])Live oil, gas and water
Section titled “Live oil, gas and water”A live oil holds dissolved gas. Its table is in long form: the rows that share a solution_gor
are one family, the first at the bubble point and the rest above it, and the last family needs
rows above its bubble point. A live oil flows beside a gas table, so the fluid is a
ps.fluid.BlackOil, and its initial state needs two more facts: the gas-oil contact, and how
much gas the oil holds at each depth (ps.initial.RsVsDepth, or the bubble point by depth,
ps.initial.PbVsDepth).
live = ps.fluid.BlackOil( oil=ps.pvt.LiveOil(solution_gor=[50.0, 100.0, 100.0], # sm3/sm3 pressure=[100.0, 200.0, 300.0], # barsa: 300 is above the oil_fvf=[1.20, 1.30, 1.28], # bubble point of the last oil_viscosity=[1.0, 0.8, 0.85]), # family (100 sm3/sm3) gas=ps.pvt.DryGas(pressure=[50.0, 400.0], gas_fvf=[0.02, 0.003], # rm3/sm3 gas_viscosity=[0.013, 0.025]), water=water, oil_surface_density=850.0, gas_surface_density=0.9, water_surface_density=1020.0,)initial = ps.initial.Equilibrium( datum_depth=2005.0, datum_pressure=200.0, oil_water_contact=2020.0, gas_oil_contact=1990.0, # above the top: no gas cap solution_gor_by_depth=ps.initial.RsVsDepth(depth=[1990.0, 2020.0], solution_gor=[100.0, 100.0]),)m = model(live, initial, [oil_water_curves, gas_oil_curves])What goes wrong, with the message you get:
- Oil and water only, stated as
ps.fluid.BlackOil:InputError: Model: the model's phases are oil and water, which is the ps.fluid.OilWater system. - An oil or gas surface density left out is refused, by phase (
[fluids.density.oil.missing],[fluids.density.gas.missing]); a water density left out is a warning (fluids.density.water.defaulted): Flow takes fresh water’s 999.014 kg/m3. - A live oil inside
ps.fluid.OilWater:[phases.dissolved_gas.requires_gas] Dissolved gas requires the gas phase. A live oil is aps.fluid.BlackOilwith a gas table. - The last live-oil family has only its bubble-point row:
[physics.pvt.undersaturated_missing]. - No gas-oil contact where gas and oil both flow:
[initialization.equil.required_value] EQUIL gas_oil_contact is required. - No depth table, the datum away from the gas-oil contact:
[physics.initial.datum_off_gas_oil_contact].
try: model(live, initial.replace(solution_gor_by_depth=None), [oil_water_curves, gas_oil_curves])except ps.ModelError as refused: print(refused) # [physics.initial.datum_off_gas_oil_contact] ... fix: State the depth tableGas that carries oil (wet gas) in a deck you loaded
Section titled “Gas that carries oil (wet gas) in a deck you loaded”A wet gas (ps.pvt.WetGas, Flow’s PVTG) needs the run’s vaporized-oil switch and, like a live
oil, a depth table in the initial state. On a loaded deck all three change together, in one
m.set:
spe1 = ps.load("opm-tests/spe1/SPE1CASE1.DATA") # FIELD: psia, stb/Mscf, rb/Mscfwet = ps.pvt.WetGas(pressure=[14.7, 14.7, 4014.7, 4014.7], vaporized_oil_gas_ratio=[0.0001, 0.0, 0.0002, 0.0], gas_fvf=[166.666, 166.0, 0.6, 0.59], gas_viscosity=[0.008, 0.008, 0.03, 0.03])state = spe1.get("initial/1").replace( vaporized_oil_by_depth=ps.initial.RvVsDepth(depth=[8300.0, 8450.0], vaporized_oil_gas_ratio=[0.0002, 0.0002]), use_vaporized_oil_table=True,)spe1 = spe1.set({ "fluid/1/gas": wet, "dimensions/phases/vaporized_oil": True, # VAPOIL "initial/1": state,})Setting the table alone is refused with [fluids.gas.pvtg_without_vapoil] A ps.pvt.WetGas table requires the vaporized-oil phase; switching the phase on without the depth table, with
[physics.initial.datum_off_gas_oil_contact].
Gas and water
Section titled “Gas and water”A gas field over water is a ps.fluid.GasWater: a ps.pvt.DryGas table, the water, their
surface densities and, for PetroScript’s checks, the table’s temperature. The
quickstart builds one.
Components (equation of state)
Section titled “Components (equation of state)”ps.fluid.Compositional describes a fluid by its components (ps.pvt.Components), an equation of
state and its binary interaction coefficients. ps.load reads a compositional deck, you can
inspect and edit it, and m.to_python() writes it back as Python. No target compiles one yet:
ps.compile raises ps.CompileError [target.unsupported]. A model whose fluid is compositional
also needs the reservoir temperature (temperature=). A compositional fluid is also how you
describe the stream for a NeqSim lift table.
ps.help(ps.fluid.BlackOil), ps.help(ps.pvt.LiveOil) and the
fluids reference have every field.
PetroScript is licensed under the Apache License 2.0. © 2026 Blauweiss EDV LLC. PetroScript by Blauweiss AI.For AI agents: llms.txt