Exerciseschevron_rightChapter 14chevron_right14.2
fitness_center

Exercise 14.2

MSE Calculation - Drilling Efficiency from Mechanical Specific Energy

Level 2
Chapter 14: Drilling Analytics
descriptionProblem

Calculate MSE for a well drilled with an 8.5" bit. Plot MSE vs. depth and identify the depth intervals where MSE exceeds 3× the estimated rock strength. What might be causing the inefficiency?

---

We're logging an 8.5" bit through a deep OML-58 hole. Mechanical Specific Energy (MSE) is the single best drilling-efficiency metric: it's the energy spent destroying a unit volume of rock, in psi. Teale's equation is

MSE=480TRPMDb2ROP+4WOBπDb2MSE = \frac{480\,T\,RPM}{D_b^2\,ROP} + \frac{4\,WOB}{\pi\,D_b^2}

In a perfectly efficient system MSE would equal the rock's confined compressive strength (CCS). It never does: friction, vibration and dull cutters waste energy, so MSE always runs above CCS. When MSE climbs past 3× the rock strength, the bit is grinding, not cutting, and something is wrong.

The verified compute_mse(torque_ftlbs, rpm, wob_lbf, rop_fthr, bit_diameter_in) is embedded for you (do not edit it or re-derive the physics.) Note that it expects WOB in lbf, while field logs report it in klbf (thousands of lbf). A fixed, self-contained well DataFrame (300 rows, seed 142) is also provided with columns Depth (ft), Torque (ft-lbs), RPM, WOB (klbf) and ROP (ft/hr). The constant BIT_DIAMETER = 8.5 (inches).

Your tasks:

  1. Write mse_profile(torque, rpm, wob_klbf, rop, db=BIT_DIAMETER):
  • Convert WOB from klbf to lbf (multiply by 1000).
  • Call compute_mse(...) with the converted WOB and return its result

(a numpy.ndarray of MSE values in psi).

  1. Write flag_inefficient(mse_arr, ccs_arr, factor=3.0):
  • Return a boolean mask that is True wherever mse > factor * ccs

(the book's "3× rock strength" inefficiency threshold).

  1. Using the embedded ccs_estimate(depth) = 5000 + 1.5 * depth and the well

DataFrame, build these output variables:

  • mse_arr = mse_profile over the whole well (psi).
  • mse_mean = float(np.mean(mse_arr)).
  • ccs_arr = ccs_estimate(well["Depth (ft)"]).
  • inefficient_mask = flag_inefficient(mse_arr, ccs_arr) (factor 3.0).
  • inefficient_pct = percentage of cells flagged inefficient

(float(inefficient_mask.mean() * 100)).

> Think about it: the rotary term 480*T*RPM/(Db²*ROP) dominates. It's > inversely proportional to ROP, so a fast, clean cut keeps MSE low while a > stalled bit at low ROP sends MSE soaring. The small 4*WOB/(π*Db²) term is > only a few hundred psi here. Why does forgetting the klbf→lbf conversion barely > move MSE, yet still be a real bug you must get right?

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 Mechanical Specific Energy (Teale) - chapter 14 (do not edit) ─
def compute_mse(torque_ftlbs, rpm, wob_lbf, rop_fthr, bit_diameter_in):
    """
    Calculate Mechanical Specific Energy.

    Parameters
    ----------
    torque_ftlbs : array-like
        Torque (ft-lbs).
    rpm : array-like
        Rotary speed (rev/min).
    wob_lbf : array-like
        Weight on bit (lbf). Note: input in lbf, not klbf.
    rop_fthr : array-like
        Rate of penetration (ft/hr).
    bit_diameter_in : float
        Bit diameter (inches).

    Returns
    -------
    numpy.ndarray
        MSE values (psi).
    """
    torque = np.asarray(torque_ftlbs, dtype=float)
    r = np.asarray(rpm, dtype=float)
    w = np.asarray(wob_lbf, dtype=float)
    rop = np.asarray(rop_fthr, dtype=float)
    db = bit_diameter_in

    # Avoid division by zero
    rop_safe = np.where(rop > 0, rop, 1e-6)

    rotary_term = 480 * torque * r / (db**2 * rop_safe)
    wob_term = 4 * w / (np.pi * db**2)

    return rotary_term + wob_term


# ── Constants and confined-compressive-strength model (do not edit) ───────
BIT_DIAMETER = 8.5  # inches


def ccs_estimate(depth):
    """Estimated confined compressive strength (psi), rising with depth."""
    return 5000 + 1.5 * np.asarray(depth, dtype=float)


# ── Fixed, self-contained OML-58 drilling log (do not edit) ───────────────
np.random.seed(142)
_n = 300
well = pd.DataFrame({
    "Depth (ft)":      np.random.uniform(6000, 9000, _n),
    "Torque (ft-lbs)": np.random.uniform(2000, 12000, _n),
    "RPM":             np.random.uniform(90, 150, _n),
    "WOB (klbf)":      np.random.uniform(18, 40, _n),
    "ROP (ft/hr)":     np.random.uniform(15, 90, _n),
})


def mse_profile(torque, rpm, wob_klbf, rop, db=BIT_DIAMETER):
    """MSE (psi) from field logs, converting WOB from klbf to lbf first."""
    wob_lbf = np.asarray(wob_klbf, dtype=float) * 1000.0
    return compute_mse(torque, rpm, wob_lbf, rop, db)


def flag_inefficient(mse_arr, ccs_arr, factor=3.0):
    """Boolean mask: True where MSE exceeds factor x rock strength (CCS)."""
    return np.asarray(mse_arr) > factor * np.asarray(ccs_arr)


mse_arr = mse_profile(well["Torque (ft-lbs)"], well["RPM"],
                      well["WOB (klbf)"], well["ROP (ft/hr)"])
mse_mean = float(np.mean(mse_arr))
ccs_arr = ccs_estimate(well["Depth (ft)"])
inefficient_mask = flag_inefficient(mse_arr, ccs_arr)
inefficient_pct = float(inefficient_mask.mean() * 100)

print("mean MSE (psi):", mse_mean)
print("inefficient cells (%):", inefficient_pct)

lockCopying code is a Full Access feature.