Exerciseschevron_rightChapter 12chevron_right12.2
fitness_center

Exercise 12.2

Vogel IPR from Test Data - Back-Solving Absolute Open Flow

Level 2
Chapter 12: Nodal Analysis
descriptionProblem

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 Pb=PeP_b = P_e, 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)**2

The 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:

  1. 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) ``

  1. Call it on the test point to get the scalar qo_max:

qo_max = qo_max_from_test(PE, Q_TEST, PWF_TEST).

  1. 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).

  1. Read off two reference rates the tests will check:
  • q_at_zero = float(vogel_ipr(PE, qo_max, [0.0])[0]), the rate at Pwf = 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?

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


# ── 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.