Exerciseschevron_rightChapter 13chevron_right13.1
fitness_center

Exercise 13.1

Basic Rate Allocation - Pipeline-Constrained Oil & Profit LP

Level 1
Chapter 13: Production Optimization
descriptionProblem

Three wells with maximum rates of 1,000, 800, and 1,500 STB/day share a pipeline with 2,500 STB/day capacity. Each well has operating costs of 10,10, 14, and $8 per barrel respectively. Formulate and solve the allocation to maximize oil production, then separately to maximize profit.

---

Three wells on OML 58 feed one shared export pipeline. Each well has a maximum oil rate and a per-barrel operating cost, and the pipeline can only move 2500 STB/d total. You will solve the same allocation two different ways with scipy.optimize.linprog: once to maximize barrels, once to maximize dollars. And see why they disagree.

The three-well system constants are embedded for you (do not edit them):

MAX_OIL = np.array([1000.0, 800.0, 1500.0])   # STB/d per well
OP_COST = np.array([10.0, 14.0, 8.0])          # $/STB operating cost per well
PIPELINE_BPD = 2500.0                           # shared pipeline capacity, STB/d
OIL_PRICE = 75.0                                # $/bbl (house value, same as 13.9)

Every barrel is bounded by 0 <= rate_i <= MAX_OIL[i], and the whole field is bounded by sum(rates) <= PIPELINE_BPD. Both functions build that same linear program; only the objective changes.

Your tasks:

  1. Write allocate_max_oil(max_oil, pipeline):
  • Maximize total oil subject to sum(rates) <= pipeline and the per-well

bounds. Because linprog minimizes, use c = -np.ones(n).

  • Build A_ub = [[1, 1, ..., 1]], b_ub = [pipeline],

bounds = [(0, m) for m in max_oil], and call linprog(..., method='highs').

  • Return the tuple (rates, total_oil) where rates = result.x and

total_oil = float(result.x.sum()).

  1. Write allocate_max_profit(max_oil, op_cost, price, pipeline):
  • The per-barrel margin is margin = price - op_cost. Maximize

sum(margin_i * rate_i), so c = -margin. Use the same A_ub, b_ub, and bounds as task 1.

  • Return the tuple (rates, total_profit) where rates = result.x and

total_profit = float(np.sum(margin * rates)).

  1. After defining both, assign the module-level output variables the tests read:
rates_oil, total_oil = allocate_max_oil(MAX_OIL, PIPELINE_BPD)
rates_profit, total_profit = allocate_max_profit(MAX_OIL, OP_COST, OIL_PRICE, PIPELINE_BPD)

> Think about it: the max-oil solution just fills the pipeline to 2500 > STB/d. Any split that sums to 2500 is equally good, so the barrels are fixed > but the per-well rates are not unique. The max-profit solution is different: > with margins price - cost = [65, 61, 67], the cheapest-margin well (well 2, > the $14 one) is the first to drop to zero, and the pipeline fills with the two > high-margin wells. Why does optimizing dollars instead of barrels change which > wells you choke back?

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


# ── OML-58 three-well allocation system (do not edit) ────────────────────
# The LP pattern below mirrors the chapter's ch13-linprog cell: linprog
# minimizes, so the objective is negated, the pipeline cap is one A_ub row,
# and the per-well maxima are box bounds.
MAX_OIL = np.array([1000.0, 800.0, 1500.0])   # STB/d per well
OP_COST = np.array([10.0, 14.0, 8.0])          # $/STB operating cost per well
PIPELINE_BPD = 2500.0                           # shared pipeline capacity, STB/d
OIL_PRICE = 75.0                                # $/bbl (house value, same as 13.9)


def allocate_max_oil(max_oil, pipeline):
    """Maximize total oil through the pipeline.

    Returns (rates, total_oil):
      rates     = per-well STB/d array (result.x) that maximizes total oil
                  subject to sum(rates) <= pipeline and 0 <= rate_i <= max_oil_i
      total_oil = float(rates.sum())
    """
    n = len(max_oil)
    c = -np.ones(n)                       # minimize -> negate the oil objective
    A_ub = [[1.0] * n]                    # sum(rates) ...
    b_ub = [pipeline]                     # ... <= pipeline
    bounds = [(0, m) for m in max_oil]    # 0 <= rate_i <= max_oil_i
    result = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
    return result.x, float(result.x.sum())


def allocate_max_profit(max_oil, op_cost, price, pipeline):
    """Maximize total profit through the pipeline.

    Returns (rates, total_profit):
      rates        = per-well STB/d array (result.x) that maximizes
                     sum((price - op_cost_i) * rate_i) under the same
                     pipeline + bound constraints
      total_profit = float(sum(margin * rates))
    """
    n = len(max_oil)
    margin = price - op_cost              # per-barrel profit margin
    c = -margin                           # minimize -> negate the profit objective
    A_ub = [[1.0] * n]
    b_ub = [pipeline]
    bounds = [(0, m) for m in max_oil]
    result = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
    rates = result.x
    return rates, float(np.sum(margin * rates))


rates_oil, total_oil = allocate_max_oil(MAX_OIL, PIPELINE_BPD)
rates_profit, total_profit = allocate_max_profit(MAX_OIL, OP_COST, OIL_PRICE, PIPELINE_BPD)

print("max-oil:    total_oil =", total_oil, "  rates =", rates_oil)
print("max-profit: total_profit =", total_profit, "  rates =", rates_profit)

lockCopying code is a Full Access feature.