Exerciseschevron_rightChapter 14chevron_right14.4
fitness_center

Exercise 14.4

Stick-Slip Severity Index - Torque CV & ROP Loss

Level 2
Chapter 14: Drilling Analytics
descriptionProblem

Extend the stick-slip detection function to return a severity index (0 to 1) based on the ratio of torque standard deviation to mean torque. Plot severity vs. depth and correlate with ROP. By how much does severe stick-slip reduce drilling rate?

---

The chapter's detect_stick_slip(...) only gives you a yes/no flag. A driller wants to know how bad the dysfunction is and what it costs in penetration rate. You will turn the boolean detector into a continuous severity index and then quantify the ROP loss it causes on an OML well section.

The verified detect_stick_slip(torque, rpm, window=50, torque_cv_threshold=0.3) is embedded for you (read it; you are extending the same idea) and a self-contained well DataFrame is built for you with np.random.seed(99): 600 rows over Depth 8000 → 9200 ft, baseline Torque ~6000 ft-lbs, RPM ~120, ROP ~40 ft/hr, with a clear injected stick-slip zone from 8400 to 8800 ft where the torque swings violently (6000 + 4000*sin(...)) and the ROP is depressed by 18 ft/hr.

Your tasks:

  1. Write stick_slip_severity(torque, window=30) returning a pandas.Series

the same length as torque. For each rolling window it is the torque coefficient of variation: rolling_std / rolling_mean (use pandas' default ddof=1 std, and .replace(0, np.nan) on the mean to avoid divide-by-zero), then clipped to [0, 1] so it reads as a 0-to-1 severity index. Leading rows are NaN (the window has not filled yet). That is expected.

  1. Write rop_reduction(df, severity_threshold=0.2, window=30) that splits the

rows by severity and returns a dict:

  • severe = rows where severity > severity_threshold
  • mild = rows where severity <= severity_threshold
  • 'rop_severe' = mean df["ROP"] over the severe rows
  • 'rop_mild' = mean df["ROP"] over the mild rows
  • 'reduction_pct' = 100 * (1 - rop_severe / rop_mild)
  1. Assign the output variables exactly:
  • severity = stick_slip_severity(well["Torque"])
  • max_severity = float(severity.max())
  • reduction = rop_reduction(well)
  • reduction_pct = float(reduction["reduction_pct"])

> Think about it: severity is a ratio (std ÷ mean), so it is dimensionless > and scale-invariant: doubling every torque reading leaves it unchanged. That is > exactly why a CV beats raw standard deviation for comparing wells with different > baseline torque. By how much does the severe zone drop the ROP versus the calm > rock around it?

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 stick-slip detector from the chapter (do not edit) ──────────
def detect_stick_slip(torque, rpm, window=50, torque_cv_threshold=0.3):
    """
    Detect stick-slip vibration from surface measurements.

    Stick-slip is indicated by high coefficient of variation
    in torque at relatively stable surface RPM.

    Parameters
    ----------
    torque : array-like
        Torque measurements (ft-lbs).
    rpm : array-like
        Surface RPM measurements.
    window : int
        Rolling window size.
    torque_cv_threshold : float
        Coefficient of variation threshold for flagging.

    Returns
    -------
    pandas.Series
        Boolean mask where True indicates stick-slip.
    """
    tq = pd.Series(torque)
    r = pd.Series(rpm)

    tq_mean = tq.rolling(window).mean()
    tq_std = tq.rolling(window).std()
    rpm_std = r.rolling(window).std()

    # Coefficient of variation in torque
    tq_cv = tq_std / tq_mean.replace(0, np.nan)

    # RPM should be relatively stable (driller holding steady)
    rpm_stable = rpm_std < 15

    return (tq_cv > torque_cv_threshold) & rpm_stable


# ── Self-contained OML well section (do not edit) ────────────────────────
def build_well():
    """600-row well with a clear injected stick-slip zone (8400-8800 ft)."""
    np.random.seed(99)
    n = 600
    depth = np.linspace(8000, 9200, n)
    torque = 6000 + np.random.normal(0, 150, n)
    rop = 40 + np.random.normal(0, 3, n)
    rpm = 120 + np.random.normal(0, 5, n)

    ss = (depth >= 8400) & (depth <= 8800)        # the stick-slip zone
    torque[ss] = 6000 + 4000 * np.sin(np.linspace(0, 80, ss.sum()))
    rop[ss] = rop[ss] - 18                          # ROP depressed by dysfunction

    return pd.DataFrame({"Depth": depth, "Torque": torque, "ROP": rop, "RPM": rpm})


well = build_well()


def stick_slip_severity(torque, window=30):
    """Rolling torque coefficient of variation, clipped to [0, 1].

    severity = rolling_std(ddof=1) / rolling_mean   (mean 0 -> NaN), clip [0, 1]
    Returns a pandas.Series the same length as torque (leading rows are NaN).
    """
    tq = pd.Series(torque)
    cv = tq.rolling(window).std() / tq.rolling(window).mean().replace(0, np.nan)
    return cv.clip(0, 1)


def rop_reduction(df, severity_threshold=0.2, window=30):
    """Split rows by severity and quantify the ROP penalty.

    Returns {'rop_severe', 'rop_mild', 'reduction_pct'} where
      severe        = severity > severity_threshold
      mild          = severity <= severity_threshold
      reduction_pct = 100 * (1 - rop_severe / rop_mild)
    """
    sev = stick_slip_severity(df["Torque"], window=window)
    severe = sev > severity_threshold
    mild = sev <= severity_threshold
    rop_severe = df["ROP"][severe].mean()
    rop_mild = df["ROP"][mild].mean()
    reduction_pct = 100 * (1 - rop_severe / rop_mild)
    return {"rop_severe": rop_severe, "rop_mild": rop_mild,
            "reduction_pct": reduction_pct}


severity = stick_slip_severity(well["Torque"])
max_severity = float(severity.max())
reduction = rop_reduction(well)
reduction_pct = float(reduction["reduction_pct"])

print("max severity:", round(max_severity, 4))
print("ROP severe (ft/hr):", round(reduction["rop_severe"], 2))
print("ROP mild (ft/hr):", round(reduction["rop_mild"], 2))
print("ROP loss in severe stick-slip (%):", round(reduction_pct, 2))

lockCopying code is a Full Access feature.