Exerciseschevron_rightChapter 3chevron_right3.8
fitness_center

Exercise 3.8

Multi-Well Production Comparison

Level 2
Chapter 3: Data Structures
descriptionProblem

The starter builds a production dictionary: four wells, each with twelve months of oil_bopd, water_bwpd, and gas_mscfd (gas in thousand scf/day) stored as parallel lists.

Write three functions, then build a comparison figure.

  1. water_cut(oil_bopd, water_bwpd): fraction of liquid that is water:

water / (oil + water). Return 0.0 if both are zero (a shut-in well has no water cut, not a divide-by-zero).

  1. gor(oil_bopd, gas_mscfd): gas–oil ratio in scf/bbl:

gas_mscfd × 1000 / oil_bopd. Return 0.0 if oil_bopd is zero.

  1. first_month_wc_above(well_data, threshold=0.5): the **1-based month

number** in which this well's water cut first reaches threshold, or None if it never does. well_data is one well's dict (with oil_bopd and water_bwpd lists). Crossing 50 % water cut is a classic "the well is watering out" milestone.

Then build a 2×2 matplotlib figure (plt.subplots(2, 2, ...)) with one line per well in each panel: oil rate, water rate, GOR, and water cut over the twelve months. Label the axes and add a legend.

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 matplotlib.pyplot as plt

months = list(range(1, 13))

_specs = [
    ("OD-001", 1800, 0.020, 120, 20,  900, 0.010),
    ("OD-002",  900, 0.040, 300, 90,  600, 0.030),
    ("OD-005", 2400, 0.015,  80, 15, 1500, 0.010),
    ("OD-007", 1300, 0.030, 200, 40,  750, 0.020),
]
production = {}
for _name, _oil0, _doil, _w0, _dw, _gas0, _dgas in _specs:
    production[_name] = {
        "oil_bopd":   [round(_oil0 * (1 - _doil) ** i) for i in range(12)],
        "water_bwpd": [_w0 + _dw * i for i in range(12)],
        "gas_mscfd":  [round(_gas0 * (1 - _dgas) ** i) for i in range(12)],
    }


def water_cut(oil_bopd, water_bwpd):
    total = oil_bopd + water_bwpd
    return water_bwpd / total if total > 0 else 0.0


def gor(oil_bopd, gas_mscfd):
    return gas_mscfd * 1000 / oil_bopd if oil_bopd > 0 else 0.0


def first_month_wc_above(well_data, threshold=0.5):
    for i, (o, w) in enumerate(zip(well_data["oil_bopd"], well_data["water_bwpd"])):
        if water_cut(o, w) >= threshold:
            return i + 1
    return None


fig, axes = plt.subplots(2, 2, figsize=(12, 8))
for name, d in production.items():
    wc = [water_cut(o, w) for o, w in zip(d["oil_bopd"], d["water_bwpd"])]
    g = [gor(o, gm) for o, gm in zip(d["oil_bopd"], d["gas_mscfd"])]
    axes[0, 0].plot(months, d["oil_bopd"], marker="o", label=name)
    axes[0, 1].plot(months, d["water_bwpd"], marker="o", label=name)
    axes[1, 0].plot(months, g, marker="o", label=name)
    axes[1, 1].plot(months, wc, marker="o", label=name)

axes[0, 0].set_title("Oil rate"); axes[0, 0].set_ylabel("bopd")
axes[0, 1].set_title("Water rate"); axes[0, 1].set_ylabel("bwpd")
axes[1, 0].set_title("GOR"); axes[1, 0].set_ylabel("scf/bbl")
axes[1, 1].set_title("Water cut"); axes[1, 1].set_ylabel("fraction")
for ax in axes.flat:
    ax.set_xlabel("Month")
    ax.grid(True, alpha=0.3)
axes[0, 0].legend()
fig.suptitle("OML 58 - four-well production comparison", fontweight="bold")
plt.tight_layout()
plt.show()

lockCopying code is a Full Access feature.