Exercise 15.10
Integrated Gas Field Development - Wells, Pipeline, Compression & Reserves
A gas field has estimated reserves of 500 Bscf. Design a development plan that includes: well deliverability (number of wells needed for a plateau rate of 200 MMscf/d), gathering system sizing, compression requirements, and a 200-km transmission pipeline to the delivery point. Present a cost estimate for the surface facilities.
---
This is the capstone for the gas-engineering chapter: chain four sizing calculations into a single development plan for a 500-Bscf gas field on OML 58. Every number you produce flows from a verified chapter tool; do not invent or "simplify" the correlations. The verified weymouth (pipeline capacity) and compression_power (gas horsepower) functions and the field constants are embedded for you.
Write one function:
def field_development(reserves_bscf, plateau_mmscfd, per_well_mmscfd,
recovery_factor, L_km, P_in, P_out, P_suction,
T_F, Z_avg, gamma_g) -> dict:Build the plan in four steps:
- Well count.
n_wells = ceil(plateau_mmscfd / per_well_mmscfd); round
up so the wells can hold the plateau (an integer).
- Plateau life.
recoverable_mmscf = recovery_factor reserves_bscf 1000;
plateau_days = recoverable_mmscf / plateau_mmscfd; plateau_years = plateau_days / 365 (a float).
- Transmission pipeline. Convert the length with
L_miles = L_km * 0.621371.
Search the standard line sizes DIAMETERS = [24, 30, 36, 42, 48] inches and pick the smallest D whose Weymouth capacity at the pipeline conditions meets the plateau, i.e. the smallest D with weymouth(P_in, P_out, D, L_miles, gamma_g, T_F, Z_avg) >= plateau_mmscfd. Call that D_transmission (inches).
- Compression station. The field gas leaves the gathering header at
P_suction and must be boosted to the pipeline inlet P_in. Size it with station = compression_power(plateau_mmscfd, P_suction, P_in, T_F, Z_avg, gamma_g) (note the argument order: rate, suction, discharge, suction temp, Z, gravity).
Return a dict with exactly these keys:
{
"n_wells": n_wells, # int
"plateau_years": plateau_years, # float
"D_transmission": D_transmission, # inches, one of DIAMETERS
"station": station, # the compression_power dict
"total_hp": station["Total HP"],
}The OML-58 development constants are embedded: RESERVES_BSCF = 500.0, PLATEAU_MMSCFD = 200.0, PER_WELL_MMSCFD = 25.0, RECOVERY_FACTOR = 0.80, L_KM = 200.0, P_OUT = 800.0 (delivery psia), P_IN = 1200.0 (pipeline inlet psia), P_SUCTION = 600.0 (gathering header psia, boosted up to P_IN), T_F = 80.0, Z_AVG = 0.92, GAMMA_G = 0.65.
Finally, run the plan once on the OML-58 constants and unpack the answers the tests read:
fd = field_development(RESERVES_BSCF, PLATEAU_MMSCFD, PER_WELL_MMSCFD,
RECOVERY_FACTOR, L_KM, P_IN, P_OUT, P_SUCTION,
T_F, Z_AVG, GAMMA_G)
n_wells = fd["n_wells"]
plateau_years = fd["plateau_years"]
d_transmission = fd["D_transmission"]
total_hp = fd["total_hp"]> Think about it: the plateau life is just volume conservation; > plateau_years 365 plateau_mmscfd must equal the recoverable gas > recovery_factor reserves_bscf 1000, no matter how you slice it. Pipeline > capacity grows like D^(8/3) in Weymouth, so a small bump in plateau rate can > force the next pipe size up. Why does picking the smallest adequate diameter > matter for the capital cost, and what happens to the gradient if you under-size?
Stuck? Reveal hints one at a time — they progress from nudge to near-solution.
visibilityReveal reference solutionexpand_more
Try solving it yourself first — the hints walk you through it. The solution below is one valid approach; yours may differ and still be correct.
import numpy as np
import math
# ── Verified gas pipeline capacity - Weymouth (do not edit) ──────────────
def weymouth(P1, P2, D_in, L_miles, gamma_g, T_avg_F, Z_avg, E=0.92):
"""Weymouth equation for gas pipeline capacity (MMscf/day)."""
Tb = 520
Pb = 14.73
T_R = T_avg_F + 459.67
q = 433.5 * (Tb/Pb) * E * (
(P1**2 - P2**2) * D_in**(16/3) /
(gamma_g * T_R * Z_avg * L_miles)
)**0.5
return q / 1e6
# ── Verified gas compression horsepower (do not edit) ────────────────────
def compression_power(q_mmscfd, P_suction, P_discharge, T_suction_F,
Z_avg, gamma_g, k=1.28, eta_adiabatic=0.82):
"""
Calculate gas compression horsepower.
Parameters
----------
q_mmscfd : float
Gas flow rate (MMscf/day).
P_suction : float
Suction pressure (psia).
P_discharge : float
Discharge pressure (psia).
T_suction_F : float
Suction temperature (deg F).
Z_avg : float
Average Z-factor across compressor.
gamma_g : float
Gas specific gravity.
k : float
Ratio of specific heats (Cp/Cv). ~1.28 for natural gas.
eta_adiabatic : float
Adiabatic efficiency (typically 0.75-0.85).
Returns
-------
dict
Compression results.
"""
T_s = T_suction_F + 459.67 # Rankine
r = P_discharge / P_suction # compression ratio
# Check if staging is needed (r > 4 typically requires multiple stages)
max_ratio_per_stage = 4.0
n_stages = int(np.ceil(np.log(r) / np.log(max_ratio_per_stage)))
r_per_stage = r**(1/n_stages)
# Adiabatic head per stage
# HP = (q * Ts * Zs / eta) * (k/(k-1)) * [(r^((k-1)/k)) - 1]
exponent = (k - 1) / k
# Total power (HP) using gas horsepower equation
hp_per_mmscfd = (
# GPSA adiabatic gas-HP constant: 0.0857 = 3.0303 * (P_base/T_base)
# = 3.0303 * 14.7/520. Omitting the base-condition ratio inflates HP ~35x.
(0.0857 / eta_adiabatic) *
(k / (k - 1)) *
T_s * Z_avg *
(r_per_stage**exponent - 1) *
n_stages
)
total_hp = hp_per_mmscfd * q_mmscfd
# Discharge temperature per stage
T_discharge_R = T_s * r_per_stage**exponent
T_discharge_F = T_discharge_R - 459.67
return {
"Overall ratio": round(r, 2),
"Stages": n_stages,
"Ratio/stage": round(r_per_stage, 2),
"Total HP": round(total_hp, 0),
"HP/MMscf/d": round(hp_per_mmscfd, 0),
"T_discharge (deg F)": round(T_discharge_F, 1),
}
# ── OML-58 gas field development constants (do not edit) ──────────────────
RESERVES_BSCF = 500.0 # estimated reserves, Bscf
PLATEAU_MMSCFD = 200.0 # target plateau rate, MMscf/d
PER_WELL_MMSCFD = 25.0 # deliverability per well, MMscf/d
RECOVERY_FACTOR = 0.80 # fraction of GIIP recovered
L_KM = 200.0 # transmission pipeline length, km
P_OUT = 800.0 # delivery (outlet) pressure, psia
P_IN = 1200.0 # pipeline inlet pressure, psia
P_SUCTION = 600.0 # gathering header (compressor suction) pressure, psia
T_F = 80.0 # average gas temperature, deg F
Z_AVG = 0.92 # average compressibility factor
GAMMA_G = 0.65 # gas specific gravity
DIAMETERS = [24, 30, 36, 42, 48] # standard transmission line sizes, inches
def field_development(reserves_bscf, plateau_mmscfd, per_well_mmscfd,
recovery_factor, L_km, P_in, P_out, P_suction,
T_F, Z_avg, gamma_g):
"""Integrated gas field development plan (capstone).
Chains four sizing steps and returns a dict:
n_wells = ceil(plateau / per_well) (int)
plateau_years = recoverable_mmscf / plateau / 365 (float)
D_transmission = smallest Weymouth-adequate diameter (in)
station = compression_power(...) dict
total_hp = station["Total HP"]
"""
# 1) wells needed to hold the plateau (round UP)
n_wells = math.ceil(plateau_mmscfd / per_well_mmscfd)
# 2) plateau life from recoverable volume
recoverable_mmscf = recovery_factor * reserves_bscf * 1000
plateau_days = recoverable_mmscf / plateau_mmscfd
plateau_years = plateau_days / 365
# 3) smallest transmission diameter that meets the plateau
L_miles = L_km * 0.621371
D_transmission = None
for D in DIAMETERS:
if weymouth(P_in, P_out, D, L_miles, gamma_g, T_F, Z_avg) >= plateau_mmscfd:
D_transmission = D
break
# 4) compression from gathering suction up to the pipeline inlet
station = compression_power(plateau_mmscfd, P_suction, P_in, T_F, Z_avg, gamma_g)
return dict(
n_wells=n_wells,
plateau_years=plateau_years,
D_transmission=D_transmission,
station=station,
total_hp=station["Total HP"],
)
fd = field_development(RESERVES_BSCF, PLATEAU_MMSCFD, PER_WELL_MMSCFD,
RECOVERY_FACTOR, L_KM, P_IN, P_OUT, P_SUCTION,
T_F, Z_AVG, GAMMA_G)
n_wells = fd["n_wells"]
plateau_years = fd["plateau_years"]
d_transmission = fd["D_transmission"]
total_hp = fd["total_hp"]
print("n_wells:", n_wells)
print("plateau_years:", plateau_years)
print("D_transmission (in):", d_transmission)
print("total_hp:", total_hp)
print("station:", fd["station"])
lockCopying code is a Full Access feature.