Exerciseschevron_rightChapter 14chevron_right14.5
fitness_center

Exercise 14.5

Optimal WOB and RPM - Mining the Drilling Sweet Spot

Level 2
Chapter 14: Drilling Analytics
descriptionProblem

For a given depth interval (constant formation), vary WOB from 10 to 45 klbf and RPM from 60 to 180. Use the drilling data to find the WOB-RPM combination that historically produced the highest ROP in that interval. Present the results as a contour plot.

---

We'll work this on a single constant-formation interval of an OML-58 well, so rock strength is fixed and ROP responds only to the controllable parameters WOB (weight on bit) and RPM (rotary speed). A synthetic interval DataFrame is built for you with np.random.seed(11) and 2,000 bottomhole records where ROP rises with both WOB and RPM but rolls over at high values (there is a real interior sweet spot) plus measurement noise. Do not modify the generator or re-derive the physics; your job is to mine the data, not regenerate it.

The bin edges are fixed constants:

  • WOB_BINS = np.arange(10, 50, 5) → edges 10, 15, 20, 25, 30, 35, 40, 45
  • RPM_BINS = np.arange(60, 200, 20) → edges 60, 80, 100, 120, 140, 160, 180

Your tasks:

  1. Write rop_grid(df, wob_bins=WOB_BINS, rpm_bins=RPM_BINS):
  • Bin each row's WOB with pd.cut(df["WOB"], bins=wob_bins) and its RPM with

pd.cut(df["RPM"], bins=rpm_bins).

  • Group by both bins and take the mean ROP in each cell.
  • Return a 2-D pandas table (a pivot / unstack) whose rows are WOB bins

and columns are RPM bins, with mean ROP as the values.

  1. Write optimal_wob_rpm(df, wob_bins=WOB_BINS, rpm_bins=RPM_BINS):
  • Find the single cell with the highest mean ROP.
  • Return the tuple (best_wob, best_rpm, best_rop) where best_wob and

best_rpm are the two pandas Interval objects of the winning cell and best_rop is that cell's mean ROP as a float.

  1. Build the table and find the optimum on the provided interval data:
  • grid = rop_grid(interval)
  • best_wob, best_rpm, best_rop = optimal_wob_rpm(interval)

The output variables the tests read are exactly: grid (the table), best_wob and best_rpm (the two pandas Interval objects), and best_rop (a float).

> Think about it: for this interval the highest mean ROP lands in the > high-WOB / high-RPM corner, around 80.6 ft/hr in cell WOB (40, 45], > RPM (160, 180]. But that corner is a property of this formation's data, not > a universal answer; feed in a different interval and the sweet spot moves. Your > function must read it off the grid every time, never assume the corner.

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 numpy as np
import pandas as pd


# ── Verified constant-formation interval generator + bin edges (do not edit) ──
# A single OML-58 drilling interval: rock strength is fixed, so ROP responds only
# to WOB and RPM. ROP rises with both but rolls over at high values (a real
# interior sweet spot) plus N(0, 3) measurement noise. Seeded for reproducibility.
WOB_BINS = np.arange(10, 50, 5)    # edges 10, 15, 20, 25, 30, 35, 40, 45
RPM_BINS = np.arange(60, 200, 20)  # edges 60, 80, 100, 120, 140, 160, 180


def make_interval():
    """Build the constant-formation interval DataFrame (do not edit)."""
    np.random.seed(11)
    n = 2000
    WOB = np.random.uniform(10, 45, n)
    RPM = np.random.uniform(60, 180, n)
    ROP = (5 + 1.2 * WOB + 0.15 * RPM
           - 0.01 * (WOB - 35) ** 2 - 0.0008 * (RPM - 150) ** 2
           + np.random.normal(0, 3, n))
    return pd.DataFrame({"WOB": WOB, "RPM": RPM, "ROP": ROP})


interval = make_interval()


# ── Your work starts here ────────────────────────────────────────────────────
def rop_grid(df, wob_bins=WOB_BINS, rpm_bins=RPM_BINS):
    """Mean ROP per (WOB bin, RPM bin) cell as a 2-D table.

    Rows are WOB bins, columns are RPM bins, values are mean ROP.
    """
    d = df.copy()
    d["WOB_bin"] = pd.cut(d["WOB"], bins=wob_bins)
    d["RPM_bin"] = pd.cut(d["RPM"], bins=rpm_bins)
    grid = (d.groupby(["WOB_bin", "RPM_bin"], observed=False)["ROP"]
              .mean()
              .unstack())
    return grid


def optimal_wob_rpm(df, wob_bins=WOB_BINS, rpm_bins=RPM_BINS):
    """Return (best_wob, best_rpm, best_rop) for the highest-mean-ROP cell.

    best_wob, best_rpm are pandas Interval objects; best_rop is a float.
    """
    grid = rop_grid(df, wob_bins, rpm_bins)
    stacked = grid.stack()
    best_wob, best_rpm = stacked.idxmax()
    best_rop = float(stacked.max())
    return best_wob, best_rpm, best_rop


grid = rop_grid(interval)
best_wob, best_rpm, best_rop = optimal_wob_rpm(interval)

print("grid shape:", grid.shape)
print("best WOB bin:", best_wob, "  best RPM bin:", best_rpm)
print("best mean ROP (ft/hr):", best_rop)

lockCopying code is a Full Access feature.