Skip to content

Relative permeability and SCAL

Relative permeability tables describe how phase mobility changes as saturation changes. A phase may be present in a cell but still flow poorly if its relative permeability is low. Capillary pressure columns describe pressure offsets between phase pairs where the model uses them.

Each saturation curve is a ps.relperm table held by column (one saturation region), its columns the literature’s symbols in the literature’s order, each pair of phases named lighter phase first. A cell the deck defaults is None (the simulator derives it, written 1*); the saturation column states every row. from_frame reads a curve’s columns by name from a polars or pandas frame, or a mapping of column name to values.

import petroscript as ps
water_oil = 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 = ps.relperm.GasOil.from_frame(
{"sg": [0.0, 0.8], "krg": [0.0, 1.0], "krog": [1.0, 0.0], "pcog": [0.0, 0.0]}
)

ps.relperm.OilWater has the columns sw, krw, krow and pcow. It describes water-oil flow behavior as water saturation varies.

ps.relperm.GasOil has the columns sg (or sl), krg, krog and pcog. It describes gas-oil flow behavior as gas saturation varies; stated against the liquid saturation sl instead, it is the SLGOF-style gas/oil table where liquid saturation, rather than gas saturation, indexes the gas/oil mobility relationship.

ps.relperm.Water, ps.relperm.Gas and ps.relperm.Oil model split phase-function tables. They are useful when a deck expresses water, gas, and oil mobility as separate functions rather than paired water/oil and gas/oil tables. PetroScript keeps these as semantic phase-function tables; users do not author target keyword records directly.

Water has columns sw, krw and pc. Gas has columns sg, krg and pc. Oil has columns so, krow and krog. In a gas-water system a Water curve without pc is the storage system’s water curve, beside its Gas curve; elsewhere it states no capillary pressure. ps.relperm.GasWater has the columns sg, krg, krw and pc.

A model’s relperm is the list of its saturation functions and the statements about them, or one list per saturation region as {region: [...]}. Use paired tables such as ps.relperm.OilWater and ps.relperm.GasOil for classic SPE-style water/oil and gas/oil behavior. Use ps.relperm.Water, Gas, and Oil for split family II phase functions. Use either an sg or an sl gas/oil curve for one gas/oil relperm family, not both.

Statements that hold for the whole model sit in the same list (or region 1’s):

  • ps.relperm.JFunction (phase, surface tension, …) enables bounded J-function capillary pressure behavior.
  • ps.relperm.MaxCapillaryPressure (maximum pressure) applies a positive water capillary-pressure maximum, one per saturation region.
  • ps.relperm.CriticalTolerance (value) is the relative permeability at or below which a phase counts as immobile when critical saturations are found.
  • ps.relperm.Hysteresis(family="killough" | "carlson", ...) selects a typed hysteresis model, scope, wetting policy, and capillary-pressure scaling behavior.
  • ps.relperm.EndpointScaling(curve="imbibition", ...) describes imbibition endpoint maps with semantic field names such as connate_water, maximum_gas_relperm, oil_relperm_at_critical_water, and maximum_oil_water_capillary_pressure.
  • ps.relperm.Hysteresis(cycle_policy="alternating_slugs", ...) enables typed WAG hysteresis with only Flow-supported controls: Land’s parameter, secondary drainage reduction, imbibition linear fraction, and three-phase switch threshold.

The three-phase method, ps.relperm.ThreePhase with method="stone1" or "stone2", sits in every saturation region’s list with one method; a Stone-I exponent is stated in every region or in none.

These controls are semantic API objects; users do not author backend keyword records directly.

Hysteresis controls are statements in the relperm list, so the public API describes behavior rather than deck cards:

hysteresis = ps.relperm.Hysteresis(
family="killough",
scope="both",
wetting="imbibition_table",
capillary_pressure_scaling=True,
cycle_policy="alternating_slugs",
lands_parameter=3.0,
secondary_drainage_reduction=0.5,
imbibition_linear_fraction=0.2,
three_phase_switch_threshold=0.01,
)
imbibition = ps.relperm.EndpointScaling(
curve="imbibition",
connate_water=0.16,
maximum_water=0.82,
maximum_gas=0.77,
maximum_water_relperm=0.66,
maximum_gas_relperm=0.71,
oil_relperm_at_critical_water=0.62,
maximum_oil_water_capillary_pressure=20.0,
)
drainage = ps.relperm.EndpointScaling(
method="three_point",
connate_water=0.18,
maximum_water=0.85,
maximum_gas=0.80,
)
relperm = [
ps.relperm.Water(sw=[0.16, 0.82], krw=[0.0, 0.66], pc=[0.0, 0.0]),
ps.relperm.Gas(sg=[0.0, 0.77], krg=[0.0, 0.71], pc=[0.0, 0.0]),
ps.relperm.Oil(so=[0.0, 0.84], krow=[0.0, 1.0], krog=[0.0, 1.0]),
drainage,
imbibition,
hysteresis,
]

A word-valued choice takes one of its listed words in lower case or in capitals; a word naming none, a mixed case included, is refused listing the choices. This keeps tests and agent-authored PetroScript focused on reservoir semantics such as typed hysteresis model selection, imbibition endpoint maps, or alternating WAG slugs, while the compiler owns the exact backend records.

