Exerciseschevron_rightChapter 13chevron_right13.10
fitness_center

Exercise 13.10

Full Field Optimization Report - Constrained Oil & Profit Allocation

Level 3
Chapter 13: Production Optimization
descriptionProblem

Build a complete field optimization for 8 wells with realistic parameters. Include: optimal rate allocation with facility constraints, gas lift optimization, a surveillance dashboard for each well, and a monthly re-optimization schedule. Present the results as a report with tables and plots.

---

This is the capstone for the optimization chapter. You run a full-field allocation study on an 8-well Niger Delta field (OML framing), composing the chapter's linear-programming machinery into a single report. The plots and dashboards the book mentions are yours to enjoy, but the graded core is the two allocation totals: max-oil and max-profit.

The field has eight wells, each with a max oil rate, gas-oil ratio (GOR), water cut (WC), and operating cost. Three shared facilities cap the whole field: total liquid handling, gas processing, and water disposal. The constraint-coefficient structure is the verified one from the chapter: for each well the liquid load is 1/(1-WC), the gas load is GOR/1e6 (MMscf per STB of oil), and the water load is WC/(1-WC). Do not re-derive or "simplify" it.

The embedded constants (do not edit them):

WELL_NAMES = ['A-01','A-02','A-03','B-01','B-02','B-03','C-01','C-02']
MAX_OIL = np.array([1200.,800.,600.,1500.,900.,1100.,700.,1000.])  # STB/d
GOR     = np.array([450.,1200.,300.,800.,600.,950.,350.,700.])     # scf/STB
WC      = np.array([0.15,0.05,0.45,0.10,0.30,0.20,0.50,0.25])      # frac
OP_COST = np.array([12.,18.,8.,15.,10.,14.,7.,11.])               # $/STB
MAX_LIQUID = 6000.0   # bbl/d
MAX_GAS    = 4.0      # MMscf/d
MAX_WATER  = 700.0    # bbl/d
OIL_PRICE  = 75.0     # $/bbl

Your tasks:

  1. Write the objective-agnostic allocation engine:

``python def field_allocate(objective_coeffs, max_oil, gor, wc, max_liquid, max_gas, max_water): """Maximize sum(objective_coeffs_i rate_i) subject to the three facility constraints and 0 <= rate_i <= max_oil_i. Return (rates, achieved_objective, total_oil).""" ` Build A_ub = np.array([1/(1-wc), gor/1e6, wc/(1-wc)]) and b_ub = np.array([max_liquid, max_gas, max_water]), negate the objective (c = -objective_coeffs) because linprog minimizes, set per-well bounds (0, max_oil_i), and solve with method='highs'. Return the optimal rates array, the achieved objective sum(objective_coeffs rates), and the total oil sum(rates)`.

  1. Write the report wrapper:

``python def field_report(max_oil, gor, wc, op_cost, price, max_liquid, max_gas, max_water): """Run two allocations and return a dict.""" ``

  • max-oil allocation: objective_coeffs = np.ones(...).
  • max-profit allocation: objective_coeffs = price - op_cost (net dollars

per STB of oil for each well).

  • Return a dict with keys:

'oil_rates', 'total_oil', 'profit_rates', 'total_profit', 'naive_oil' (= sum(max_oil)), and, for the max-oil case, 'gas_used', 'water_used', 'liquid_used' (recomputed from the oil allocation as sum(rate*gor/1e6), sum(rate*wc/(1-wc)), sum(rate/(1-wc))).

  1. At module scope, call the report and expose the three output variables the

tests read: ``python report = field_report(MAX_OIL, GOR, WC, OP_COST, OIL_PRICE, MAX_LIQUID, MAX_GAS, MAX_WATER) total_oil = report['total_oil'] total_profit = report['total_profit'] naive_oil = report['naive_oil'] ``

> Think about it: the naive plan (every well wide open) sums to 7,800 STB/d, > but the facilities can only carry about 4,613 STB/d; the constraints cost you > roughly 3,200 STB/d. Which facility actually binds here? (Check which of > gas_used / water_used / liquid_used sits exactly on its limit.) And why > does the max-profit plan land on the same total oil as the max-oil plan > for this particular field?

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 LP constraint structure from Chapter 13 (do not edit) ───────
# For an oil allocation x (STB/d per well), the chapter's facility loads are:
#   liquid load per well = 1 / (1 - wc)        -> total liquid  = sum(x / (1-wc))
#   gas    load per well = gor / 1e6           -> total gas     = sum(x * gor/1e6)  [MMscf/d]
#   water  load per well = wc / (1 - wc)       -> total water   = sum(x * wc/(1-wc))
# linprog MINIMIZES, so to maximize an objective we pass c = -objective_coeffs.
# (These are the exact coefficients used in the chapter's max-oil allocation.)


