Exercise 4.9
Pressure Survey Analysis
A pressure survey records static bottomhole pressure (SBHP) at a handful of dates over a well's life. The slope of pressure against time tells the reservoir engineer how fast the reservoir is depleting, and, extrapolated, when a well will reach abandonment pressure.
The starter builds survey (columns: well, date, sbhp_psi) for three wells, with date already parsed to datetime. Use DAYS_PER_MONTH = 30.4375 to turn date gaps into months. Write three functions:
decline_rate_psi_per_month(df, well): fit a straight line to that
well's SBHP vs. months since its first survey (np.polyfit(months, sbhp, 1)) and return the slope (negative = depleting).
fastest_depleting(df): the well with the steepest (most negative)
decline rate.
months_to_abandonment(df, well, p_abandon=2000): from the latest
survey, how many months until the line reaches p_abandon: (p_abandon − latest_sbhp) / rate.
Then plot SBHP vs. months for all three wells, each with its fitted trend line. A few pressure points and a ruler is the cheapest depletion diagnostic in the business.
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 pandas as pd
import matplotlib.pyplot as plt
survey = pd.DataFrame([
{"well": "OD-001", "date": "2023-01-15", "sbhp_psi": 4850},
{"well": "OD-001", "date": "2023-07-20", "sbhp_psi": 4620},
{"well": "OD-001", "date": "2024-01-10", "sbhp_psi": 4390},
{"well": "OD-001", "date": "2024-07-15", "sbhp_psi": 4185},
{"well": "OD-003", "date": "2023-06-01", "sbhp_psi": 5100},
{"well": "OD-003", "date": "2024-01-15", "sbhp_psi": 4750},
{"well": "OD-003", "date": "2024-07-01", "sbhp_psi": 4520},
{"well": "OD-005", "date": "2023-03-01", "sbhp_psi": 4200},
{"well": "OD-005", "date": "2024-03-01", "sbhp_psi": 4050},
{"well": "OD-005", "date": "2024-09-01", "sbhp_psi": 3920},
])
survey["date"] = pd.to_datetime(survey["date"])
DAYS_PER_MONTH = 30.4375
P_ABANDON = 2000
def _months_since_first(dates):
return (dates - dates.iloc[0]).dt.days / DAYS_PER_MONTH
def decline_rate_psi_per_month(df, well):
d = df[df["well"] == well].sort_values("date")
months = _months_since_first(d["date"])
slope, _intercept = np.polyfit(months, d["sbhp_psi"], 1)
return slope
def fastest_depleting(df):
rates = {well: decline_rate_psi_per_month(df, well) for well in df["well"].unique()}
return min(rates, key=rates.get)
def months_to_abandonment(df, well, p_abandon=P_ABANDON):
d = df[df["well"] == well].sort_values("date")
latest_sbhp = d["sbhp_psi"].iloc[-1]
return (p_abandon - latest_sbhp) / decline_rate_psi_per_month(df, well)
fig, ax = plt.subplots(figsize=(10, 6))
for well in survey["well"].unique():
d = survey[survey["well"] == well].sort_values("date")
months = _months_since_first(d["date"])
slope, intercept = np.polyfit(months, d["sbhp_psi"], 1)
ax.scatter(months, d["sbhp_psi"], label=well)
ax.plot(months, slope * months + intercept)
print(f"{well}: {slope:6.1f} psi/month, "
f"{months_to_abandonment(survey, well):5.0f} months to abandonment")
ax.set_xlabel("Months since first survey")
ax.set_ylabel("SBHP (psi)")
ax.set_title("Pressure survey decline - OML 58")
ax.legend()
plt.tight_layout()
plt.show()
lockCopying code is a Full Access feature.