Exercise 13.3
Water Disposal Cost - Net-Revenue Allocation
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 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 waterYour tasks:
- 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.
- 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)wherenet_percomes 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
linprogminimises, passc = -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).
- 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?
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
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.