# ── 8-well Niger Delta field constants (do not edit) ─────────────────────
WELL_NAMES = ['A-01', 'A-02', 'A-03', 'B-01', 'B-02', 'B-03', 'C-01', 'C-02']
MAX_OIL = np.array([1200., 800., 600., 1500., 900., 1100., 700., 1000.])  # STB/d
GOR     = np.array([450., 1200., 300., 800., 600., 950., 350., 700.])     # scf/STB
WC      = np.array([0.15, 0.05, 0.45, 0.10, 0.30, 0.20, 0.50, 0.25])      # frac
OP_COST = np.array([12., 18., 8., 15., 10., 14., 7., 11.])                # $/STB
MAX_LIQUID = 6000.0   # bbl/d liquid handling
MAX_GAS    = 4.0      # MMscf/d gas processing
MAX_WATER  = 700.0    # bbl/d water disposal
OIL_PRICE  = 75.0     # $/bbl


def field_allocate(objective_coeffs, max_oil, gor, wc, max_liquid, max_gas, max_water):
    """Maximize sum(objective_coeffs_i * rate_i) subject to the three facility
    constraints and 0 <= rate_i <= max_oil_i.
    Return (rates, achieved_objective, total_oil).
    """
    objective_coeffs = np.asarray(objective_coeffs, float)
    # Verified Chapter-13 constraint-coefficient structure:
    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])
    c = -objective_coeffs               # linprog minimizes -> negate to maximize
    bounds = [(0, m) for m in max_oil]  # 0 <= rate_i <= max_oil_i
    res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method='highs')
    rates = res.x
    achieved = float(np.dot(objective_coeffs, rates))
    total_oil = float(np.sum(rates))
    return rates, achieved, total_oil


def field_report(max_oil, gor, wc, op_cost, price, max_liquid, max_gas, max_water):
    """Run two allocations (max-oil and max-profit) and return a report dict.

    Keys: 'oil_rates','total_oil','profit_rates','total_profit','naive_oil',
          'gas_used','water_used','liquid_used' (facility usage for the max-oil case).
    """
    n = len(max_oil)

    # Allocation 1: maximize total oil (every well counts equally).
    oil_rates, _, total_oil = field_allocate(
        np.ones(n), max_oil, gor, wc, max_liquid, max_gas, max_water)

    # Allocation 2: maximize profit (net $/STB = price - operating cost).
    profit_coeffs = price - op_cost
    profit_rates, total_profit, _ = field_allocate(
        profit_coeffs, max_oil, gor, wc, max_liquid, max_gas, max_water)

    naive_oil = float(np.sum(max_oil))

    # Facility usage recomputed from the max-oil allocation.
    gas_used = float(np.sum(oil_rates * gor / 1e6))
    water_used = float(np.sum(oil_rates * wc / (1.0 - wc)))
    liquid_used = float(np.sum(oil_rates / (1.0 - wc)))

    return dict(
        oil_rates=oil_rates,
        total_oil=total_oil,
        profit_rates=profit_rates,
        total_profit=total_profit,
        naive_oil=naive_oil,
        gas_used=gas_used,
        water_used=water_used,
        liquid_used=liquid_used,
    )


report = field_report(MAX_OIL, GOR, WC, OP_COST, OIL_PRICE,
                      MAX_LIQUID, MAX_GAS, MAX_WATER)
total_oil = report['total_oil']
total_profit = report['total_profit']
naive_oil = report['naive_oil']

print("FULL FIELD OPTIMIZATION REPORT")
print(f"  Naive total (all wells wide open): {naive_oil:,.0f} STB/d")
print(f"  Constrained max-oil total:         {total_oil:,.1f} STB/d")
print(f"  Constrained max-profit:            ${total_profit:,.0f}/d")
print(f"  Facility usage (max-oil): liquid {report['liquid_used']:,.0f} / {MAX_LIQUID:,.0f},"
      f" gas {report['gas_used']:.3f} / {MAX_GAS}, water {report['water_used']:,.0f} / {MAX_WATER:,.0f}")

lockCopying code is a Full Access feature.