Exerciseschevron_rightChapter 13chevron_right13.3
fitness_center

Exercise 13.3

Water Disposal Cost - Net-Revenue Allocation

Level 2
Chapter 13: Production Optimization
descriptionProblem

A field produces 4,000 bbl/day of water that must be disposed at $2.50/bbl. Write a function that calculates net revenue (oil revenue minus water disposal cost minus operating cost) and optimize the allocation to maximize net revenue instead of gross oil.

---

The chapter's linear program maximised barrels of oil. But a barrel of oil is not pure profit: every STB carries an operating cost, and on high-water-cut wells you also pay to truck away the produced water. On OML 58 the disposal contract runs 2.50/bblofwater,andat452.50/bbl of water, and at 45% water cut that surcharge can quietly eat a well's margin. So we re-solve the same 5-well allocation, but now the objective is net revenue (/day), not oil rate.

The verified 5-well dataset, the facility limits, and the three constraint coefficients are embedded for you exactly as they appear in the chapter. Do not re-derive the physics or the constraint matrix. The constants are:

MAX_OIL = [1200, 800, 600, 1500, 900]   STB/d   (per-well max oil rate)
GOR     = [450, 1200, 300, 800, 600]     scf/STB
WC      = [0.15, 0.05, 0.45, 0.10, 0.30] water-cut fraction
OP_COST = [12, 18, 8, 15, 10]            $/STB oil
MAX_LIQUID = 3000  bbl/d,  MAX_GAS = 2.0 MMscf/d,  MAX_WATER = 300 bbl/d
OIL_PRICE = 75.0 $/bbl,    WATER_DISPOSAL = 2.50 $/bbl water

Your tasks:

  1. Write net_revenue_per_stb(price, op_cost, wc, disposal):
  • Return an array of per-well net revenue per STB of oil:

price - op_cost_i - disposal * (wc_i / (1 - wc_i)).

  • The wc/(1 - wc) term is the barrels of water produced per STB of oil,

so disposal * wc/(1 - wc) is the disposal cost charged to each oil barrel.

  1. Write

optimize_net_revenue(max_oil, gor, wc, op_cost, price, disposal, max_liquid, max_gas, max_water):

  • Maximise sum(net_per_i * rate_i) where net_per comes from task 1.
  • Use the chapter's three facility constraints, with

A_ub = np.array([1/(1 - wc), gor/1e6, wc/(1 - wc)]) and b_ub = [max_liquid, max_gas, max_water], and bounds 0 <= rate_i <= max_oil_i.

  • Because linprog minimises, pass c = -net_revenue_per_stb(...).
  • Return (rates, total_net_revenue, total_oil) in that order, where

total_net_revenue is the optimised net revenue in $/day (a positive number) and total_oil = sum(rates).

  1. Build the two output values the tests read:

``python net_per = net_revenue_per_stb(OIL_PRICE, OP_COST, WC, WATER_DISPOSAL) rates_net, total_net, total_oil = optimize_net_revenue( MAX_OIL, GOR, WC, OP_COST, OIL_PRICE, WATER_DISPOSAL, MAX_LIQUID, MAX_GAS, MAX_WATER) ``

> Think about it: well A-03 carries the field's worst water cut (45%) yet has > the highest net revenue per STB, because its $8/STB operating cost is the > cheapest. Net economics, not water cut alone, decides who gets choked. As the > disposal tariff climbs, watch the mean pressure-style invariant of this > problem: the optimal rates barely move (water still binds at 300 bbl/d), so > the dollar value falls but the barrels stay put. Why would raising the disposal > cost lower total net revenue without changing which wells flow?

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
from scipy.optimize import linprog


# ── Verified OML-58 allocation dataset (do not edit) ─────────────────────
# Copied verbatim from chapter 13 (ch13-allocation-setup): the 5-well field
# data and facility limits that the production-allocation LP runs on.
MAX_OIL = np.array([1200.0, 800.0, 600.0, 1500.0, 900.0])  # STB/d  (per-well max oil rate)
GOR     = np.array([450.0, 1200.0, 300.0, 800.0, 600.0])   # scf/STB
WC      = np.array([0.15, 0.05, 0.45, 0.10, 0.30])         # water-cut fraction
OP_COST = np.array([12.0, 18.0, 8.0, 15.0, 10.0])          # $/STB oil

MAX_LIQUID = 3000.0   # facility liquid limit, bbl/d
MAX_GAS    = 2.0      # gas-handling limit, MMscf/d
MAX_WATER  = 300.0    # water-disposal limit, bbl/d

OIL_PRICE      = 75.0   # $/bbl oil
WATER_DISPOSAL = 2.50   # $/bbl water (book value)


def net_revenue_per_stb(price, op_cost, wc, disposal):
    """Per-well net revenue per STB of oil.

    Returns price - op_cost_i - disposal * (wc_i / (1 - wc_i)).
    The wc/(1 - wc) term is barrels of water produced per STB of oil, so
    disposal * wc/(1 - wc) is the disposal cost charged to each oil barrel.
    """
    return price - op_cost - disposal * (wc / (1.0 - wc))


def optimize_net_revenue(max_oil, gor, wc, op_cost, price, disposal,
                         max_liquid, max_gas, max_water):
    """Maximise total net revenue ($/day) over the 5-well facility constraints.

    Objective: maximise sum(net_per_i * rate_i), where net_per comes from
    net_revenue_per_stb(...). Subject to the chapter's three constraints
    (liquid via 1/(1-wc), gas via gor/1e6, water via wc/(1-wc)) and bounds
    0 <= rate_i <= max_oil_i.

    Returns (rates, total_net_revenue, total_oil).
    """
    net = net_revenue_per_stb(price, op_cost, wc, disposal)
    c = -net                       # linprog minimises, so negate
    A_ub = np.array([1.0 / (1.0 - wc), gor / 1e6, wc / (1.0 - wc)])
    b_ub = np.array([max_liquid, max_gas, max_water])
    bounds = [(0, m) for m in max_oil]
    res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
    rates = res.x
    total_net_revenue = -res.fun
    total_oil = float(np.sum(rates))
    return rates, total_net_revenue, total_oil


net_per = net_revenue_per_stb(OIL_PRICE, OP_COST, WC, WATER_DISPOSAL)

rates_net, total_net, total_oil = optimize_net_revenue(
    MAX_OIL, GOR, WC, OP_COST, OIL_PRICE, WATER_DISPOSAL,
    MAX_LIQUID, MAX_GAS, MAX_WATER)

print("net revenue per STB ($/STB):", np.round(net_per, 4))
print("optimal rates (STB/d):", np.round(rates_net, 2))
print("total oil (STB/d):", round(total_oil, 2))
print("total net revenue ($/d):", round(total_net, 2))

lockCopying code is a Full Access feature.