Exercise 5.2
Waterflood Surveillance Dashboard
Build a 2×2 surveillance dashboard for a waterflood project. Synthesise the underlying data and plot all four panels in a single figure built with fig, axes = plt.subplots(2, 2, figsize=(14, 9)).
Use 36 monthly time-points for everything (t = np.arange(0, 36)). Build the data however you like (exponential decline, sigmoid water cut, etc.); the grader only checks plot structure, not the exact synthetic values.
Panels (build them in this order):
axes[0, 0]: oil + water rate, dual y-axis. Plot oil rate on the
primary y-axis, water rate on a secondary axis created with axes[0, 0].twinx(). The chart must have at least 2 lines.
axes[0, 1]: water cut over time with a horizontal reference
line at the 0.90 economic limit (axhline).
axes[1, 0]: cumulative oil vs cumulative water injected. Use
np.cumsum to build the two cumulative series; plot the second against the first.
axes[1, 1]: injection rate over time for 3 injection wells.
Three lines on one axis.
Add a fig.suptitle(...) for the dashboard title. Each panel needs axis labels and (where multiple lines are shown) a legend.
> Think about it: in panel 3, what does the slope of cum-oil vs > cum-water-injected tell you about sweep efficiency? What happens to > that slope as the flood matures?
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 matplotlib.pyplot as plt
t = np.arange(0, 36)
# Synthetic data
oil_rate = 1500 * np.exp(-0.04 * t)
water_rate = 200 + 50 * t + 30 * np.sin(t / 3)
water_cut = water_rate / (oil_rate + water_rate)
cum_oil = np.cumsum(oil_rate * 30) # bbl
cum_water_inj = np.cumsum((1800 + 80 * t) * 30) # bbl
inj_a = 1800 + 80 * t + 50 * np.sin(t / 4)
inj_b = 1500 + 60 * t + 30 * np.cos(t / 5)
inj_c = 2000 + 40 * t + 70 * np.sin(t / 6 + 1)
fig, axes = plt.subplots(2, 2, figsize=(14, 9))
# Panel 1: dual y-axis
ax1 = axes[0, 0]
ax1.plot(t, oil_rate, "C2-", label="Oil rate (bopd)")
ax1.set_xlabel("Month")
ax1.set_ylabel("Oil rate (bopd)", color="C2")
ax1b = ax1.twinx()
ax1b.plot(t, water_rate, "C0-", label="Water rate (bwpd)")
ax1b.set_ylabel("Water rate (bwpd)", color="C0")
ax1.set_title("Oil & water rate")
# Panel 2: water cut with economic limit
ax2 = axes[0, 1]
ax2.plot(t, water_cut, "C3-", label="Water cut")
ax2.axhline(0.90, color="black", linestyle="--", label="Economic limit (0.90)")
ax2.set_xlabel("Month"); ax2.set_ylabel("Water cut")
ax2.set_ylim(0, 1)
ax2.set_title("Water cut over time")
ax2.legend()
# Panel 3: cum_oil vs cum_water_inj
ax3 = axes[1, 0]
ax3.plot(cum_water_inj / 1e6, cum_oil / 1e6, "C1-")
ax3.set_xlabel("Cumulative water injected (MMbbl)")
ax3.set_ylabel("Cumulative oil produced (MMbbl)")
ax3.set_title("Sweep efficiency: cum oil vs cum water injected")
ax3.grid(True, alpha=0.3)
# Panel 4: three injectors
ax4 = axes[1, 1]
ax4.plot(t, inj_a, label="INJ-A")
ax4.plot(t, inj_b, label="INJ-B")
ax4.plot(t, inj_c, label="INJ-C")
ax4.set_xlabel("Month"); ax4.set_ylabel("Injection rate (bwpd)")
ax4.set_title("Injection rates by well")
ax4.legend()
fig.suptitle("Waterflood Surveillance Dashboard", fontweight="bold")
plt.tight_layout()
plt.show()
lockCopying code is a Full Access feature.