Paired relperm tables have four columns, split phase-function tables three, each column one value per row. Saturations and relative permeabilities are unitless. Capillary pressure is in the model pressure unit system; under FIELD, that is psi. Each column is a list, a tuple or a 1-D array of finite numbers (None for a defaulted cell); numeric strings, object arrays and booleans are rejected instead of coerced.

Saturation columns should be monotonic, bounded to physically meaningful values, and should not skip endpoints that the simulator needs for interpolation. Relative permeabilities should be non-negative. Common mistakes are reversing water/gas saturation columns, using percentages instead of fractions, and assuming a capillary-pressure value is unitless.

Current status: emitted now for the implemented single-region SCAL-style path.

  • SWOF is emitted from OilWater.
  • SGOF is emitted from GasOil, and SLGOF from a GasOil stated against sl.
  • SWFN, SGFN, and SOF3 are emitted from split phase-function tables (a Water curve without pc writes SWFN with pc 0), and GSF/WSF from a gas-water storage system’s pair.

J-function capillary pressure, water capillary-pressure maxima, the Stone I and II three-phase methods, the critical-saturation tolerance, endpoint hysteresis, WAG hysteresis, solvent saturation/PVT properties, and directional relperm family maps are emitted now through the semantic controls above. Broader capillary-pressure systems and family-specific solvent saturation functions remain future/unsupported.

Imbibition regions, undirected and directional

Section titled “Imbibition regions, undirected and directional”

regions.imbibition assigns one imbibition region per cell, applying in every flow direction, and lowers to IMBNUM. A run whose imbibition regions differ by direction states regions.imbibition_x, imbibition_y and imbibition_z instead, three plain maps like the relative-permeability triple’s relperm_x, relperm_y and relperm_z, and they lower to IMBNUMX, IMBNUMY and IMBNUMZ. A deck may state both, and the corpus does.

The directional triple obeys the two rules its relperm sibling obeys, for the same reasons: all three directions or none, because a direction left out has no map to fall back on; and SaturationOptions(direct=True) (SATOPTS 'DIRECT', a model-wide item of the relperm list), because that is what turns directional regions on at all.

A saturation function can be stated as a fitted correlation instead of a table. ps.relperm.LETRelPerm states one, from two per-phase curves (water and oil for the water-oil pair, gas and oil for the gas-oil pair) and one capillary-pressure fit, each a mapping of its named parameters: it sits in the relperm list, one per saturation region, exactly as tabulated curves do.

water_oil_let = ps.relperm.LETRelPerm(
water={
"connate_saturation": 0.021,
"critical_saturation": 0.021,
"lower": 1.927,
"elevation": 5.667,
"top": 2.214,
"endpoint_relperm": 0.995,
},
oil={
"connate_saturation": 0.021,
"critical_saturation": 0.100,
"lower": 2.367,
"elevation": 3.100,
"top": 1.900,
"endpoint_relperm": 1.002,
},
capillary={
"lower": 1.0, "elevation": 1.0, "top": 1.0,
"initial_pressure": 0.0, "threshold_pressure": 0.0,
},
)

A LET model and a tabulated table for the same phase pair are mutually exclusive; stating both reports relperm.let.excludes_tabulated.

SWOFLET and SGOFLET are emitted from these correlations, and importing a deck that states them reads them back: seventeen parameters per saturation region, in the deck’s own order and with the deck’s own numbers. Every one must be stated. A tabulated family reads a defaulted cell off the simulator’s own derivation, but each LET parameter shapes the curve on its own and there is no neighbouring row to read a missing one from, so a defaulted slot is reported rather than filled in with a number the deck did not choose.

The endpoint relative permeability must be positive and is not capped at one. The textbook range would cap it, and the corpus contradicts that: model2’s LET include states Krto = 1.002 in eight of its ten water-oil tables and Krtg = 1.005 in all ten of the gas-oil set, and the target runs that deck.

Use ps.relperm.EndpointScaling(...) (its curve is "drainage" unless stated) to describe drainage endpoint-scaling intent. The API uses semantic field names such as connate_water, capillary_connate_water, critical_water, maximum_water_relperm, and oil_relperm_at_critical_gas. Backend endpoint arrays and scaling-option cards are compiler-owned; users should not author raw endpoint keyword calls.

Example:

endpoints = ps.relperm.EndpointScaling(
method="three_point",
connate_water=0.18,
capillary_connate_water=0.19,
connate_gas=0.02,
capillary_connate_gas=0.03,
critical_water=0.24,
critical_gas=0.05,
maximum_water=0.85,
maximum_gas=0.80,
maximum_water_relperm=0.70,
maximum_gas_relperm=0.75,
gas_relperm_at_residual_oil=0.55,
water_relperm_at_critical_oil=0.50,
oil_relperm_at_critical_gas=0.62,
oil_relperm_at_critical_water=0.64,
)
relperm = [water_oil, gas_oil, endpoints]

Every relative-permeability anchor may be stated under either method. SCALECRS item 1 chooses how the saturations scale (two-point by default); the anchors KRGR, KRWR, KRORG and KRORW switch Flow’s three-point scaling of the relative-permeability value on by being stated (opm-common EclEpsConfig), and a two-point run with gas_relperm_at_residual_oil is another run than one without it. The imbibition anchors (IKRGR and the like) follow the same rule.

SCALECRS is not a user-facing authoring surface; it is emitted internally only when typed scaling policy requires it. Imbibition endpoint maps are authored through ps.relperm.EndpointScaling(curve="imbibition", ...) when hysteresis is active.

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