Storage, brine, and salt chemistry
PetroScript exposes CO2/H2 storage, brine salinity, and salt-precipitation behavior through semantic physics objects. Users describe reservoir intent; the compiler owns the OPM Flow keyword spelling, section placement, record order, and slash/default-token details.
CO2 storage activity model
Section titled “CO2 storage activity model”For CO2 storage brine physics, choose the salting-out activity model by its word
on the storage package, a ps.storage.Package in the model’s physics=:
import petroscript as ps
storage = ps.storage.Package( kind="co2", components=["co2"], gas_dissolves_in_water=True, activity_model="duan_sun_direct",)The words ("rumpf_spycher_pruess_iterative", "duan_sun_iterative",
"duan_sun_direct") are the public surface. Do not pass integer model codes or
build a card object directly. H2 storage intentionally rejects this field because it is CO2
activity-model intent. Model validation also requires a brine package before
the activity model can lower, so the salting-out model stays inside the
Flow-evidenced CO2-brine context.
Uniform salt state
Section titled “Uniform salt state”A uniform salt state is a storage-model concept: it describes the whole field at once for the CO2-brine model that CO2STORE/H2STORE activates, and it belongs to the storage package. The brine package owns the depth profile and the per-cell salinity field instead.
storage = ps.storage.Package( kind="co2", components=["co2"], salinity=ps.brine.UniformSalinity(basis="molality", value=1.0),)brine = ps.brine.Package( pvt=ps.brine.WaterPVT( reference_pressure=3000.0, surface_reference_salinity=0.0, rows=[ (0.0, 1.0, 3.0e-6, 0.5, 0.0), (10.0, 0.8, 3.0e-6, 0.6, 0.0), ], ),)State basis="liquid_mole_fraction" instead of "molality" when the model
should be initialized by liquid-phase salt mole fraction; the basis selects the
card the storage package renders.
surface_reference_salinity is explicit. If it is omitted, PetroScript does not
invent a surface-salinity value; the compiler leaves that item absent.
A uniform salt state is read by CO2 and H2 storage alike: the H2-brine PVT
properties depend on the salinity too (manual 5.3.61), and three salinities give
three different answers in an H2 run as they do in a CO2 one. The salt state is
a ps.brine.UniformSalinity with its basis stated; bare floats are rejected
because they would otherwise be ambiguous and unlowered.
Salinity by depth, rows by name
Section titled “Salinity by depth, rows by name”ps.brine.SalinityVsDepth(rows=...) states the initial salinity profile as
(depth, salinity) pairs in that order, or as records naming depth and
salinity; a record that lacks a column is refused, naming the column.
brine = ps.brine.Package( initial_salinity=ps.brine.SalinityVsDepth( rows=[{"depth": 2500.0, "salinity": 30000.0}, {"depth": 2600.0, "salinity": 35000.0}] ),)Storage streams
Section titled “Storage streams”ps.pvt.Composition(name=..., mole_fraction={component: fraction, ...}) is
one WELLSTRE row: the mapping’s keys are the components, so a species is
named once by construction; the package reads them as its component words, in
lower case or in capitals (“co2” or “CO2”). The package declares it in its compositions=, and an injector
injects it by its name, ps.wells.InjectedStream(well=..., composition=...). A
component the composition leaves unstated is 0, and WELLSTRE writes every
component of the package; fractions that do not sum to one within 1e-6 are
refused as the composition is built. The initial composition against depth is
ps.initial.CompositionVsDepth(depth=[...], mole_fraction={component: [...]}),
each depth summing to one.
stream = ps.pvt.Composition(name="C02STREAM", mole_fraction={"co2": 1.0, "h2o": 0.0})storage = ps.storage.Package(kind="co2", components=["co2", "h2o"], compositions=[stream])Dissolved gas beside a storage package
Section titled “Dissolved gas beside a storage package”A storage run that declares OIL, GAS and DISGAS states no formulation
card and no oil PVT family: manuals 5.3.18 and 5.3.61 say the normal PVT
keywords are not required once the model is activated. There is then nothing
for the solution-gas coupling to be read off — no table type, no BLACKOIL
declaration — so the storage package carries the declaration itself, beside
gas_dissolves_in_water, which is the same shape of statement:
storage = ps.storage.Package( kind="h2", components=["h2"], simulator_supplied_phases=["gas", "oil"], dissolved_gas=True,)The field is only a package declaration where the package supplies both the
gas and the oil phase, which is the manuals’ option (2). A run that states its
own oil PVT family declares the coupling through that family as usual and
leaves this False; asking for both is refused rather than silently
preferred. Importing a deck of this shape restates DISGAS and adds no
BLACKOIL card the deck never stated.
Convective dissolution, one constant per PVT region
Section titled “Convective dissolution, one constant per PVT region”OPM Flow reads one convective-dissolution record (DRSDTCON) per PVT region, in
region order. ps.storage.ConvectiveDissolution(coefficient=) states it in a
ps.Event of a model with a storage package: one number for every region, or
{region: number} naming each of the model’s PVT regions; in the start’s
Event it is written at the schedule’s head, in a later one at its date. A map
naming other regions is refused (storage.drsdtcon.pvt_regions), and so is the
statement in a model with no storage package (storage.controls.package_missing),
as ps.load refuses DRSDTCON beside neither CO2STORE nor H2STORE:
dissolution = ps.storage.ConvectiveDissolution(coefficient={1: 0.04, 2: -1.0})# model.at(model.start, dissolution)A later region may ask for something the manual’s “a real positive value”
does not cover. Zero, in the manual’s own words, means dissolution does not
occur there. A negative constant means the region wants no limit at all —
the instantaneous dissolution described for a run that states the keyword
nowhere. That reading was measured off the reference run committed beside
co2store/CO2STORE_DRSDTCON.DATA, whose region 1 (constant 0.04) climbs to
saturation over 27 report steps while its region 2 (constant -1) is saturated
at the first.
Importing a storage deck of this shape also restates its declared PVT region count. A storage run states no PVT family, so nothing counts the regions but the region map itself, and both the TABDIMS cross-check and the model’s PVT-region gate read the map rather than families no table produced. That relaxation is scoped to the PVT map: a saturation region map is still held to the saturation families the deck states.
Salt precipitation capillary-pressure factor
Section titled “Salt precipitation capillary-pressure factor”Salt precipitation supports a capillary-pressure multiplier as a function of the porosity factor:
brine = ps.brine.Package( solubility=ps.brine.SaltSolubility(rows=[(0.5, 135.0)]), initial_precipitated_salt=0.0, gas_water_vaporization=ps.brine.VaporizationBySalt( rows=[(3000.0, 0.0, 0.01), (3000.0, 1.0, 0.005)] ), capillary_pressure_factor=ps.brine.CapillaryPressureFactor( rows=[(0.0, 2.0), (1.0, 1.0)] ),)Salt precipitation is the brine package’s: its solubility limit, its initial precipitated salt, its salt-dependent gas-water vaporization and its capillary-pressure factor. Each table validates finite, positive, strictly ordered semantic values. The public API is the physical table, not a raw keyword record. PCFACT requires at least two rows plus explicit salt gas-water vaporization and vaporized-water gas-water formulation; PetroScript does not auto-enable those prerequisites from the capillary-pressure factor alone.
Explicit non-public closures
Section titled “Explicit non-public closures”These stay outside the public API on purpose:
- Biofilm activation requires a broader biology/BIOFPARA semantic surface; the data-less storage flag is not exposed.
- Ezrokhi aqueous density/viscosity tables require CNAMES/COMPS-family parser mechanics, which remain outside PetroScript’s public semantic API.
- Polymer salt/temperature deepenings remain non-public because current Flow docs and evidence treat those columns as ignored for the implemented polymer path.
- Thermal CO2/brine mixing belongs to the thermal domain, not this storage/brine salt surface.
PetroScript is licensed under the Apache License 2.0. © 2026 Blauweiss EDV LLC. PetroScript by Blauweiss AI.For AI agents: llms.txt