Exercise 12.2
Vogel IPR from Test Data - Back-Solving Absolute Open Flow
A well test gives a flow rate of 1,200 STB/day at a flowing bottomhole pressure of 2,400 psi. The reservoir pressure is 4,200 psi and the bubble point is 4,200 psi. Since , the reservoir is saturated and Vogel's equation applies across the entire drawdown range. Using Vogel's equation, calculate qo_max and plot the full IPR curve.
---
A single well test pins one point on the inflow performance curve: at a flowing bottomhole pressure of 2,400 psi the well delivered 1,200 STB/day. The average reservoir pressure is 4,200 psi and, crucially, the bubble point is also 4,200 psi. Because Pb == Pe, the reservoir is saturated: gas comes out of solution the instant you draw the well down at all, so Vogel's two-phase equation applies across the entire drawdown range (there is no single-phase linear region above the bubble point to splice in).
Vogel's dimensionless IPR is
q_o / qo_max = 1 - 0.2*(Pwf/Pe) - 0.8*(Pwf/Pe)**2The well test gives you q_o at one Pwf; the only unknown is the absolute open flow potential qo_max (the rate the well would make if you could pull Pwf all the way to zero). Rearranging Vogel for qo_max lets you anchor the whole curve to that one measured point.
The verified vogel_ipr(Pe, qo_max, Pwf_array) function is embedded for you. do not modify it or re-derive the correlation. The constants are (verbatim from book Exercise 12.2): Q_TEST = 1200.0 STB/day, PWF_TEST = 2400.0 psi, PE = 4200.0 psi.
Your tasks:
- Write
qo_max_from_test(Pe, q_test, pwf_test)that back-solves Vogel's
equation for the absolute open flow potential:
``python qo_max = q_test / (1 - 0.2*(pwf_test/Pe) - 0.8*(pwf_test/Pe)**2) ``
- Call it on the test point to get the scalar
qo_max:
qo_max = qo_max_from_test(PE, Q_TEST, PWF_TEST).
- Build the full IPR curve over the drawdown range:
PWF_CURVE = np.linspace(0, PE, 200) then q_curve = vogel_ipr(PE, qo_max, PWF_CURVE).
- Read off two reference rates the tests will check:
q_at_zero = float(vogel_ipr(PE, qo_max, [0.0])[0]), the rate atPwf = 0
(this must equal qo_max exactly).
q_at_half = float(vogel_ipr(PE, qo_max, [PE/2])[0]), the rate at half the
reservoir pressure.
The exact names the tests read are: vogel_ipr, qo_max_from_test, qo_max, q_curve, q_at_zero, q_at_half.
> Think about it: at Pwf = 0 Vogel returns exactly qo_max, and at > Pwf = Pe it returns exactly 0. But at Pwf = Pe/2 the rate is not half of > qo_max; it comes out higher (~1,345 STB/day vs. 961). That bulge above the > straight line connecting the two endpoints is the two-phase concavity: Vogel's > curve is concave because liberated gas only chokes the inflow once you draw the > pressure down hard. Why does back-solving from one test point automatically > reproduce that same test rate when you plug qo_max back into vogel_ipr?
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
# ── Verified Vogel IPR (do not edit) ─────────────────────────────────────
def vogel_ipr(Pe, qo_max, Pwf_array):
"""
Vogel's IPR for solution-gas drive reservoirs.
Parameters
----------
Pe : float
Average reservoir pressure (psi).
qo_max : float
Absolute open flow potential (STB/day).
Pwf_array : array-like
Flowing bottomhole pressures (psi).
Returns
-------
numpy.ndarray
Oil flow rates (STB/day).
"""
Pwf = np.asarray(Pwf_array)
ratio = Pwf / Pe
return qo_max * (1 - 0.2 * ratio - 0.8 * ratio**2)
# ── Book Exercise 12.2 constants (do not edit) ───────────────────────────
Q_TEST = 1200.0 # well-test oil rate, STB/day
PWF_TEST = 2400.0 # flowing bottomhole pressure at the test, psi
PE = 4200.0 # average reservoir pressure, psi
# Note: the bubble point equals PE (4200 psi), so the reservoir is SATURATED
# and Vogel's equation applies across the whole drawdown range.
def qo_max_from_test(Pe, q_test, pwf_test):
"""Back-solve Vogel's IPR for the absolute open flow potential qo_max.
Vogel: q_test / qo_max = 1 - 0.2*(pwf_test/Pe) - 0.8*(pwf_test/Pe)**2
so qo_max = q_test / (1 - 0.2*(pwf_test/Pe) - 0.8*(pwf_test/Pe)**2)
"""
ratio = pwf_test / Pe
return q_test / (1 - 0.2 * ratio - 0.8 * ratio**2)
qo_max = qo_max_from_test(PE, Q_TEST, PWF_TEST)
PWF_CURVE = np.linspace(0, PE, 200)
q_curve = vogel_ipr(PE, qo_max, PWF_CURVE)
q_at_zero = float(vogel_ipr(PE, qo_max, [0.0])[0])
q_at_half = float(vogel_ipr(PE, qo_max, [PE / 2])[0])
print("qo_max (STB/day):", qo_max)
print("q at Pwf=0 (STB/day):", q_at_zero)
print("q at Pwf=Pe/2 (STB/day):", q_at_half)
lockCopying code is a Full Access feature.