Exerciseschevron_rightChapter 8chevron_right8.10
fitness_center

Exercise 8.10

Complete Fluid Characterization

Level 3
Chapter 8: PVT Correlations
descriptionProblem

A wireline crew has just logged a new discovery on OML 58 and the lab turned around a separator test overnight. Before anyone books a barrel or sizes a facility, the reservoir engineer runs one calculation: a fluid characterization that tells you what kind of oil you have and (critically) whether the reservoir sits above or below its bubble point. That single fact decides everything downstream: whether free gas is coming out of solution in the reservoir, how you model the OOIP, and how the well will behave on first draw-down.

This is the discovery summary. You are given the cards on the table:

  • API gravity: 29° (a medium crude)
  • Gas specific gravity gamma_g: 0.82
  • Reservoir temperature T_F: 195 °F
  • Initial reservoir pressure Pi: 4800 psia
  • Initial solution GOR Rs: 480 scf/STB

Write one function that bundles the whole characterization:

characterize(API, gamma_g, T_F, Pi, Rs) -> dict

It must return a dict with exactly these keys:

  • gamma_o: oil specific gravity from api_to_sg(API).
  • pb_psia: bubble-point pressure from standing_bubble_point(Rs, gamma_g, T_F, API).
  • bo_at_pb: oil FVF at the bubble point, standing_Bo(Rs, gamma_g, gamma_o, T_F)

(Standing's Bo is evaluated at bubble-point conditions; it uses the oil gravity you just computed, not the API).

  • mu_at_pb: live-oil viscosity at the bubble point: take the dead-oil

viscosity beggs_robinson_dead_oil(T_F, API), then apply the gas-in-solution correction beggs_robinson_live_oil(mu_od, Rs).

  • state: the string "undersaturated" if Pi > pb_psia, else "saturated".
  • undersaturation_psi: the cushion Pi - pb_psia (how far the reservoir

sits above its bubble point).

Then call characterize on the discovery data above and store the result in report, and pull state out into discovery_state for a quick read.

For this fluid the bubble point lands well below 4800 psia, so the reservoir is undersaturated: no free gas in the reservoir yet, and that undersaturation_psi cushion is the pressure you can produce before gas starts breaking out.

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

# ── OML 58 new-discovery data (separator test + initial conditions) ──────
API = 29.0          # oil API gravity, degrees
GAMMA_G = 0.82      # gas specific gravity (air = 1)
T_F = 195.0         # reservoir temperature, deg F
PI_PSIA = 4800.0    # initial reservoir pressure, psia
RS_SCF_STB = 480.0  # initial solution GOR, scf/STB


# ── PVT correlations (provided - do not edit) ────────────────────────────
def api_to_sg(API):
    return 141.5 / (API + 131.5)


def standing_bubble_point(Rs, gamma_g, T_F, API):
    exponent = 0.00091 * T_F - 0.0125 * API
    return 18.2 * ((Rs / gamma_g) ** 0.83 * 10 ** exponent - 1.4)


def standing_Bo(Rs, gamma_g, gamma_o, T_F):
    F = Rs * np.sqrt(gamma_g / gamma_o) + 1.25 * T_F
    return 0.972 + 1.47e-4 * F ** 1.175


def beggs_robinson_dead_oil(T_F, API):
    Y = 10 ** (3.0324 - 0.02023 * API)
    X = Y * T_F ** (-1.163)
    return 10 ** X - 1


def beggs_robinson_live_oil(mu_od, Rs):
    A = 10.715 * (Rs + 100) ** (-0.515)
    B = 5.44 * (Rs + 150) ** (-0.338)
    return A * mu_od ** B


# ── Your task ────────────────────────────────────────────────────────────
def characterize(API, gamma_g, T_F, Pi, Rs):
    """One-call new-discovery fluid summary -> dict."""
    gamma_o = api_to_sg(API)
    pb_psia = standing_bubble_point(Rs, gamma_g, T_F, API)
    bo_at_pb = standing_Bo(Rs, gamma_g, gamma_o, T_F)
    mu_od = beggs_robinson_dead_oil(T_F, API)
    mu_at_pb = beggs_robinson_live_oil(mu_od, Rs)
    state = "undersaturated" if Pi > pb_psia else "saturated"
    undersaturation_psi = Pi - pb_psia
    return {
        "gamma_o": float(gamma_o),
        "pb_psia": float(pb_psia),
        "bo_at_pb": float(bo_at_pb),
        "mu_at_pb": float(mu_at_pb),
        "state": state,
        "undersaturation_psi": float(undersaturation_psi),
    }


report = characterize(API, GAMMA_G, T_F, PI_PSIA, RS_SCF_STB)
discovery_state = report["state"]

print("characterization:", report)
print("reservoir state:", discovery_state)

lockCopying code is a Full Access feature.