Exercise 3.8
Multi-Well Production Comparison
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.
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).
gor(oil_bopd, gas_mscfd): gas–oil ratio in scf/bbl:
gas_mscfd × 1000 / oil_bopd. Return 0.0 if oil_bopd is zero.
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.
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 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.