Exercise 8.3
Viscosity Profile by API Gravity
Three appraisal wells on OML 58 each penetrated a different oil leg, and the lab gave you three crudes: a heavy 20° API, a medium 32° API, and a light 45° API. Same gas gravity (gamma_g = 0.75), same reservoir temperature (T_F = 180 °F), and the same solution gas-oil ratio at the bubble point (Rs = 500 scf/STB) for all three. The facilities team needs to know how much pumping horsepower each barrel will cost, and viscosity is what drives that.
You'll run the Beggs-Robinson viscosity correlation, which is a two-step chain:
- Dead-oil (gas-free) viscosity from API and temperature:
mu_od = beggs_robinson_dead_oil(T_F, API), in cp.
- Live-oil (gas-saturated) viscosity by correcting the dead-oil value for
the dissolved gas at the bubble point: mu_l = beggs_robinson_live_oil(mu_od, Rs), in cp.
Both functions are embedded for you. Write one function:
viscosity_by_api(api_list, T_F, Rs): for each API inapi_list, compute
the dead-oil and live-oil viscosities and return a dict keyed by the API value:
``python { 20: {"mu_dead_cp": ..., "mu_live_cp": ..., "reduction_pct": ...}, 32: {...}, 45: {...}, } ``
where reduction_pct = (mu_dead_cp - mu_live_cp) / mu_dead_cp * 100, the percentage of the dead-oil viscosity that the dissolved gas knocks out.
Then build the profile and store it:
profile = viscosity_by_api([20, 32, 45], T_F=180.0, Rs=500.0)Optional plot: make mu_live_cp vs API (one line, markers welcome). Label the x-axis "API gravity (°API)" and the y-axis "Live oil viscosity (cp)" so the heavy-to-light fall-off is obvious.
The physics you should see: the heaviest crude (20° API) is the most viscous, both dead and live; dissolved gas always cuts viscosity, so mu_live_cp < mu_dead_cp in every case; and the gap between the heavy barrel and the light barrel is large; that 20° API oil costs far more to move.
Stuck? Reveal hints one at a time — they progress from nudge to near-solution.
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 matplotlib.pyplot as plt
# OML 58 appraisal crudes: same gas gravity, temperature, and Rs at Pb.
GAMMA_G = 0.75
T_F = 180.0 # reservoir temperature, °F
RS_AT_PB = 500.0 # solution GOR at the bubble point, scf/STB
API_LIST = [20, 32, 45] # heavy, medium, light
def beggs_robinson_dead_oil(T_F, API):
"""Dead-oil (gas-free) viscosity, cp."""
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):
"""Live-oil (gas-saturated) viscosity from dead-oil viscosity, cp."""
A = 10.715 * (Rs + 100) ** (-0.515)
B = 5.44 * (Rs + 150) ** (-0.338)
return A * mu_od ** B
def viscosity_by_api(api_list, T_F, Rs):
"""For each API -> {mu_dead_cp, mu_live_cp, reduction_pct}."""
out = {}
for api in api_list:
mu_dead_cp = float(beggs_robinson_dead_oil(T_F, api))
mu_live_cp = float(beggs_robinson_live_oil(mu_dead_cp, Rs))
reduction_pct = (mu_dead_cp - mu_live_cp) / mu_dead_cp * 100.0
out[api] = {
"mu_dead_cp": mu_dead_cp,
"mu_live_cp": mu_live_cp,
"reduction_pct": reduction_pct,
}
return out
profile = viscosity_by_api(API_LIST, T_F=T_F, Rs=RS_AT_PB)
apis = list(profile.keys())
mu_live = [profile[a]["mu_live_cp"] for a in apis]
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(apis, mu_live, marker="o", color="darkred",
label="Live oil viscosity")
ax.set_xlabel("API gravity (°API)")
ax.set_ylabel("Live oil viscosity (cp)")
ax.set_title("OML 58 - live oil viscosity vs API gravity")
ax.grid(True, alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
print("viscosity profile:", profile)
lockCopying code is a Full Access feature.