Skip to content

Fluids and PVT

Black-oil simulation separates reservoir behavior into oil, water, and gas phases. PVT data explains how each phase’s volume and viscosity change with pressure and, for oil, solution gas. PetroScript models the implemented SPE1 style of single-region black-oil PVT with explicit semantic objects instead of raw table cards.

The phases a model holds, and their solution-gas coupling, are read off its fluid system: the system’s class names the phase set (see Fluid systems below) and its PVT tables the coupling. Dissolved gas (oil carrying solution gas) is a ps.pvt.LiveOil oil table. Vaporized oil (gas carrying vaporized oil) is a ps.pvt.WetGas gas table, and requires active oil and gas phases. Vaporized water is a ps.pvt.VaporizedWaterGasPVT gas table in a gas-water system, and requires an initial RVW state.

ps.pvt.Water(...) is water around a pressure anchor. The formation-volume factor maps surface water volume to reservoir volume; compressibility and viscosibility describe the local pressure response used by the simulator. Each value must be a finite deck-native scalar (float/int) in the model’s units, and reference_pressure also takes a ps.Quantity of pressure; numeric strings are rejected instead of being silently coerced.

ps.pvt.DryGas(...) is a dry-gas table, held by column: pressure, gas_fvf and gas_viscosity, each a list, a tuple or a 1-D array, one value per row; DryGas.from_frame reads them from a frame by name and table.frame() returns them. Rows should be ordered by increasing pressure. PVT table arrays must contain finite numeric values; numeric strings and non-finite values are rejected.

import petroscript as ps
gas = ps.pvt.DryGas(
pressure=[14.7, 264.7],
gas_fvf=[166.666, 12.093],
gas_viscosity=[0.008, 0.0096],
)

ps.pvt.WetGas holds vaporized-oil gas PVT in long form: the columns pressure, vaporized_oil_gas_ratio, gas_fvf and gas_viscosity, the rows of one pressure together as its family, the saturated row first. This keeps the public API semantic: users provide pressure-indexed wet-gas behavior, not backend records.

Advanced vaporized-oil gas tables are currently exercised in executable Flow-backed parity examples; ps.pvt.WetGas.from_frame reads the long-form columns by name.

ps.pvt.VaporizedWaterGasPVT holds its table in long form: the columns pressure, vaporized_water_gas_ratio, gas_fvf and gas_viscosity, the rows of one pressure together as its family. Use it only with the explicit gas-water formulation and vaporized-water intent; PetroScript rejects a wet-water gas table in ordinary dry-gas models so generated decks do not miss the required VAPWAT/RVW context.

ps.pvt.LiveOil holds live-oil PVT in long form: the columns solution_gor, pressure, oil_fvf and oil_viscosity, the rows of one solution_gor together as its family. A family’s first row is saturated, at its bubble-point pressure, and the rest are undersaturated. Every column must contain finite numeric values; use numeric arrays/list-like data, not deck-lexeme strings.

Advanced live-oil PVT families are covered by executable parity examples; ps.pvt.LiveOil.from_frame reads the long-form columns by name.

ps.pvt.DeadOil(...) is a dead-oil table, for oil without active dissolved gas. Its columns are pressure, oil_fvf and oil_viscosity. Use it for oil/water cases where oil PVT is pressure-only rather than organized by solution gas-oil-ratio families. Its table data follows the same finite numeric-array contract as DryGas.

oil = ps.pvt.DeadOil(pressure=[14.7, 5014.7], oil_fvf=[2.0, 1.827], oil_viscosity=[0.2, 0.449])

ps.pvt.ConstantCompressibilityOil is a dead-oil constant-compressibility PVT anchor. The semantic fields are reference pressure, oil FVF, oil compressibility, oil viscosity, and viscosity compressibility. The lowerer emits PVCDO; callers do not spell the keyword in public code.

ps.solvent.CO2LiveOilPVT models bounded CO2-dynamic black-oil/EOR oil PVT. Authors state its rows, the CO2 mass fraction first (each fraction’s rows together, the fractions rising), plus the conventional live-oil baseline and injected-gas density/PVT that OPM Flow requires to allocate the extended black-oil model. PetroScript lowers those support facts to PVTO, SDENSITY, and PVDS next to PVTSOL; it does not activate the conventional SOLVENT/CO2SOL model or expose a general compositional framework.

