← All examples
54

Canonical Grid model

Petroleum starter - Nodal analysis

Vogel inflow meets a quadratic outflow surrogate. Pressures are absolute psi, rate is STB/day; this is not a validated multiphase tubing model.

Scale
Small
Source
petroleum-nodal-analysis.grid
Length
78 lines
Collection
Advanced language
Level
Intermediate
Runtime
Portable
Version
1.0.0
petroleum-nodal-analysis.grid
Get Grid
MODEL "Petroleum starter - Nodal analysis"
DESCRIPTION "Vogel inflow meets a quadratic outflow surrogate. Pressures are absolute psi, rate is STB/day; this is not a validated multiphase tubing model."
VERSION "1.0.0"
TAGS "petroleum", "starter", "nodal", "scenarios"

strict

# Synthetic saturated solution-gas-drive inflow and illustrative tubing inputs.
input ReservoirPressure_psia = 3000
  VALIDATE BETWEEN 100 AND 20000 MESSAGE "Reservoir pressure must be 100 to 20,000 absolute psi in this example."
input AOF_stbd = 1351.3513513513512
  VALIDATE BETWEEN 1 AND 100000 MESSAGE "AOF must be positive (1 to 100,000 STB/day)."
input StaticLiftPressure_psia = 500
  VALIDATE BETWEEN 0 AND 20000 MESSAGE "Static lift pressure must be 0 to 20,000 absolute psi."
input TubingCoefficient = 0.001
  VALIDATE BETWEEN 0 AND 1 MESSAGE "The quadratic coefficient must be nonnegative, in psi/(STB/day)^2."
input BracketLower_stbd = 0
  VALIDATE BETWEEN 0 AND 100000 MESSAGE "The lower bracket must be nonnegative."
input BracketUpper_stbd = 1300
  VALIDATE BETWEEN 0 AND 100000 MESSAGE "The upper bracket must be nonnegative."

# Enforce the inflow correlation's pressure domain inside the callable.
outflow_pressure = (rate) => StaticLiftPressure_psia + TubingCoefficient * rate^2
inflow_rate = (pressure) => pressure >= 0 AND pressure <= ReservoirPressure_psia THEN AOF_stbd * (1 - 0.2 * pressure / ReservoirPressure_psia - 0.8 * (pressure / ReservoirPressure_psia)^2) ELSE #N/A
balance = (rate) => inflow_rate(outflow_pressure(rate)) - rate
output OperatingRate_stbd IS number = ROOT(balance, BracketLower_stbd, BracketUpper_stbd, 1e-8, 200)
output OperatingPressure_psia IS number = outflow_pressure(OperatingRate_stbd)
output RateResidual_stbd IS number = balance(OperatingRate_stbd)
output LowerResidual_stbd = balance(BracketLower_stbd)
output UpperResidual_stbd = balance(BracketUpper_stbd)
output Assumptions = "Vogel saturated solution-gas-drive screening; quadratic outflow is an illustrative surrogate. Valid inflow pressure is 0 to reservoir pressure. Bracket endpoints outside that domain fail explicitly."
output ScenarioGuide = "Save Baseline. Change TubingCoefficient to 0.0005 and save Lower tubing loss. Restore Baseline or reset inputs; verify the residual and bracket."

# Plot only rates for which the pressure curves are physically in range.
ChartMaxRate_stbd = TubingCoefficient > 0 THEN MIN(AOF_stbd, SQRT(MAX(0, ReservoirPressure_psia - StaticLiftPressure_psia) / TubingCoefficient)) ELSE AOF_stbd
Rates_stbd = MAP(SEQUENCE(14, 1, 0, 1), (index) => ChartMaxRate_stbd * index / 13)
OutflowPressures_psia = MAP(Rates_stbd, (rate) => outflow_pressure(rate))
InflowPressures_psia = MAP(Rates_stbd, (rate) => ReservoirPressure_psia * (SQRT(0.04 + 3.2 * (1 - rate / AOF_stbd)) - 0.2) / 1.6)
Curve = MAP(Rates_stbd, (rate) => {
  rate: rate,
  outflow: outflow_pressure(rate),
  inflow: ReservoirPressure_psia * (SQRT(0.04 + 3.2 * (1 - rate / AOF_stbd)) - 0.2) / 1.6
})
A1 = "Operating oil rate (STB/day)"
B1 = OperatingRate_stbd
A2 = "Operating bottomhole pressure (absolute psi)"
B2 = OperatingPressure_psia
A3 = "Equation residual (STB/day)"
B3 = RateResidual_stbd
A4 = Assumptions

Charts_1!type = "charts"
Charts_1!config = <toml>
  version = 1
  kind = "charts"
  title = "Nodal analysis"

  [layout]
  title = "Synthetic well operating point"
  x_axis = "Oil rate (STB/day)"
  y_axis = "Bottomhole pressure (absolute psi)"

  [[charts]]
  type = "line"
  bind = "Curve"
  x = "rate"
  y = "outflow"
  label = "Quadratic outflow surrogate"

  [[charts]]
  type = "line"
  bind = "Curve"
  x = "rate"
  y = "inflow"
  label = "Vogel inflow"
</toml>

END MODEL