Exerciseschevron_rightChapter 9chevron_right9.4
fitness_center

Exercise 9.4

Abandonment Rate Sensitivity

Level 2
Chapter 9: Decline Curve Analysis
descriptionProblem

Using the hyperbolic fit from this chapter, calculate EUR for abandonment rates of 5, 10, 20, 50, and 100 bbl/d. Plot EUR vs. abandonment rate. Why does a lower abandonment rate significantly increase EUR, and what are the economic implications?

---

Well OD-014 on OML 58 has been history-matched to a hyperbolic Arps decline: qi_bopd = 1800, di_per_month = 0.15, b_factor = 0.6. The EUR you book against this well is not a single number. It depends on the abandonment rate q_abandon_bopd, the production rate at which the well is no longer worth keeping on. That rate is set by economics: oil price, lease operating expense, and the cost of artificial lift all push the economic limit up or down. Lower the abandonment rate (cheaper to keep producing, higher oil price) and the well lives longer, sweeping more reserves into the EUR.

For a hyperbolic well the closed-form cumulative from qi down to q_abandon is

EUR = (qi**b / (Di*(1-b))) * (qi**(1-b) - q_abandon**(1-b))

which is exactly eur_hyperbolic(qi, Di, b, q_abandon) (embedded for you). Mind the units: qi is bopd (bbl/DAY) while Di and time are per month, so eur_hyperbolic returns volume in bopd·months. Multiply by DAYS_PER_MONTH = 30.44 to report the booked EUR in barrels (this mirrors the book's own day/month convention).

Write eur_vs_abandonment(qi_bopd, di_per_month, b_factor, q_ab_list) returning a dict mapping each abandonment rate to its EUR in bbl:

keyvalue
q_abandon_bopd (float)eur_bbl = eur_hyperbolic(qi, Di, b, q_ab) * DAYS_PER_MONTH

Use the keys exactly as they appear in q_ab_list (one entry per rate, same rates, no extras). Then call it on the OD-014 parameters over the Q_AB_LIST = [5, 10, 20, 50, 100] (bopd) schedule already defined for you and store the result in eur_table.

You should see EUR fall monotonically as the abandonment rate climbs; the reserves you can book shrink as the economic limit rises. That single curve is the economic-limit tradeoff: every dollar of added operating cost that lifts the abandonment rate quietly strands barrels in the ground.

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


# ── Embedded Arps EUR closed form (do not edit) ──────────────────────────
def eur_hyperbolic(qi, Di, b, q_abandon):
    if qi <= q_abandon or b >= 1:
        return float("nan")
    return (qi ** b / (Di * (1.0 - b))) * (qi ** (1.0 - b) - q_abandon ** (1.0 - b))


# ── OD-014 hyperbolic fit (OML 58) + abandonment-rate schedule (bopd) ─────
# qi is bopd (bbl/DAY), Di and t are per-MONTH, so eur_hyperbolic returns
# bopd*months. Multiply by DAYS_PER_MONTH to report the EUR in barrels.
DAYS_PER_MONTH = 30.44
QI_BOPD = 1800.0
DI_PER_MONTH = 0.15
B_FACTOR = 0.6
Q_AB_LIST = [5.0, 10.0, 20.0, 50.0, 100.0]


def eur_vs_abandonment(qi_bopd, di_per_month, b_factor, q_ab_list):
    """Map each abandonment rate (bopd) -> hyperbolic EUR (bbl)."""
    table = {}
    for q_abandon_bopd in q_ab_list:
        eur_bbl = float(
            eur_hyperbolic(qi_bopd, di_per_month, b_factor, q_abandon_bopd) * DAYS_PER_MONTH
        )
        table[q_abandon_bopd] = eur_bbl
    return table


eur_table = eur_vs_abandonment(QI_BOPD, DI_PER_MONTH, B_FACTOR, Q_AB_LIST)

print("EUR table:", eur_table)
for q_ab in Q_AB_LIST:
    print(f"  q_abandon={q_ab:6.1f} bopd -> EUR={eur_table[q_ab]:,.0f} bbl")

lockCopying code is a Full Access feature.