The fluid system’s fields oil_surface_density, gas_surface_density and water_surface_density state finite scalar surface-condition phase densities. These connect reservoir calculations to stock-tank/surface units used by reports and controls.

A deck’s GRAVITY (the oil’s API gravity, the water’s and gas’s specific gravities) loads as the three surface densities OPM Flow reads from it, by Flow’s own conversion. The loaded deck writes its GRAVITY back while the densities are the ones it means, and DENSITY once one is edited.

The fluid system is the model’s fluid=. The class is the formulation: an oil-water system is a ps.fluid.OilWater, a gas-water one a ps.fluid.GasWater, and every other phase set a ps.fluid.BlackOil. A system keeps the PVT tables required by its active phases together, with the surface densities, so validation and lowering can reason about a coherent fluid description.

water = ps.pvt.Water(reference_pressure=3000.0, fvf=1.0, compressibility=3.0e-6,
viscosity=0.5, viscosibility=0.0)
system = ps.fluid.OilWater(oil=oil, water=water, oil_surface_density=53.0,
water_surface_density=64.0)

A model with several PVT regions states one system per region, {1: system, 2: system}. A system’s temperature is the temperature its tables are stated at (degF in FIELD, degC in METRIC); an isothermal run writes no card for it, and the implied z-factor check reads it.

Under FIELD, pressure is psi, viscosity is cP, gas/oil ratios follow deck-native surface-volume conventions, and formation-volume factors are deck-native ratios. A scalar field that states a kind (a table’s reference pressure, a system’s temperature) takes a number in the model’s units or a ps.Quantity of that kind; the others (surface densities, viscosities, compressibilities, formation-volume factors) take numbers in the model’s units and refuse a ps.Quantity. Strings and non-finite values fail at model construction. Every PVT table is held by column, each column 1-D and all of one length; the family tables (LiveOil, WetGas, VaporizedWaterGasPVT) hold their families in long form. Surface densities are in the active unit system.

Common mistakes include pressure rows that are not monotonically increasing, negative viscosity or formation-volume factors, enabling dissolved gas without an oil table that represents solution-GOR families, enabling vaporized oil without a wet-gas PVT table, enabling vaporized water without a vaporized-water gas table and initial RVW, omitting the baseline/support gas data for CO2-dynamic oil PVT, mixing dry-gas and wet-gas tables, and mixing unit systems inside a single model. PetroScript validates table width and several monotonic/positive constraints for the implemented tables.

Current status: emitted now for the implemented black-oil PVT path.

  • OIL, GAS, WATER, and DISGAS are emitted in RUNSPEC from the fluid system’s phases when enabled.
  • PVTW is emitted from Water.
  • PVDG is emitted from DryGas.
  • PVTG is emitted from WetGas when vaporized oil is active.
  • PVTGW is emitted from VaporizedWaterGasPVT when vaporized water is active.
  • PVTO is emitted from LiveOil.
  • PVDO is emitted from DeadOil.
  • PVCDO is emitted from ConstantCompressibilityOil.
  • PVTSOL is emitted from CO2LiveOilPVT together with its compiler-owned PVTO/SDENSITY/PVDS support consequences.
  • DENSITY is emitted from the system’s surface densities.
  • GRAVITY is written back for a loaded deck that states it, while the densities are the ones it means.

A ragged PVT keyword (PVTO, PVTG, PVTGW) states one table body per PVT region. A deck says that a region uses the region before it by leaving that region’s body empty — a bare terminator with no rows.

The semantic surface holds that as what it is: the two regions carry the same table object. Put one table into two regions’ systems and the restatement writes the rows once and a bare terminator for the repeat, the way the deck did; build two equal-but-separate tables and both bodies are written out. Importing a deck of this shape shares the table the same way.

The first region cannot be empty — there is nothing before it to repeat — and that stays refused by name.

Compositional EOS fluids are modelled (ps.fluid.Compositional with ps.pvt.Components): ps.load reads a compositional deck into a model you can inspect and edit like any other, and m.to_python() writes it back as Python that rebuilds it. Neither target compiles one yet: ps.compile raises ps.CompileError [target.unsupported] for opm-flow (OPM Flow 2026.04 runs no equation-of-state fluid) and for jutuldarcy (until its compositional support lands). A compositional fluid also describes the stream of a NeqSim lift table (how-to). Thermal PVT and broader multi-region PVT are future/unsupported.

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