Skip to content

Boundary conditions and source terms

PetroScript models external mass and heat exchange as reservoir concepts. A boundary condition (ps.grid.BoundaryCondition) states a face of a box of cells at the grid’s edge and what happens there — free flow, a signed phase rate, a fixed Dirichlet pressure, or a constant temperature — and a source term (ps.grid.SourceTerm) injects or withdraws a physical component in one cell. Compiler lowerers own the ECL/Flow keywords (BCCON, BCPROP, and SOURCE).

State boundary conditions in the model’s boundary_conditions=[...], and a cell source term as a ps.grid.SourceTerm among the changes of a ps.Event:

import petroscript as ps
influx = ps.grid.BoundaryCondition(
number=1,
cells=ps.Box(i=(1, 1), j=(1, 3), k=(1, 1)),
face="minus_i",
kind="dirichlet",
phase="water",
pressure=3200.0,
)
open_edge = ps.grid.BoundaryCondition(
number=2, cells=ps.Box(i=(3, 3), j=(1, 3), k=(1, 1)), face="plus_i", kind="free"
)
field = ps.Group(name="FIELD")
model = ps.Model(
name="BOUNDARY_CASE",
units="field",
start="2020-01-01",
grid=ps.grid.Cartesian(shape=(3, 3, 1), dx=1000.0, dy=1000.0, dz=50.0, top=2500.0),
rock=ps.Rock(porosity=0.2, permeability=100.0, vertical_ratio=1.0),
fluid=ps.fluid.OilWater(
oil=ps.pvt.DeadOil(pressure=[14.7, 3500.0], oil_fvf=[1.06, 1.0], oil_viscosity=[1.0, 1.2]),
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,
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])],
initial=ps.initial.Equilibrium(
datum_depth=2510.0, datum_pressure=3000.0, oil_water_contact=2600.0, gas_oil_contact=0.0
),
groups=[field],
wells=[ps.Well(name="PROD1", i=3, j=3, group=field)],
boundary_conditions=[influx, open_edge],
schedule=ps.Schedule(
report=[ps.Every(30.0)],
events=[
ps.Event(
"2020-01-01",
ps.Perforate(well="PROD1", k=(1, 1), diameter=0.3),
ps.Produce(well="PROD1", oil_rate=150.0, bhp_limit=2500.0),
ps.grid.SourceTerm(cell=(2, 2, 1), component="water", rate=1500.0),
)
],
),
)

The boundary connections lower to BCCON in the GRID section, the control behaviors lower to BCPROP in the SCHEDULE section, and each source term lowers to a SOURCE record at its event’s position in the schedule timeline.

A control keeps the POSITION it was stated at. An imported BCPROP standing after a time card is filed there rather than at the schedule head, because the statement includes when it was made: a deck that runs a day and only then imposes a Dirichlet pressure runs that day unconstrained on purpose, and hoisting the card imposes the condition from t=0 instead. Controls stated at the head still lower at the head, which is the ordinary case.

Only the placement travels on the timeline. What a control SAYS is written once, on the boundary itself, and the render reads it back by identifier — so the head block and the schedule position can never disagree about a record.

  • kind="free" — keep the initial boundary state throughout the run.
  • kind="rate" with phase= and rate= — constant influx (negative) or efflux (positive) mass rate per unit area for one phase.
  • kind="dirichlet" with phase=, pressure= and an optional temperature= — fixed boundary pressure; the temperature requires an active ps.thermal.Package(...) in the model’s physics=[...].
  • kind="thermal" with temperature= — constant-temperature boundary with no mass flow; requires an active thermal package. Omitting the temperature uses the simulator’s initial-state value.

Boundary conditions are unit-system agnostic: the same semantic objects lower for FIELD, METRIC, and LAB models.

ps.grid.SourceTerm(cell=..., component=..., rate=..., heat_rate=..., temperature=...) defines one cell source (positive rate) or sink (negative rate) of one component, stated in a ps.Event. Solvent, polymer, and microbe/oxygen/urea components require their activating packages (ps.solvent.Package, ps.polymer.Package, ps.micp.Package). heat_rate and temperature are thermal-only and mutually exclusive.

  • source_term.cell.bounds — the source cell must lie inside the grid.
  • source_term.component.requires_solvent / ...requires_polymer / ...requires_micp — activate the matching package first.
  • source_term.thermal.requires_thermal, lab_boundary.temperature.requires_thermal, and lab_boundary.control.requires_thermal — temperature-bearing boundary or source intents need ps.thermal.Package(...).
  • lab_boundary.identifier.unique and lab_boundary.cell_range.bounds — boundary identifiers must be unique and connections must stay in bounds.

A flux aquifer whose strength the schedule states

Section titled “A flux aquifer whose strength the schedule states”

initial_water_flux_rate is optional, and a flux aquifer that omits it is a complete statement: its connections declare it, and its strength arrives as a schedule statement — ps.aquifers.Flux(...) changes in a ps.Event — at whatever position the timeline gives it. Setting the rate at the first scheduled position is not the same as an initial rate, and only the second writes a SOLUTION record; opm-tests/aquifers/AQUFLUX-02 is a deck of the first shape and AQUFLUX-01 of the second.

What is not a statement is a connection nothing claims. An aquifer declared by connections alone, with no initial rate and no schedule statement anywhere, is refused by name.

Prescribed-flux aquifers accept thermal-only state alongside their connections: ps.aquifers.ConstantFlux(..., temperature=..., pressure=...) requires an active thermal package and lowers into the AQUFLUX optional record slots. Finite aquifers take an explicit water PVT-table choice with ps.aquifers.Fetkovich(..., pvt_region=...).

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