Exercise 14.8
Offset Well Comparison - Composite Best Time-Depth Curve
Given time-depth data for three offset wells and the current well, plot all four on the same chart. Calculate the composite best performance (taking the fastest time at each depth from any well). How much time could be saved if the current well matched the composite best?
---
We'll benchmark the current well on OML 58 against three offset wells that drilled the same hole section. A time-depth curve plots cumulative depth against elapsed days; the standard way to compare rigs is to put every well on a common depth grid and read off who reached each depth fastest.
The current well is the chapter's Well A. It lost two days to a stuck-pipe event at 6,500 ft and reached TD (9,200 ft) in 24 days. The three offsets drilled cleaner. The composite best curve takes, at each depth, the fastest time any one well achieved there: a synthetic "best of fleet" benchmark. If the current well had matched that composite all the way down, how many days would it have saved?
All four wells share the same depth grid. The data are embedded for you as constants (do not modify them):
CURRENT_DEPTHS = [0,500,1500,3000,4500,5500,6500,7500,8500,9200](ft)CURRENT_DAYS = [0,0.5,1.5,3,5.5,8,11,16,20,24]OFFSET_B_DAYS = [0,0.5,1.5,3,5,7.5,10,13,16.5,20]OFFSET_C_DAYS = [0,0.6,1.8,3.5,5.2,7,9.5,12,15,18.5]OFFSET_D_DAYS = [0,0.55,1.6,3.2,5.3,7.8,10.5,13.5,17,21]DEPTH_GRID = np.arange(0, 9201, 200.0): the common comparison grid (47 nodes)
Each offset uses the same depths as CURRENT_DEPTHS. The wells list for the composite includes all four wells (current + B + C + D).
Your tasks:
- Write
interp_days(depths, days, query_depths=DEPTH_GRID), a thin
np.interp wrapper that resamples one well's (depths, days) onto query_depths and returns the interpolated days array.
- Write
composite_best(wells, query_depths=DEPTH_GRID)wherewellsis a list
of (depths, days) tuples. Interpolate every well onto query_depths and return the element-wise minimum days across all wells at each depth: the fastest time any well achieved at that depth. Return an np.ndarray the same length as query_depths.
- Write
time_savings(current, wells, query_depths=DEPTH_GRID)wherecurrent
is the (depths, days) tuple for the current well. Return the float current_total_days - composite_total_days, i.e. the current well's final day minus the composite curve's final day: the days that could be saved if the current well had matched the composite best at TD.
- Build the four-well list (current + B + C + D), then assign:
composite_curve:composite_best(...)overDEPTH_GRID(length 47).current_total: the current well's final days (float).composite_total: the composite curve's final value (float).savings_days:time_savings(...)for the current well vs. all four.
> Think about it: the composite curve is a lower envelope. At every depth > it can never be slower than any single well, so composite_curve should sit at > or below the current well's own curve everywhere, and it can only increase with > depth (you never un-drill footage). Why is the composite final day exactly the > fastest offset's TD time whenever one well dominates the bottom hole section?
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
# ── Verified OML-58 offset time-depth data (from chapter 14.8, do not edit) ──
# All four wells drilled the same hole section and share one depth grid (ft).
# Days are cumulative elapsed days to reach each depth. The CURRENT well is the
# chapter's Well A: a 2-day stuck-pipe event at 6,500 ft pushed it to 24 days.
CURRENT_DEPTHS = np.array([0, 500, 1500, 3000, 4500, 5500, 6500, 7500, 8500, 9200], float)
CURRENT_DAYS = np.array([0, 0.5, 1.5, 3, 5.5, 8, 11, 16, 20, 24], float)
OFFSET_DEPTHS = CURRENT_DEPTHS # offsets use the same depth grid
OFFSET_B_DAYS = np.array([0, 0.5, 1.5, 3, 5, 7.5, 10, 13, 16.5, 20], float)
OFFSET_C_DAYS = np.array([0, 0.6, 1.8, 3.5, 5.2, 7, 9.5, 12, 15, 18.5], float)
OFFSET_D_DAYS = np.array([0, 0.55, 1.6, 3.2, 5.3, 7.8, 10.5, 13.5, 17, 21], float)
# Common comparison grid: 0 .. 9200 ft in 200-ft steps (47 nodes).
DEPTH_GRID = np.arange(0, 9201, 200.0)
def interp_days(depths, days, query_depths=DEPTH_GRID):
"""Resample one well's time-depth curve onto query_depths (np.interp wrapper)."""
return np.interp(query_depths, depths, days)
def composite_best(wells, query_depths=DEPTH_GRID):
"""Composite best curve: element-wise MINIMUM days across all wells.
wells is a list of (depths, days) tuples. Interpolate every well onto
query_depths and return the fastest (smallest) time at each depth.
"""
curves = [interp_days(d, t, query_depths) for (d, t) in wells]
return np.minimum.reduce(curves)
def time_savings(current, wells, query_depths=DEPTH_GRID):
"""Days saveable if the current well had matched the composite best.
current is the (depths, days) tuple for the current well; wells is the list
of all wells (including current). Returns current_total - composite_total.
"""
cur_d, cur_t = current
current_total = float(interp_days(cur_d, cur_t, query_depths)[-1])
composite_total = float(composite_best(wells, query_depths)[-1])
return current_total - composite_total
# Build the four-well list (current + B + C + D) and compute the outputs.
WELLS = [
(CURRENT_DEPTHS, CURRENT_DAYS),
(OFFSET_DEPTHS, OFFSET_B_DAYS),
(OFFSET_DEPTHS, OFFSET_C_DAYS),
(OFFSET_DEPTHS, OFFSET_D_DAYS),
]
composite_curve = composite_best(WELLS)
current_total = float(interp_days(CURRENT_DEPTHS, CURRENT_DAYS)[-1])
composite_total = float(composite_curve[-1])
savings_days = time_savings((CURRENT_DEPTHS, CURRENT_DAYS), WELLS)
print("current total days: ", current_total)
print("composite total days: ", composite_total)
print("days saveable: ", savings_days)
lockCopying code is a Full Access feature.