Exerciseschevron_rightChapter 15chevron_right15.8
fitness_center

Exercise 15.8

Gas Gathering System - Pipeline Sizing & Station Compression

Level 3
Chapter 15: Gas Engineering
descriptionProblem

Design a gas gathering system for four wells producing 10, 15, 8, and 22 MMscf/d at wellhead pressures of 400, 350, 300, and 450 psia respectively. All wells feed into a central compressor station via pipelines of varying lengths (2, 5, 3, and 8 miles). Size each pipeline and calculate the compressor power needed to boost the gas to 1,000 psia for transmission.

---

You'll design the full gathering system for an OML cluster: pick the smallest commercial pipe that meets each well's duty, then size the central compressor that boosts the commingled stream into the transmission line. Two verified chapter functions are embedded for you; do not modify or re-derive them:

  • weymouth(P1, P2, D_in, L_miles, gamma_g, T_avg_F, Z_avg, E=0.92) returns a

line's capacity in MMscf/day.

  • compression_power(q_mmscfd, P_suction, P_discharge, T_suction_F, Z_avg, gamma_g, ...)

returns a dict with "Total HP", "Stages", "Overall ratio", etc.

Write gathering_design(wells, header_psia, transmission_psia, T_F, Z_avg, gamma_g) where wells is a list of dicts, each {'q': MMscf/d, 'whp': wellhead psia, 'L': miles}.

For each well, in list order:

  1. Size the flowline. Search the commercial candidates

DIAMETERS = [4, 6, 8, 10, 12, 16] (inches) and pick the smallest D such that weymouth(whp, header_psia, D, L, gamma_g, T_F, Z_avg) >= q. That is, the cheapest pipe whose Weymouth capacity meets the well's rate when it flows from its wellhead pressure down to the common gathering header_psia.

Then:

  1. Sum every well's q into q_total (MMscf/d).
  2. Size the station: call

compression_power(q_total, header_psia, transmission_psia, T_F, Z_avg, gamma_g) this boosts the gathered gas from the low header pressure up to the transmission pressure.

Return a dict with exactly these keys:

  • 'diameters': the list of chosen D per well, same order as wells.
  • 'q_total': total gathered rate (MMscf/d).
  • 'station': the full compression_power result dict.
  • 'total_hp': station['Total HP'].

Embedded constants from the book exercise:

WELLS = [{'q': 10.0, 'whp': 400.0, 'L': 2.0},
         {'q': 15.0, 'whp': 350.0, 'L': 5.0},
         {'q':  8.0, 'whp': 300.0, 'L': 3.0},
         {'q': 22.0, 'whp': 450.0, 'L': 8.0}]
HEADER_PSIA       = 250.0
TRANSMISSION_PSIA = 1000.0
T_F               = 80.0
Z_AVG             = 0.95
GAMMA_G           = 0.65

Finally, call it and expose the variables the tests read:

gd        = gathering_design(WELLS, HEADER_PSIA, TRANSMISSION_PSIA, T_F, Z_AVG, GAMMA_G)
q_total   = gd['q_total']
total_hp  = gd['total_hp']
diameters = gd['diameters']

> Think about it: the longer or higher-rate a well's duty, the bigger the > pipe it needs, so the 22 MMscf/d well on 8 miles should never call for a > smaller line than the 8 MMscf/d well on 3 miles. And because the station > only sees the commingled q_total, its horsepower scales linearly with > total throughput: double the gathered gas, double the HP. Pick the minimum > adequate diameter; oversizing every line passes the capacity test but wastes > millions in steel.

lightbulbHints (0/3)

Stuck? Reveal hints one at a time — they progress from nudge to near-solution.

codeYour solution
main.py
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


# ── Verified gas-pipeline / compression functions (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


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 gathering-cluster constants (do not edit) ────────────────────────
DIAMETERS = [4, 6, 8, 10, 12, 16]   # commercial pipe candidates, inches

WELLS = [{'q': 10.0, 'whp': 400.0, 'L': 2.0},
         {'q': 15.0, 'whp': 350.0, 'L': 5.0},
         {'q':  8.0, 'whp': 300.0, 'L': 3.0},
         {'q': 22.0, 'whp': 450.0, 'L': 8.0}]
HEADER_PSIA       = 250.0    # common gathering-header pressure, psia
TRANSMISSION_PSIA = 1000.0   # transmission delivery pressure, psia
T_F               = 80.0     # flowing temperature, deg F
Z_AVG             = 0.95     # average gas Z-factor
GAMMA_G           = 0.65     # gas specific gravity


def gathering_design(wells, header_psia, transmission_psia, T_F, Z_avg, gamma_g):
    """Design a gas gathering system: minimum pipe per well + station HP.

    For each well, choose the smallest DIAMETERS entry whose Weymouth capacity
    (from wellhead pressure down to header_psia) meets the well's rate. Then
    commingle all rates and size the station compressor that boosts q_total from
    header_psia up to transmission_psia.

    Returns a dict with keys:
      'diameters' : chosen D per well (same order as wells)
      'q_total'   : total gathered rate, MMscf/d
      'station'   : compression_power result dict
      'total_hp'  : station['Total HP']
    """
    diameters = []
    q_total = 0.0
    for w in wells:
        q, whp, L = w['q'], w['whp'], w['L']
        chosen = None
        for D in DIAMETERS:
            cap = weymouth(whp, header_psia, D, L, gamma_g, T_F, Z_avg)
            if cap >= q:
                chosen = D
                break
        if chosen is None:           # even the largest pipe is undersized
            chosen = DIAMETERS[-1]
        diameters.append(chosen)
        q_total += q

    station = compression_power(q_total, header_psia, transmission_psia,
                                T_F, Z_avg, gamma_g)

    return {
        'diameters': diameters,
        'q_total': q_total,
        'station': station,
        'total_hp': station['Total HP'],
    }


gd = gathering_design(WELLS, HEADER_PSIA, TRANSMISSION_PSIA, T_F, Z_AVG, GAMMA_G)
q_total = gd['q_total']
total_hp = gd['total_hp']
diameters = gd['diameters']

print("q_total (MMscf/d):", q_total)
print("diameters (in):", diameters)
print("total station HP:", total_hp)

lockCopying code is a Full Access feature.