Exerciseschevron_rightChapter 8chevron_right8.7
fitness_center

Exercise 8.7

Build a PVT Report

Level 3
Chapter 8: PVT Correlations
descriptionProblem

Write a function pvt_report(API, gamma_g, T_F, Rs_at_Pb, pressures) that generates a complete PVT property table showing Rs, Bo, μo, and fluid state (undersaturated/saturated) at each pressure in the input list.

Format the output as a report-ready table with headers and units. Test it with at least two different fluids.

---

This is the capstone for the PVT chapter: one function that turns four fluid parameters and a pressure schedule into a reservoir-engineering PVT report: exactly the table you would hand a material-balance or nodal-analysis run.

The base fluid is the OML 58 black oil: API = 32, gamma_g = 0.75, T_F = 185, with Rs_at_Pb = 550 scf/STB. Its bubble point lands near 2525 psia (Standing). Above that pressure the oil is undersaturated; all the gas is dissolved and Rs is pinned at Rs_at_Pb. Below it the oil is saturated: free gas has broken out and the dissolved Rs falls with pressure.

Write pvt_report(API, gamma_g, T_F, Rs_at_Pb, pressures) returning a list of dicts, one per pressure (same order as pressures), each with:

keymeaning
p_psiathe pressure (float)
rs_scf_stbsolution GOR at that pressure
bo_rb_stboil formation volume factor (standing_Bo)
mu_cplive-oil viscosity (Beggs-Robinson)
state'undersaturated' if P >= Pb else 'saturated'

Rules at each pressure P:

  • Compute the bubble point once with standing_bubble_point(Rs_at_Pb, ...).
  • If P >= Pb: state = 'undersaturated', rs_scf_stb = Rs_at_Pb

(constant; gas stays in solution).

  • If P < Pb: state = 'saturated',

rs_scf_stb = vasquez_beggs_Rs(P, T_F, API, gamma_g) (gas comes out of solution as pressure drops).

  • bo_rb_stb = standing_Bo(rs_scf_stb, gamma_g, gamma_o, T_F) where

gamma_o = api_to_sg(API).

  • mu_cp = beggs_robinson_live_oil(mu_od, rs_scf_stb) where

mu_od = beggs_robinson_dead_oil(T_F, API) is the dead-oil viscosity.

Then build the report for the base fluid over the PRESSURES schedule already defined for you (it straddles the bubble point) and store it in report.

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 PVT correlations (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 vasquez_beggs_Rs(P, T_F, API, gamma_gs):
    if API <= 30:
        C1, C2, C3 = 0.0362, 1.0937, 25.724
    else:
        C1, C2, C3 = 0.0178, 1.187, 23.931
    return C1 * gamma_gs * P ** C2 * np.exp(C3 * API / (T_F + 460))


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


# ── OML 58 base black oil + pressure schedule (straddles Pb ≈ 2525 psia) ─
BASE_API = 32.0
BASE_GAMMA_G = 0.75
BASE_T_F = 185.0
BASE_RS_AT_PB = 550.0
PRESSURES = [4000.0, 3000.0, 2000.0, 1000.0, 500.0]


def pvt_report(API, gamma_g, T_F, Rs_at_Pb, pressures):
    """List of per-pressure PVT dicts (p_psia, rs_scf_stb, bo_rb_stb, mu_cp, state)."""
    gamma_o = api_to_sg(API)
    mu_od = beggs_robinson_dead_oil(T_F, API)
    pb_psia = standing_bubble_point(Rs_at_Pb, gamma_g, T_F, API)

    rows = []
    for p in pressures:
        p_psia = float(p)
        if p_psia >= pb_psia:
            state = "undersaturated"
            rs_scf_stb = float(Rs_at_Pb)
        else:
            state = "saturated"
            rs_scf_stb = float(vasquez_beggs_Rs(p_psia, T_F, API, gamma_g))

        bo_rb_stb = float(standing_Bo(rs_scf_stb, gamma_g, gamma_o, T_F))
        mu_cp = float(beggs_robinson_live_oil(mu_od, rs_scf_stb))

        rows.append({
            "p_psia": p_psia,
            "rs_scf_stb": rs_scf_stb,
            "bo_rb_stb": bo_rb_stb,
            "mu_cp": mu_cp,
            "state": state,
        })
    return rows


report = pvt_report(BASE_API, BASE_GAMMA_G, BASE_T_F, BASE_RS_AT_PB, PRESSURES)

print("PVT report rows:", len(report))
print("first row:", report[0])

lockCopying code is a Full Access feature.