Exerciseschevron_rightChapter 10chevron_right10.3
fitness_center

Exercise 10.3

Drive-Mechanism Diagnosis

Level 2
Chapter 10: Material Balance
descriptionProblem

Using the data from Exercise 10.2, calculate F/EtF/E_t at each pressure step and plot it against cumulative production. Is the trend stable, rising, or falling? What does this tell you about the drive mechanism?

---

The material-balance equation does more than count barrels: read it the right way and it diagnoses the reservoir's drive mechanism. The trick is the ratio of underground withdrawal F to the total expansion the reservoir can muster on its own, Et = Eo + Efw (oil-plus-dissolved-gas expansion plus the rock-and-connate-water expansion).

Rearrange the MBE for a reservoir with no gas cap and no water influx and you get F = N * Et, i.e. F / Et = N, a constant equal to the OOIP. That is the Campbell / drive-diagnostic plot: chart F / Et against cumulative production and the shape of the curve names the drive:

  • Flat -> the reservoir is living off its own expansion alone:

depletion drive (a.k.a. solution-gas / volumetric depletion above the bubble point). F / Et stays pinned at the OOIP.

  • Rising -> an extra source of energy is pushing oil out beyond what

expansion explains: water drive (an aquifer We is doing work the right-hand side does not account for, so the apparent N inflates over time).

  • Falling -> a gas cap is expanding and its energy is being mis-charged

to Et, dragging the apparent N down.

Well block OD-014 on OML 58 produced the history embedded in the starter. It is a classic undersaturated block: pressure stays above the bubble point, so Bo increases as pressure drops (the oil swells) and the free-gas term cancels (Rp - Rs = 0), leaving F = Np * Bo.

Write one function:

  • diagnose(P, Np, Gp, Wp, Bo, Bg, Rs, Bw, Boi, Bgi, Rsi, Swi, cw, cf, Pi)

-> (ratios_psi_free, label) where:

  • ratios is the NumPy array of F / Et at every step after t = 0

(skip the first row; at initial conditions Et = 0 and the ratio is undefined).

  • label is a string: "depletion" if F / Et is essentially flat

(coefficient of variation, std / mean, below 0.02), "water drive" if the ratio is clearly rising, or "gas cap" if it is clearly falling.

Use calculate_mbe_terms(...) (provided) to get F, Eo, Efw, then form Et = Eo + Efw. Call diagnose(...) on the OD-014 inputs and store the result in ratios and label.

For OD-014 you should find every F / Et value sitting right around 50,000,000 STB (the OOIP), a coefficient of variation well under 0.001, and label == "depletion". A stable ratio means pure depletion: the reservoir is producing on its own expansion alone, with no aquifer and no gas cap feeding it external energy. That is a sober diagnosis: depletion drive recovers the least oil, so OD-014 is a candidate for pressure maintenance (water or gas injection) if the economics support 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


# ── MBE helpers (provided - do not change) ───────────────────────────────
def calculate_mbe_terms(P, Np, Gp, Wp, Bo, Bg, Rs, Bw, Boi, Bgi, Rsi, Swi, cw, cf, Pi):
    P = np.asarray(P, float); Np = np.asarray(Np, float); Gp = np.asarray(Gp, float)
    Wp = np.asarray(Wp, float); Bo = np.asarray(Bo, float); Bg = np.asarray(Bg, float); Rs = np.asarray(Rs, float)
    dP = Pi - P
    Rp = np.where(Np > 0, Gp / Np, 0.0)
    F   = Np * (Bo + (Rp - Rs) * Bg) + Wp * Bw           # underground withdrawal (RB)
    Eo  = (Bo - Boi) + (Rsi - Rs) * Bg                   # oil + dissolved-gas expansion
    Efw = Boi * (cw * Swi + cf) * dP / (1.0 - Swi)       # rock + connate-water expansion
    Eg  = Boi / Bgi * (Bg - Bgi)                          # gas-cap expansion (per unit m)
    return F, Eo, Eg, Efw, Rp, dP


# ── OD-014 production + PVT history (OML 58, undersaturated block) ────────
# Pressure stays ABOVE the bubble point: Bo RISES as P drops (oil swells),
# Rs is constant, and the free-gas term cancels (Rp - Rs = 0) so F = Np * Bo.
P_psi  = np.array([4000.0, 3800.0, 3600.0, 3400.0, 3200.0, 3000.0])
Np_stb = np.array([0.0, 192666.0, 384411.0, 575243.0, 765167.0, 954191.0])
Bo     = np.array([1.31000, 1.31314, 1.31629, 1.31943, 1.32258, 1.32572])  # RISES as P drops
Rs     = np.full(6, 600.0)            # constant above the bubble point
Bg     = np.full(6, 0.00090)          # cancels above Pb (Rp - Rs = 0); present for the signature
Bw     = 1.02
Wp_stb = np.zeros(6)
Gp_scf = Np_stb * 600.0               # Gp = Np * Rsi above Pb
Boi, Bgi, Rsi, Swi, cw, cf, Pi = 1.31000, 0.00087, 600.0, 0.22, 3.2e-6, 5.0e-6, 4000.0


def diagnose(P, Np, Gp, Wp, Bo, Bg, Rs, Bw, Boi, Bgi, Rsi, Swi, cw, cf, Pi):
    """Compute F/Et after t=0 and name the drive mechanism.

    Returns (ratios, label) where:
      ratios -> np.array of F / Et for every step after the first row,
      label  -> "depletion" if std/mean < 0.02 (flat), else "water drive"
                if rising, else "gas cap" if falling.
    """
    F, Eo, _Eg, Efw, _Rp, _dP = calculate_mbe_terms(
        P, Np, Gp, Wp, Bo, Bg, Rs, Bw, Boi, Bgi, Rsi, Swi, cw, cf, Pi
    )
    Et = Eo + Efw
    ratios = (F / Et)[1:]                      # skip t=0 where Et == 0
    cov = ratios.std() / ratios.mean()
    if cov < 0.02:
        label = "depletion"
    elif ratios[-1] > ratios[0]:
        label = "water drive"
    else:
        label = "gas cap"
    return ratios, label


ratios, label = diagnose(
    P_psi, Np_stb, Gp_scf, Wp_stb, Bo, Bg, Rs, Bw,
    Boi, Bgi, Rsi, Swi, cw, cf, Pi,
)

print(f"F/Et per step (STB): {np.array2string(ratios, precision=0)}")
print(f"mean F/Et = {ratios.mean():,.0f} STB   "
      f"CoV = {ratios.std() / ratios.mean():.5f}")
print(f"drive diagnosis -> {label.upper()}")

lockCopying code is a Full Access feature.