Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Stochastic Programming

This notebook was prepared by Jialu Wang and revised by Maddie Watson at the University of Notre Dame.

# Imports
import sys

if "google.colab" in sys.modules:
    !wget "https://raw.githubusercontent.com/ndcbe/optimization/main/notebooks/helper.py"
    import helper

    helper.easy_install()
else:
    sys.path.insert(0, "../")
    import helper
helper.set_plotting_style()
Matplotlib is building the font cache; this may take a moment.

Farmer’s example: one model, reused

Read the physical balances, connect them to Pyomo Blocks, and analyze complete solution tables. The SAA, CVaR, and zero-VSS pages reuse this implementation. Further examples retain the optional uniform-yield, dependence, and profit-floor studies.

Textbook data: Birge and Louveaux (2011), 2nd ed., §1.1a–c, pp. 4–11.

The textbook farmer example in Birge and Louveaux (2011), 2nd ed., §1.1a–c, pp. 4–11, uses 500 acres and three crops. Wheat and corn must supply 200 and 240 tons of feed; shortages can be purchased and surplus sold. Sugar beets have a premium-price sales cap of 6000 tons.

WheatCornSugar Beets
Yield (T/acre)2.5320
Planting cost ($/acre)150230260
Selling price ($/T)17015036 under 6000 T, 10 above 6000 T
Purchase price ($/T)238210–
Minimum requirement (T)200240–

Total available land: 500 acres

To help the farmer make up his mind, we can set up the following model. Let

  • x1x_1 = acres of land devoted to wheat,

  • x2x_2 = acres of land devoted to corn,

  • x3x_3 = acres of land devoted to sugar beets,

  • w1w_1 = tons of wheat sold,

  • y1y_1 = tons of wheat purchased,

  • w2w_2 = tons of corn sold,

  • y2y_2 = tons of corn purchased,

  • w3w_3 = tons of sugar beets sold at the favorable price,

  • w4w_4 = tons of sugar beets sold at the unfavorable price.

Farmer's problem: acres allocated to each crop, with required animal feed and quantities sold to or purchased from the market.

Deterministic mean-yield model

First solve at the specified mean yields. Later, the same model is solved separately at each realized yield to compute the perfect-information benchmark:

min⁡150x1+230x2+260x3⏟planting costs+238y1−170w1⏟wheat purchases less sales+210y2−150w2⏟corn purchases less sales −36w3−10w4⏟sugar beet saless.t.x1+x2+x3≤500,(plant up to 500 acres)2.5x1+y1−w1≥200,(satisfy wheat demand)3x2+y2−w2≥240,(satisfy corn demand)w3+w4≤20x3,(beet sales cannot exceed production)w3≤6000,(cap beet sales at favorable price)x1,x2,x3,y1,y2,w1,w2,w3,w4≥0.\begin{align*} \min \quad & \underbrace{150x_1 + 230x_2 + 260x_3}_{\text{planting costs}} \underbrace{+ 238y_1 - 170w_1}_{\text{wheat purchases less sales}} \\ & \underbrace{+ 210y_2 - 150w_2}_\text{corn purchases less sales} ~ \underbrace{- 36w_3 - 10w_4}_{\text{sugar beet sales}} \\ \text{s.t.} \quad & x_1 + x_2 + x_3 \leq 500, \quad \text{(plant up to 500 acres)} \\ & 2.5x_1 + y_1 - w_1 \geq 200, \quad \text{(satisfy wheat demand)}\\ & 3x_2 + y_2 - w_2 \geq 240, \quad \text{(satisfy corn demand)}\\ & w_3 + w_4 \leq 20x_3, \quad \text{(beet sales cannot exceed production)}\\ & w_3 \leq 6000, \quad \text{(cap beet sales at favorable price)}\\ & x_1, x_2, x_3, y_1, y_2, w_1, w_2, w_3, w_4 \geq 0. \end{align*}
Two linear beet sales channels produce a revenue kink at the premium quota.

With bb tons available to sell, optimal beet revenue is 36min⁡(b,6000)+10max⁡(b−6000,0)36\min(b,6000)+10\max(b-6000,0). The separate sales variables and premium bound represent this concave piecewise-linear revenue within an LP; binary variables are unnecessary for these prices.

Declare shared decisions and scenario Blocks

A Block groups variables, constraints, and expressions. The root model owns acreage; each scenario Block owns purchases and sales and refers to the same acreage variables. One scenario gives the deterministic LP. More scenarios reuse the model without copying planting decisions.

The builder and its helpers below are defined before the first solve. Monetary objectives use profit maximization; this is equivalent to minimizing net cost in the mathematical formulation above. Units are checked on the small models.

Root acreage is shared; scenario Blocks own trades and balances.
import numpy as np
import pyomo.environ as pyo
from pyomo.environ import units as u
from pyomo.util.check_units import assert_units_consistent

u.load_definitions_from_strings(["USD = [currency]"])
USD_PER_ACRE = u.USD / u.acre
USD_PER_TON = u.USD / u.metric_ton
TON_PER_ACRE = u.metric_ton / u.acre
CROPS = ["WHEAT", "CORN", "BEETS"]
FEED_CROPS = ["WHEAT", "CORN"]
SALE_TYPES = ["WHEAT", "CORN", "BEETS_FAVORABLE", "BEETS_UNFAVORABLE"]
MEAN_YIELD = dict(zip(CROPS, [2.5, 3.0, 20.0]))  # T/acre
PLANT_COST = dict(zip(CROPS, [150.0, 230.0, 260.0]))  # $/acre
BUY_PRICE = dict(zip(FEED_CROPS, [238.0, 210.0]))  # $/T
SELL_PRICE = dict(zip(SALE_TYPES, [170.0, 150.0, 36.0, 10.0]))  # $/T
FEED_NEED = dict(zip(FEED_CROPS, [200.0, 240.0]))  # T
TOTAL_LAND, BEET_QUOTA = 500.0, 6000.0  # acres, T
def build_farmer(
    scenarios, weights=None, plant_cost=None, risk=None, check_units=False
):
    """Return a fresh farmer model; scenarios contain yields and optional prices.

    scenarios: nonempty list of dictionaries; 'yield' maps crops to T/acre.
    Optional 'buy' and 'sell' mappings give $/T. weights sum to one.
    plant_cost maps crops to $/acre. risk selects an optional risk policy.
    """
    n = len(scenarios)
    if n == 0:
        raise ValueError("At least one scenario is required")
    w = np.full(n, 1 / n) if weights is None else np.asarray(weights, float)
    if w.shape != (n,) or not np.all(np.isfinite(w)) or np.any(w <= 0):
        raise ValueError("Retained scenarios need positive finite probabilities")
    if not np.isclose(w.sum(), 1.0):
        raise ValueError("Scenario probabilities must sum to one")
    costs = PLANT_COST if plant_cost is None else plant_cost
    m = pyo.ConcreteModel()
    m.CROPS = pyo.Set(initialize=CROPS)
    m.FEED_CROPS = pyo.Set(initialize=FEED_CROPS)
    m.SALE_TYPES = pyo.Set(initialize=SALE_TYPES)
    m.SCENARIOS = pyo.RangeSet(0, n - 1)
    m.weight = pyo.Param(m.SCENARIOS, initialize=dict(enumerate(w)))
    # Plant once, before learning the yields: no scenario index on acreage.
    m.acreage = pyo.Var(m.CROPS, domain=pyo.NonNegativeReals, units=u.acre)
    m.land_limit = pyo.Constraint(expr=sum(m.acreage.values()) <= TOTAL_LAND * u.acre)
    m.planting_cost = pyo.Expression(
        expr=sum(costs[c] * USD_PER_ACRE * m.acreage[c] for c in CROPS)
    )
    # The helper below attaches recourse to each Block, sharing root acreage.
    m.scenarios = pyo.Block(
        m.SCENARIOS, rule=lambda b, s: add_farmer_scenario(b, m, scenarios[s])
    )
    m.expected_profit = pyo.Expression(
        expr=sum(m.weight[s] * m.scenarios[s].profit for s in m.SCENARIOS)
    )
    m.objective = pyo.Objective(expr=m.expected_profit, sense=pyo.maximize)
    if risk:
        add_farmer_risk(m, risk)  # Optional tail-risk and downside policies.
    if check_units:
        assert_units_consistent(m)
    return m
def add_farmer_scenario(b, m, scenario):
    """Attach one scenario's recourse, using the root model's acreage."""
    crop_yield = scenario["yield"]
    buy = scenario.get("buy", BUY_PRICE)
    sell = scenario.get("sell", SELL_PRICE)
    b.purchases = pyo.Var(m.FEED_CROPS, domain=pyo.NonNegativeReals, units=u.metric_ton)
    b.sales = pyo.Var(m.SALE_TYPES, domain=pyo.NonNegativeReals, units=u.metric_ton)
    b.sales["BEETS_FAVORABLE"].setub(BEET_QUOTA)

    # Harvest + purchases - sales must cover animal feed in this scenario.
    @b.Constraint(m.FEED_CROPS)
    def feed_balance(block, c):
        return (
            crop_yield[c] * TON_PER_ACRE * m.acreage[c]
            + block.purchases[c]
            - block.sales[c]
            >= FEED_NEED[c] * u.metric_ton
        )

    b.beets_constraint = pyo.Constraint(
        expr=b.sales["BEETS_FAVORABLE"] + b.sales["BEETS_UNFAVORABLE"]
        <= crop_yield["BEETS"] * TON_PER_ACRE * m.acreage["BEETS"]
    )
    # Profit includes planting cost; weights summing to one count it once.
    b.profit = pyo.Expression(
        expr=sum(sell[k] * USD_PER_TON * b.sales[k] for k in SALE_TYPES)
        - sum(buy[c] * USD_PER_TON * b.purchases[c] for c in FEED_CROPS)
        - m.planting_cost
    )
farmer_solver = pyo.SolverFactory("appsi_highs")
assert farmer_solver.available(), "HiGHS is required for the rest of this notebook"


def solve_farmer(m):
    """Solve, refusing anything but a proven optimum."""
    results = farmer_solver.solve(m)
    assert pyo.check_optimal_termination(
        results
    ), f"Solve failed: termination={results.solver.termination_condition}"
    return m


def plan_of(m):
    """Stage 1 acreage as a plain array, in the order of CROPS."""
    return np.array([pyo.value(m.acreage[c]) for c in CROPS])
def grade_plan(plan, scenarios, weights=None, plant_cost=None):
    """Fix acreage in CROPS order; optimize trades for the supplied scenarios.

    Return expected profit and an array of scenario profits, both in dollars.
    """
    m = build_farmer(scenarios, weights, plant_cost)
    for crop, acres in zip(CROPS, plan):
        m.acreage[crop].fix(acres)
    solve_farmer(m)
    return (
        pyo.value(m.expected_profit),
        np.array([pyo.value(m.scenarios[i].profit) for i in m.SCENARIOS]),
    )
def correlated_scenario(multiplier):
    """One scenario of Section 1.1b: every crop scaled by the same factor."""
    return {"yield": {c: MEAN_YIELD[c] * multiplier for c in CROPS}}


# The three equally likely scenarios of Section 1.1b
THREE = [correlated_scenario(f) for f in (1.2, 1.0, 0.8)]

Shared helpers for the companion pages

The optional risk interface and complete-solution utilities below are also exported from this notebook. The introductory stochastic-programming model uses the default expected-profit objective; CVaR explains the risk formulation.

def add_farmer_cvar(m, risk):
    """Lower-tail profit CVaR: alpha is the confidence level."""
    w = m.weight
    n = len(m.SCENARIOS)
    # Rockafellar-Uryasev epigraph, B&L (9.8)-(9.10), p. 85, written for the
    # LOWER tail of profit: CVaR_a = max_nu { nu - E[(nu - profit)_+]/(1-a) }
    alpha = (risk.get("cvar") or risk.get("cvar_floor"))[0]
    m.nu = pyo.Var(domain=pyo.Reals, units=u.USD)
    m.shortfall = pyo.Var(m.SCENARIOS, domain=pyo.NonNegativeReals, units=u.USD)
    m.shortfall_con = pyo.Constraint(
        m.SCENARIOS,
        rule=lambda m, i: m.shortfall[i] >= m.nu - m.scenarios[i].profit,
    )
    m.cvar = m.nu - (1.0 / (1.0 - alpha)) * sum(w[i] * m.shortfall[i] for i in range(n))
    if "cvar_floor" in risk:
        m.cvar_con = pyo.Constraint(expr=m.cvar >= risk["cvar_floor"][1] * u.USD)
def add_farmer_risk(m, risk):
    """Attach the requested risk policy to a fresh farmer model."""
    w = m.weight
    n = len(m.SCENARIOS)
    m.del_component(m.objective)
    if "floor" in risk:
        # Exercise 7(b): a profit floor imposed in every scenario
        m.profit_floor = pyo.Constraint(
            m.SCENARIOS,
            rule=lambda m, i: m.scenarios[i].profit >= risk["floor"] * u.USD,
        )
    if "minimax" in risk:
        # B&L (9.15), p. 86, in profit form: maximize the worst scenario
        m.worst = pyo.Var(domain=pyo.Reals, units=u.USD)
        m.worst_con = pyo.Constraint(
            m.SCENARIOS, rule=lambda m, i: m.worst <= m.scenarios[i].profit
        )
    if "cvar" in risk or "cvar_floor" in risk:
        add_farmer_cvar(m, risk)
    if "downside" in risk:
        # Expected downside risk, B&L (5.1)-(5.3), p. 68
        target, level = risk["downside"]
        m.shortfall_below_target = pyo.Var(
            m.SCENARIOS, domain=pyo.NonNegativeReals, units=u.USD
        )
        m.downside_def = pyo.Constraint(
            m.SCENARIOS,
            rule=lambda m, i: m.shortfall_below_target[i]
            >= target * u.USD - m.scenarios[i].profit,
        )
        m.downside_limit = pyo.Constraint(
            expr=sum(w[i] * m.shortfall_below_target[i] for i in range(n))
            <= level * u.USD
        )

    if "minimax" in risk:
        m.objective = pyo.Objective(expr=m.worst, sense=pyo.maximize)
    elif "cvar" in risk:
        lam = risk["cvar"][1]
        m.objective = pyo.Objective(
            expr=(1 - lam) * m.expected_profit + lam * m.cvar, sense=pyo.maximize
        )
    else:
        m.objective = pyo.Objective(expr=m.expected_profit, sense=pyo.maximize)
def synthetic_farmer_yields(n, seed):
    """Draw synthetic joint yields, not observed agricultural data.

    Weather multiplier = 0.65 + 0.70 * Beta(2, 2), shared by all crops.
    Independent crop deviations are Uniform(-0.08, 0.08).
    All crop multipliers lie in [0.57, 1.43], with population mean 1.
    Dependence comes from shared weather; draws across scenarios are iid.
    """
    rng = np.random.default_rng(seed)
    weather = 0.65 + 0.70 * rng.beta(2, 2, size=n)
    multipliers = weather[:, None] + rng.uniform(-0.08, 0.08, size=(n, 3))
    return [{"yield": dict(zip(CROPS, row * np.array(list(MEAN_YIELD.values()))))}
            for row in multipliers]


SYNTHETIC_SEED = 20260927
TWENTY = synthetic_farmer_yields(20, SYNTHETIC_SEED)


def solution_record(m):
    """Full solved policy and recourse, with units explicit in field names."""
    return {
        "acreage": {c: float(pyo.value(m.acreage[c])) for c in CROPS},
        "expected_profit": float(pyo.value(m.expected_profit)),
        "scenarios": [
            {"id": int(i) + 1,
             "probability": float(pyo.value(m.weight[i])),
             "purchases_tons": {c: float(pyo.value(m.scenarios[i].purchases[c]))
                                for c in FEED_CROPS},
             "sales_tons": {c: float(pyo.value(m.scenarios[i].sales[c]))
                            for c in SALE_TYPES},
             "profit": float(pyo.value(m.scenarios[i].profit))}
            for i in m.SCENARIOS],
    }


def evaluate_solution(plan, scenarios, weights=None, plant_cost=None):
    """Return all efficient trades for fixed acreage, not arbitrary risk-solve trades."""
    m = build_farmer(scenarios, weights=weights, plant_cost=plant_cost)
    for c, acres in zip(CROPS, plan):
        m.acreage[c].fix(float(acres))
    return solution_record(solve_farmer(m))


def analytic_profits(plan, scenarios, plant_cost=None):
    """Independent closed-form recourse for these farmer data (dollars).

    Valid when sales prices are nonnegative, purchase prices exceed selling
    prices, and favorable beet revenue exceeds over-quota revenue. Feed
    shortages are bought, surpluses sold; beet sales fill the premium quota first.
    """
    costs = PLANT_COST if plant_cost is None else plant_cost
    acreage = dict(zip(CROPS, plan))
    planting = sum(costs[c] * acreage[c] for c in CROPS)
    profits = []
    for s in scenarios:
        sell, buy = s.get("sell", SELL_PRICE), s.get("buy", BUY_PRICE)
        assert all(buy[c] > sell[c] >= 0 for c in FEED_CROPS)
        assert sell["BEETS_FAVORABLE"] >= sell["BEETS_UNFAVORABLE"] >= 0
        profit = -planting
        for c in FEED_CROPS:
            surplus = s["yield"][c] * acreage[c] - FEED_NEED[c]
            profit += sell[c] * max(surplus, 0) - buy[c] * max(-surplus, 0)
        beets = s["yield"]["BEETS"] * acreage["BEETS"]
        profit += sell["BEETS_FAVORABLE"] * min(beets, BEET_QUOTA)
        profit += sell["BEETS_UNFAVORABLE"] * max(beets - BEET_QUOTA, 0)
        profits.append(profit)
    return np.asarray(profits)


def lower_tail_profit(profits, alpha, weights=None):
    """Average the worst mass 1-alpha, including fractional mass at ties."""
    values = np.asarray(profits, dtype=float)
    if not 0 <= alpha < 1:
        raise ValueError("alpha must be in [0, 1)")
    w = np.full(len(values), 1 / len(values)) if weights is None else np.asarray(weights)
    if len(values) == 0 or w.shape != values.shape or np.any(w < 0) or not np.isclose(w.sum(), 1):
        raise ValueError("Use nonempty outcomes with probabilities summing to one")
    order = np.argsort(values)
    remaining, total = 1 - alpha, 0.0
    for i in order:
        mass = min(float(w[i]), remaining)
        total += mass * values[i]
        remaining -= mass
        if remaining <= 1e-14:
            break
    return total / (1 - alpha)

Known yields: solve, then interpret

build_farmer([correlated_scenario(1.0)]) creates the mean-yield LP. The same function receives a singleton list for each known-yield benchmark below.

Discuss: classify the problem. Why does the solution grow 300 acres of beets? Which resource limits the profit? The dual values report the local change in optimal profit per extra unit of the corresponding constraint bound.

mean_model = build_farmer([correlated_scenario(1.0)], check_units=True)
mean_model.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT)
solve_farmer(mean_model)
mean_plan = plan_of(mean_model)
print("Mean-yield acreage [wheat, corn, beets]:", mean_plan)
print(f"Profit: ${pyo.value(mean_model.expected_profit):,.2f}")
print(f"Land multiplier: ${mean_model.dual[mean_model.land_limit]:.2f}/acre")
for c in FEED_CROPS:
    con = mean_model.scenarios[0].feed_balance[c]
    print(f"Feed minimum multiplier, {c}: ${mean_model.dual[con]:.2f}/T")
assert np.allclose(mean_plan, [120, 80, 300])
assert np.isclose(pyo.value(mean_model.expected_profit), 118600)
Mean-yield acreage [wheat, corn, beets]: [120.  80. 300.]
Profit: $118,600.00
Land multiplier: $275.00/acre
Feed minimum multiplier, WHEAT: $-170.00/T
Feed minimum multiplier, CORN: $-168.33/T

The mean-yield model is an LP. Premium beet acreage reaches the sales quota; corn meets feed needs, and wheat takes the remaining land. The land multiplier is a local sensitivity, not a universal crop ranking. Crop rotation and shared labor constraints can change the allocation.

What if yields are known before planting?

Solve three separate problems: all crops at 120%, 100%, or 80% of mean yield. This assumes perfect information before planting.

Premium-quota acreage is inversely related to the known beet yield for the three benchmark optima.
perfect_models = [solve_farmer(build_farmer([scenario])) for scenario in THREE]
print("Known yield     wheat     corn    beets       profit ($)")
for label, model in zip(["above", "average", "below"], perfect_models):
    a = plan_of(model)
    print(
        f"{label:12s} {a[0]:8.1f} {a[1]:8.1f} {a[2]:8.1f}"
        f" {pyo.value(model.expected_profit):16,.2f}"
    )
Known yield     wheat     corn    beets       profit ($)
above           183.3     66.7    250.0       167,666.67
average         120.0     80.0    300.0       118,600.00
below           100.0     25.0    375.0        59,950.00

Analyze the results: why does lower beet yield lead to more beet acreage? Why does the bad-year solution buy corn? Which assumption makes these three planting plans unavailable to a farmer who must plant before observing yields?

One planting plan, three possible harvests

  • Stage 1: choose acreage before yield information.

  • Stage 2: buy and sell after observing the harvest.

  • Nonanticipativity: decisions use only information available at that time. One shared m.acreage enforces this structurally.

Each scenario has probability 1/3. The same build_farmer(THREE) function now creates three Blocks and maximizes expected profit.

Birge and Louveaux, §1.1b–c, pp. 6–11; §1.2, pp. 21, 25–26.

Stages describe information

A stage groups decisions that have the same information available. It need not be a fixed number of days. In this two-stage model the order is

x⟶Ξ=ξs⟶ys.\mathbf{x}\quad\longrightarrow\quad\boldsymbol{\Xi}=\boldsymbol{\xi}_s\quad\longrightarrow\quad\mathbf{y}_s.

The first-stage vector x\mathbf{x} is chosen before the uncertain yields are observed. Recourse ys\mathbf{y}_s is chosen afterward, subject to the acreage already planted. Planning for recourse means accounting for those future feasible actions when choosing x\mathbf{x} today. It does not mean waiting to choose every variable until the harvest is known.

Scenario trees: shared decisions precede branching; later decisions can differ after the corresponding information arrives.

Nonanticipativity requires identical decisions whenever scenarios have identical information histories at the decision time. All three farmer scenarios have the same history at planting, so they share acreage. After the harvest is observed, their trading decisions may differ. In a multistage tree, decisions remain shared at a common node and can separate only after the histories branch. See Birge and Louveaux, §1.2, pp. 21, 25–26, and §§2.3–2.4, pp. 57–60.

Random vectors, realizations, and scenarios

Ξ\boldsymbol{\Xi} denotes a random vector of uncertain data; ξ\boldsymbol{\xi} is one realized value. The joint law specifies which combinations occur and with what probabilities. Its support Θ⊆Rd\Theta\subseteq\mathbb{R}^d is the smallest closed set with probability one. The book writes Ξ\Xi for this support; here uppercase bold Ξ\boldsymbol{\Xi} identifies the random vector. We use Ω\Omega for the finite retained scenario indices below, whereas the book also uses Ω\Omega for an underlying sample space.

For a finite distribution, scenario s∈Ωs\in\Omega has realization ξs\boldsymbol{\xi}_s and probability ps>0p_s>0, with ∑sps=1\sum_s p_s=1. Zero-probability scenarios may be removed. A fair die has six outcomes, each of probability 1/61/6. For an event AA,

Pr⁡(Ξ∈A)=∑s:ξs∈Aps.\Pr(\boldsymbol{\Xi}\in A)=\sum_{s:\boldsymbol{\xi}_s\in A}p_s.

A probability density pΞp_{\boldsymbol{\Xi}} instead satisfies

pΞ(ξ)≥0,∫ΘpΞ(ξ) dξ=1,Pr⁡(Ξ∈A)=∫ApΞ(ξ) dξ.p_{\boldsymbol{\Xi}}(\boldsymbol{\xi})\ge0,\qquad \int_\Theta p_{\boldsymbol{\Xi}}(\boldsymbol{\xi})\,d\boldsymbol{\xi}=1,\qquad \Pr(\boldsymbol{\Xi}\in A)=\int_Ap_{\boldsymbol{\Xi}}(\boldsymbol{\xi})\,d\boldsymbol{\xi}.

A density value is not a probability at a point and can exceed one. For example, U∼Uniform⁡(0,1/2)U\sim\operatorname{Uniform}(0,1/2) has density 2 on that interval, total area 2(1/2)=12(1/2)=1, and Pr⁡(0≤U≤1/4)=2(1/4)=1/2\Pr(0\le U\le1/4)=2(1/4)=1/2; each individual point has probability zero.

Uniform density of height 2 on an interval of width one half; the shaded interval has probability one half.

For an integrable function ϕ\phi,

E[ϕ(Ξ)]={∑s∈Ωpsϕ(ξs),finite distribution,∫Θϕ(ξ)pΞ(ξ) dξ,distribution with a density.\mathbb{E}[\phi(\boldsymbol{\Xi})]= \begin{cases} \displaystyle\sum_{s\in\Omega}p_s\phi(\boldsymbol{\xi}_s),&\text{finite distribution},\\ \displaystyle\int_\Theta\phi(\boldsymbol{\xi})p_{\boldsymbol{\Xi}}(\boldsymbol{\xi})\,d\boldsymbol{\xi},&\text{distribution with a density}. \end{cases}

The weighted sum is exact when it includes the full finite law. A retained sample of a continuous law gives a discrete approximation; independent identically distributed sampling uses empirical weights 1/N1/N. The mean and covariance summarize a law but do not specify it. Likewise, independent draws across scenarios do not imply independent crops within a scenario: shared weather can correlate all three crop yields. The SAA guide supplies a declared joint generator and its complete 20-row sample. See Birge and Louveaux, §§2.1–2.2, pp. 55–57, and §1.1d, pp. 11–15.

General two-stage model and extensive form

A general finite-scenario net-cost minimization model is

min⁡x,{ys}s∈Ω∑s∈Ωpsf(x,ys,ξs)s.t.x∈X,g(x,ys,ξs)≤0,h(x,ys,ξs)=0,ys∈Ys,s∈Ω.\begin{aligned} \min_{\mathbf{x},\{\mathbf{y}_s\}_{s\in\Omega}}\quad &\sum_{s\in\Omega}p_s f(\mathbf{x},\mathbf{y}_s,\boldsymbol{\xi}_s)\\ \text{s.t.}\quad &\mathbf{x}\in X,\\ &\mathbf{g}(\mathbf{x},\mathbf{y}_s,\boldsymbol{\xi}_s)\le\mathbf{0},\quad \mathbf{h}(\mathbf{x},\mathbf{y}_s,\boldsymbol{\xi}_s)=\mathbf{0},\quad \mathbf{y}_s\in Y_s,\qquad s\in\Omega. \end{aligned}

Here x∈Rn1\mathbf{x}\in\mathbb{R}^{n_1} is shared, ys∈Rn2\mathbf{y}_s\in\mathbb{R}^{n_2} is scenario-specific, and X,YsX,Y_s include variable domains. Every positive-probability scenario has its own feasibility constraints; the probability weights do not relax those constraints. This f,g,hf,\mathbf{g},\mathbf{h} form allows nonlinear functions or integer domains. The expanded farmer model below is an LP because its coefficients are fixed once a scenario is specified and every variable is continuous.

The book’s classical linear fixed-recourse form makes the nested optimization explicit:

min⁡xcTx+E[Q(x,Ξ)]s.t.Ax=b,x≥0,Q(x,ξs)=min⁡ysqsTyss.t.Wys=hs−Tsx,ys≥0.\begin{aligned} \min_{\mathbf{x}}\quad&\mathbf{c}^{\mathsf T}\mathbf{x}+\mathbb E[Q(\mathbf{x},\boldsymbol{\Xi})]\\ \text{s.t.}\quad&A\mathbf{x}=\mathbf{b},\quad\mathbf{x}\ge\mathbf{0},\\ Q(\mathbf{x},\boldsymbol{\xi}_s)=\min_{\mathbf{y}_s}\quad&\mathbf{q}_s^{\mathsf T}\mathbf{y}_s\\ \text{s.t.}\quad&W\mathbf{y}_s=\mathbf{h}_s-T_s\mathbf{x},\quad\mathbf{y}_s\ge\mathbf{0}. \end{aligned}

WW is the recourse matrix, common to scenarios; “fixed recourse” does not require fixed recourse decisions. The data qs,hs,Ts\mathbf{q}_s,\mathbf{h}_s,T_s may depend on the realization. For a continuous law, recourse constraints hold almost surely, meaning outside a set of probability zero. For a retained finite law, each positive-probability row must be feasible. For a given x\mathbf{x}, infeasible recourse has value +∞+\infty in the net-cost convention. An expected-cost model also needs a well-defined finite expectation at the policies being compared.

Substitute the full finite law into the expectation and include each scenario’s recourse variables:

min⁡cTx+∑s∈ΩpsqsTyss.t.Ax=b,x≥0,Tsx+Wys=hs,ys≥0,s∈Ω.\begin{aligned} \min\quad&\mathbf{c}^{\mathsf T}\mathbf{x}+\sum_{s\in\Omega}p_s\mathbf{q}_s^{\mathsf T}\mathbf{y}_s\\ \text{s.t.}\quad&A\mathbf{x}=\mathbf{b},\quad\mathbf{x}\ge\mathbf{0},\\ &T_s\mathbf{x}+W\mathbf{y}_s=\mathbf{h}_s,\quad\mathbf{y}_s\ge\mathbf{0},\qquad s\in\Omega. \end{aligned}

This extensive form optimizes one planting decision and all contingent trading decisions together. It is equivalent to the nested recourse formulation when the finite scenario law is exact and the recourse optima exist. If a first-stage cost is included inside every fsf_s, then ∑spscTx=cTx\sum_s p_s\mathbf{c}^{\mathsf T}\mathbf{x}=\mathbf{c}^{\mathsf T}\mathbf{x}; it is counted once because weights sum to one.

One shared vector and linked scenario copies enforce the same planting choice.

An alternative implementation creates a copy xs\mathbf{x}_s per scenario and adds

xs=x,s∈Ω.\mathbf{x}_s=\mathbf{x},\qquad s\in\Omega.

Any feasible shared-vector solution gives feasible copies by setting each copy to x\mathbf{x}. Conversely, these equalities force every feasible copied solution to use that same vector, giving the same scenario constraints and objective. Thus the formulations are equivalent. Removing the equalities instead permits perfect-information planting and changes the information structure. build_farmer avoids the extra copies by referring to the root acreage in every Block. See Birge and Louveaux, §1.2, model (2.2), pp. 25–26; §2.4, pp. 59–60; and §3.1a, pp. 103–104.

Preserve dependence within a scenario

Common weather and independent crop yields have matching two-point marginals but different joint probabilities.

Both constructed laws give each crop a normalized yield of 0.8 or 1.2 with equal probability and mean one. Under common weather, both low yields occur with probability one half; under independence, with probability one quarter. Thus the means and marginals do not determine the joint law. The joint profit distribution and its tail can change. For this farmer’s additive, crop-separable expected recourse, holding all crop marginals fixed leaves the population risk-neutral objective unchanged; the full separability argument explains the required structure. Finite sampled marginals may still differ across runs.

min⁡150x1+230x2+260x3−13(170w1,1−238y1,1+150w2,1−210y2,1+36w3,1+10w4,1)−13(170w1,2−238y1,2+150w2,2−210y2,2+36w3,2+10w4,2)−13(170w1,3−238y1,3+150w2,3−210y2,3+36w3,3+10w4,3)s.t.scenario 1:x1+x2+x3≤500,3x1+y1,1−w1,1≥200,3.6x2+y2,1−w2,1≥240,w3,1+w4,1≤24x3,w3,1≤6000,scenario 2:2.5x1+y1,2−w1,2≥200,3x2+y2,2−w2,2≥240,w3,2+w4,2≤20x3,w3,2≤6000,scenario 3:2x1+y1,3−w1,3≥200,2.4x2+y2,3−w2,3≥240,w3,3+w4,3≤16x3,w3,3≤6000,x,y,w≥0.\begin{align*} \min \quad & 150x_1 + 230x_2 + 260x_3\\ & -\frac{1}{3}(170w_{1,1} - 238y_{1,1} + 150w_{2,1} - 210y_{2,1} + 36w_{3,1} + 10w_{4,1}) \\ & -\frac{1}{3}(170w_{1,2} - 238y_{1,2} + 150w_{2,2} - 210y_{2,2} + 36w_{3,2} + 10w_{4,2}) \\ & -\frac{1}{3}(170w_{1,3} - 238y_{1,3} + 150w_{2,3} - 210y_{2,3} + 36w_{3,3} + 10w_{4,3}) \\ \text{s.t.} \quad & \text{scenario 1:} \\ & x_1 + x_2 + x_3 \leq 500, \\ & 3x_1 + y_{1,1} - w_{1,1} \geq 200, \\ & 3.6x_2 + y_{2,1} - w_{2,1} \geq 240, \\ & w_{3,1} + w_{4,1} \leq 24x_3, \\ & w_{3,1} \leq 6000, \\ & \text{scenario 2:} \\ & 2.5x_1 + y_{1,2} - w_{1,2} \geq 200, \\ & 3x_2 + y_{2,2} - w_{2,2} \geq 240, \\ & w_{3,2} + w_{4,2} \leq 20x_3, \\ & w_{3,2} \leq 6000, \\ & \text{scenario 3:} \\ & 2x_1 + y_{1,3} - w_{1,3} \geq 200, \\ & 2.4x_2 + y_{2,3} - w_{2,3} \geq 240, \\ & w_{3,3} + w_{4,3} \leq 16x_3, \\ & w_{3,3} \leq 6000, \\ & \mathbf{x}, \mathbf{y}, \mathbf{w} \geq \mathbf{0}. \end{align*}
# Validation first: reproduce Birge & Louveaux Table 5, p. 8.
RP = solve_farmer(build_farmer(THREE))
rp_plan = plan_of(RP)
rp_expected = pyo.value(RP.expected_profit)
rp_profits = np.array([pyo.value(RP.scenarios[i].profit) for i in RP.SCENARIOS])

print(
    f"acres  wheat {rp_plan[0]:.1f}  corn {rp_plan[1]:.1f}  beets {rp_plan[2]:.1f}"
    "   (book: 170, 80, 250)"
)
print(f"expected profit = ${rp_expected:,.0f}   (book: $108,390)")
print(
    "scenario profits  above ${:,.0f} | average ${:,.0f} | below ${:,.0f}".format(
        *rp_profits
    )
)

assert np.allclose(rp_plan, [170.0, 80.0, 250.0], atol=1e-6)
assert abs(rp_expected - 108390.0) < 1.0

# Exercise the unit declarations once, on this small model.
assert_units_consistent(build_farmer(THREE, check_units=True))
print("units consistent")
acres  wheat 170.0  corn 80.0  beets 250.0   (book: 170, 80, 250)
expected profit = $108,390   (book: $108,390)
scenario profits  above $167,000 | average $109,350 | below $48,820
units consistent
print("Scenario    wheat sold   corn bought   corn sold   premium beets   profit ($)")
for label, s in zip(["above", "average", "below"], RP.SCENARIOS):
    b = RP.scenarios[s]
    print(
        f"{label:8s} {pyo.value(b.sales['WHEAT']):13.1f}"
        f" {pyo.value(b.purchases['CORN']):13.1f} {pyo.value(b.sales['CORN']):11.1f}"
        f" {pyo.value(b.sales['BEETS_FAVORABLE']):15.1f} {pyo.value(b.profit):12,.0f}"
    )
Scenario    wheat sold   corn bought   corn sold   premium beets   profit ($)
above            310.0           0.0        48.0          6000.0      167,000
average          225.0           0.0         0.0          5000.0      109,350
below            140.0          48.0         0.0          4000.0       48,820

Analyze the results: compare acreage with the three perfect-information plans. Explain the switch between buying and selling corn. Does maximizing expected profit protect the worst-year outcome?

The extensive form has 3 shared acreage variables and 6 trading variables per scenario: 21 variables total. With the quota as a bound, there are 10 explicit inequalities (one land limit and three balances per scenario), plus variable bounds. None is an equality; subtracting inequality counts is not a degrees-of- freedom calculation.

Extensive-form coefficient blocks: shared acreage columns couple all scenarios; trade columns belong to one scenario.

In the figure, each TsT_s is the scenario coefficient block multiplying shared acreage and WsW_s multiplies that scenario’s trades. Quota bounds are omitted from the block sketch. Shaded blocks may include zero coefficients; the empty off-diagonal blocks are all zero.

Compare information values consistently

Use profit maximization throughout this comparison. Let F(x,ξ)F(\mathbf{x},\boldsymbol\xi) include optimal trading after yields are observed.

WS=E[max⁡xF(x,Ξ)],RP=max⁡xE[F(x,Ξ)].WS=\mathbb E[\max_{\mathbf{x}}F(\mathbf{x},\boldsymbol\Xi)],\qquad RP=\max_{\mathbf{x}}\mathbb E[F(\mathbf{x},\boldsymbol\Xi)].
PolicyLow-yield profit [$]Mean-yield profit [$]High-yield profit [$]Expected profit [$]
Perfect-information plans59,950118,600167,666.67115,405.56
Shared stochastic plan48,820109,350167,000108,390

Values are rounded after averaging; Birge and Louveaux, §4.1, pp. 163–164.

Expected value of perfect information

For profit maximization, EVPI=WS−RP≈$7,015.56EVPI=WS-RP\approx\$7,015.56. It is the model’s increase in optimal expected profit if yields are known before planting. It bounds a risk-neutral decision maker’s willingness to pay for perfect yield information within this model, not the price of an arbitrary forecast. See Birge and Louveaux, §4.1, pp. 163–164.

Expected-value plan evaluated under uncertainty

Choose xˉ∈arg⁡max⁡xF(x,E[Ξ])\bar{\mathbf{x}}\in\arg\max_{\mathbf{x}}F(\mathbf{x},\mathbb E[\boldsymbol\Xi]) and then compute EEV=E[F(xˉ,Ξ)]EEV=\mathbb E[F(\bar{\mathbf{x}},\boldsymbol\Xi)]. Only acreage is fixed; trading is optimized per scenario.

For the mean-yield plan (120,80,300), EEV=$107,240EEV=\$107,240. See Birge and Louveaux, §4.2, p. 165.

Value of the stochastic solution

With the profit convention, VSS=RP−EEV=$108,390−$107,240=$1,150VSS=RP-EEV=\$108,390-\$107,240=\$1,150. The general ordering WS≥RP≥EEVWS\ge RP\ge EEV assumes a feasible EV plan and consistently optimized recourse. See Birge and Louveaux, §§4.2–4.3, pp. 165–166.

Why the information values have this order

Assume the optima below exist, their expected profits are finite, and the expected-value acreage xˉ\bar{\mathbf{x}} admits feasible recourse in every retained scenario. Let XX impose the first-stage restrictions and let F(x,ξs)F(\mathbf{x},\boldsymbol{\xi}_s) be the maximum scenario profit after optimizing trades at that fixed acreage. Acreage infeasible for a scenario can be excluded from that scenario’s maximization. Write x∗\mathbf{x}^* for an optimal shared plan and xsWS\mathbf{x}^{WS}_s for a scenario’s perfect-information plan.

Information timing for WS, RP, and EEV: planting can follow the harvest information only in the perfect-information benchmark.
  1. For each scenario, x∗\mathbf{x}^* is an available candidate when planting may use perfect information. Therefore

    F(xsWS,ξs)≥F(x∗,ξs).F(\mathbf{x}^{WS}_s,\boldsymbol{\xi}_s)\ge F(\mathbf{x}^*,\boldsymbol{\xi}_s).

    Multiply by ps≥0p_s\ge0 and sum: WS≥RPWS\ge RP.

  2. The shared stochastic optimum can choose the feasible candidate xˉ\bar{\mathbf{x}}, so

    RP=max⁡x∑spsF(x,ξs)≥∑spsF(xˉ,ξs)=EEV.RP=\max_{\mathbf{x}}\sum_s p_sF(\mathbf{x},\boldsymbol{\xi}_s) \ge\sum_s p_sF(\bar{\mathbf{x}},\boldsymbol{\xi}_s)=EEV.

Consequently WS≥RP≥EEVWS\ge RP\ge EEV, EVPI=WS−RP≥0EVPI=WS-RP\ge0, and VSS=RP−EEV≥0VSS=RP-EEV\ge0. The first inequality relaxes a restriction on information; the second compares an optimized plan with one feasible candidate. Neither proof requires convexity. For net-cost minimization every profit changes sign, giving WS≤RP≤EEVWS\le RP\le EEV, EVPI=RP−WSEVPI=RP-WS, and VSS=EEV−RPVSS=EEV-RP. This is the profit-form derivation of Birge and Louveaux, §4.3, Proposition 1, p. 166.

Scope. If the mean-data plan is infeasible in a retained scenario, a finite EEV cannot be assumed; one must report infeasibility or explicitly introduce and price emergency recourse. A feasible plan’s trades must be reoptimized for each harvest. Reusing mean-harvest trades is a different, more restricted policy. The mean-data optimum EVEV is also a different quantity from the expected realized profit EEVEEV: here EV=$118,600EV=\$118,600, while EEV=$107,240EEV=\$107,240.

There is no general ordering between EVPI and VSS. The alternative-price farmer gives a fully solved case with zero VSS and positive EVPI; Birge and Louveaux, §4.4, pp. 167–170, also treats the reverse case. EVPI concerns perfect information before the decision. VSS concerns optimizing the given probability law rather than its mean-data replacement. It is not the value of learning that law.

For risk-neutral expected profit, a forecast that only supplies partial yield information and leaves this model’s actions, prices, and uncertainty law unchanged cannot improve expected profit by more than perfect information does. An $8,000 forecast therefore cannot cover its cost when EVPI≈$7,015.56EVPI\approx\$7,015.56. This comparison does not price additional benefits outside the stated model.

Feasible perfect-information copies, common decisions, and one fixed candidate in a two-scenario illustration.

The square, diagonal, and fixed point illustrate the feasible-set argument. The drawing normalizes one decision to [0,1][0,1] and omits other constraints; it does not assert that the full farmer feasible set is a square.

WS = np.mean([pyo.value(m.expected_profit) for m in perfect_models])
EEV, ev_profits = grade_plan(mean_plan, THREE)
print("Policy                         expected profit ($)")
print(f"Perfect information (WS)        {WS:17,.2f}")
print(f"Shared stochastic plan (RP)     {rp_expected:17,.2f}")
print(f"Mean-yield plan evaluated (EEV) {EEV:17,.2f}")
print(f"EVPI = WS - RP: ${WS - rp_expected:,.2f}")
print(f"VSS  = RP - EEV: ${rp_expected - EEV:,.2f}")
print("EEV scenario profits [above, average, below]:", ev_profits)
assert np.isclose(EEV, 107240)
assert WS >= rp_expected >= EEV
Policy                         expected profit ($)
Perfect information (WS)               115,405.56
Shared stochastic plan (RP)            108,390.00
Mean-yield plan evaluated (EEV)        107,240.00
EVPI = WS - RP: $7,015.56
VSS  = RP - EEV: $1,150.00
EEV scenario profits [above, average, below]: [148000. 118600.  55120.]

EVPI measures the value of perfect pre-decision information. VSS compares the stochastic optimum with the evaluated mean-data plan; it does not measure the value of learning an unknown distribution.

Reference vocabulary and notation

These definitions use profit maximization. The CVaR guide, SAA guide, and advanced sampling study develop their respective formulations and computations.

TermMeaning
Chance constraintA requirement such as Pr⁡{g(x,Ξ)≤0}≥1−ε\Pr\{g(\mathbf{x},\boldsymbol{\Xi})\le0\}\ge1-\varepsilon under a specified probability model. The allowed violation probability is ε\varepsilon.
Conditional value at risk (CVaR)Average of the worst probability mass 1−α1-\alpha of a loss distribution, splitting an atom at the cutoff when necessary. For profit PP, the equivalent lower-tail profit is −CVaR⁡α(−P)-\operatorname{CVaR}_\alpha(-P).
Empirical distributionA discrete law assigning probability weights to observed or sampled outcomes; an equally weighted sample of NN harvests assigns weight 1/N1/N to each row.
EpigraphThe points on or above a function’s graph: {(u,t):t≥ϕ(u)}\{(u,t):t\ge\phi(u)\}. Auxiliary variables can represent positive-part shortfalls using linear inequalities.
Expected result of the EV policy (EEV)Expected performance after fixing a mean-data optimal first-stage decision and optimizing recourse under the original probability law.
ExpectationThe probability-weighted average of a random quantity, when it exists. See the finite-sum and density-integral formulas above.
Expected-value problem (EV)Deterministic optimization with uncertain data replaced by their means. Its solution is an EV policy; its objective value generally differs from EEV.
Expected value of perfect information (EVPI)Increase in optimal expected profit from knowing all modeled uncertainty before choosing any decisions: WS−RPWS-RP.
First-stage decisionA choice made before the modeled uncertainty is observed, such as acreage.
NonanticipativityDecisions use only available information; scenarios with the same observed history must share the corresponding decisions.
RecourseActions available after observing uncertainty, subject to earlier decisions, such as purchases and sales after planting.
Recourse problem (RP)Optimize shared first-stage decisions and adaptive recourse for the specified expected objective.
Risk aversionA preference that accounts for unfavorable outcomes beyond their contribution to the mean. A CVaR objective is one modeling choice.
Risk neutralityComparing uncertain outcomes by their expected objective value.
Sample average approximation (SAA)Replace an expectation by an average over sampled scenarios and optimize the resulting model. Training chooses the policy; independent evaluation assesses that fixed policy.
ScenarioOne joint realization of the uncertain quantities, together with its probability weight. Preserve the association among quantities within each row.
Second-stage decisionA recourse choice made after the modeled uncertainty is revealed.
Value at risk (VaR)At 0<α<10<\alpha<1, the loss quantile inf⁡{ℓ:Pr⁡(L≤ℓ)≥α}\inf\{\ell:\Pr(L\le\ell)\ge\alpha\}. It gives a threshold, rather than the average severity beyond it.
Value of the stochastic solution (VSS)Increase in expected profit from optimizing the probability model instead of evaluating an EV policy: RP−EEVRP-EEV.
Wait-and-see (WS)Expected optimal performance if all modeled uncertainty is known before any decision; a perfect-information benchmark.
SymbolRole
α\alphaCVaR confidence level; 1−α1-\alpha is the tail probability. The finite-scenario CVaR formula has a direct endpoint extension at α=0\alpha=0; it is undefined at α=1\alpha=1.
λ\lambdaOptional mixing weight in (1−λ)E[P]+λCα−(P)(1-\lambda)\mathbb E[P]+\lambda C^-_\alpha(P). It weights the criteria, while α\alpha selects which tail is measured.
PP and L=−PL=-PProfit and loss; larger profit is better, larger loss is worse. Positive profit is a negative loss.
Ξ\boldsymbol{\Xi} and ξs\boldsymbol{\xi}_sRandom vector and realized scenario data.
x\mathbf{x} and ys\mathbf{y}_sShared first-stage decision and scenario-dependent recourse.

Core stage, probability, and information-value definitions follow Birge and Louveaux, §§2.1–2.4, pp. 55–60, and §§4.1–4.3, pp. 163–166. General-distribution VaR/CVaR definitions follow Rockafellar and Uryasev (2002), Definitions 1 and 3 and Proposition 8; see the primary source links. The SAA guide cites Mak, Morton and Wood (1999) and Bayraksan and Morton (2006).

Complete solutions: acreage and all trades

Every purchase and sale quantity below is in metric tons. Profit includes planting cost. Perfect-information solutions choose a different acreage vector for each known harvest; the recourse and expected-value policies each plant once.

These are the full solutions used in the lecture handouts. The expected-value policy is evaluated with new optimal trades in each scenario, rather than reusing trades computed at mean yields.

import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import display

classical = {
    "known_mean": solution_record(mean_model),
    "perfect": [solution_record(m) for m in perfect_models],
    "rp": solution_record(RP),
    "eev": evaluate_solution(mean_plan, THREE),
    "inputs": THREE, "WS": float(WS), "RP": float(rp_expected), "EEV": float(EEV),
    "EVPI": float(WS-rp_expected), "VSS": float(rp_expected-EEV),
}

def full_solution_table(record):
    rows=[]
    for s in record["scenarios"]:
        rows.append({"Scenario":s["id"], "Probability":s["probability"],
            **{"Buy "+k+" (T)":v for k,v in s["purchases_tons"].items()},
            **{"Sell "+k+" (T)":v for k,v in s["sales_tons"].items()},
            "Profit ($)":s["profit"]})
    display(pd.Series(record["acreage"],name="Planted acres").to_frame().round(3))
    display(pd.DataFrame(rows).set_index("Scenario").round(2))
    print(f"Expected profit: ${record['expected_profit']:,.2f}")

for title,record in [("Mean yields",classical["known_mean"]),
    *[(name,r) for name,r in zip(["Known high","Known average","Known low"],classical["perfect"])],
    ("Shared stochastic plan",classical["rp"]),("Mean-yield plan evaluated",classical["eev"])]:
    print(title)
    full_solution_table(record)
results=classical
helper.save_results("farmer-information-values",results,
    notebook="notebooks/4-dev/SP.ipynb",source_tag="handout:farmer-root",
    solver="appsi_highs",description="Complete classical farmer solutions and information values",
    source_files=["notebooks/farmer.py"],
    source_cells=[("notebooks/4-dev/SP.ipynb", "provenance:farmer-information-solve")])
Mean yields
Loading...
Loading...
Expected profit: $118,600.00
Known high
Loading...
Loading...
Expected profit: $167,666.67
Known average
Loading...
Loading...
Expected profit: $118,600.00
Known low
Loading...
Loading...
Expected profit: $59,950.00
Shared stochastic plan
Loading...
Loading...
Expected profit: $108,390.00
Mean-yield plan evaluated
Loading...
Loading...
Expected profit: $107,240.00
[helper] wrote figures/results/farmer-information-values.json
fig,axes=plt.subplots(1,2,figsize=(10,4),layout="constrained")
values=[results[k]/1000 for k in ["EEV","RP","WS"]]
colors=["#009E73","#0072B2","#D55E00"]
for i,(value,label,color) in enumerate(zip(values,["EEV","RP","WS"],colors)):
    axes[0].plot([i-.25,i+.25],[value,value],color=color,linewidth=3)
    axes[0].text(i,value+.4,f"{label}\n${value:,.2f}k",ha="center",fontsize=11)
for i,label in [(0,"VSS"),(1,"EVPI")]:
    axes[0].annotate("",xy=(i+.5,values[i+1]),xytext=(i+.5,values[i]),
                     arrowprops={"arrowstyle":"<->","color":"black"})
    axes[0].text(i+.57,(values[i]+values[i+1])/2,label,fontsize=10,va="center")
axes[0].set(xlim=(-.5,2.5),ylim=(105,119),ylabel="Expected profit ($1000)",xticks=[])
for label,key,marker,color in [("EEV","eev","s",colors[0]),("RP","rp","o",colors[1])]:
    p=[s["profit"]/1000 for s in results[key]["scenarios"]]
    axes[1].plot([0,1,2],p,marker=marker,label=label,color=color)
axes[1].plot([0,1,2],[r["expected_profit"]/1000 for r in results["perfect"]],
             marker="^",linestyle="--",color=colors[2],label="WS: perfect information")
axes[1].set(xticks=[0,1,2],xticklabels=["High","Average","Low"],
            xlabel="Harvest scenario",ylabel="Profit ($1000)")
fig.legend(*axes[1].get_legend_handles_labels(),loc="outside upper center",ncol=3,fontsize=11)
helper.save_figure(fig,"farmer-information-values")
[helper] wrote media/figures/farmer-information-values.png and .pdf
<Figure size 1000x400 with 2 Axes>

Applications: identify what can adapt

For every model, specify the decision, the uncertain information, and which decisions remain available afterward. The stages follow that information structure. They do not follow whether a variable happens to appear in a scenario Block.

One robot for many tasks

Bravo-Palacios, Del Prete, and Wensing (2020) optimize a shared robot morphology together with scenario-specific trajectories. The physical design ρ\boldsymbol\rho includes quantities such as limb dimensions and actuator gear ratios. A scenario describes the task or environment; its state and control trajectories xs(t),us(t)\mathbf{x}_s(t),\mathbf{u}_s(t) may adapt to it. Their multiple-task formulation has the structure

min⁡ρ,{xs(⋅),us(⋅)}∑spsVs(xs(⋅),us(⋅),ρ),\min_{\boldsymbol\rho,\{\mathbf{x}_s(\cdot),\mathbf{u}_s(\cdot)\}} \sum_s p_s V_s(\mathbf{x}_s(\cdot),\mathbf{u}_s(\cdot),\boldsymbol\rho),

subject to each scenario’s dynamics, path constraints, and boundary conditions, with one shared design. The paper uses direct collocation to transcribe the trajectory problems into an NLP. Its planar-manipulator and hopping-robot cases therefore connect the course’s dynamic optimization and stochastic modeling topics; they are not farmer LPs. A high probability weight does not make an infeasible design feasible in another required scenario.

One physical robot design is shared by task-specific state and control trajectories.

Course connection, reported by Prof. Dowling: this work grew out of a Notre Dame student taking an early offering of this course. That teaching history is instructor-supplied context, separate from the paper’s claims.

Source: Bravo-Palacios, Del Prete & Wensing (2020), pp. 1680–1682, Fig. 1 and §II.A, Eq. (6).

Port capacity before freight demand

Bhurtyal et al. (2024) choose integer investments in loading equipment and storage facilities under a budget. After freight demand is realized, transportation and inventory decisions adapt within the installed capacity. The objective combines investment cost with expected transportation cost. The modeled network includes ports, supply/demand regions, truck and rail links, and barge movement.

The farmer analogy is capacity rather than crop identity: installed equipment and storage correspond to shared acreage; scenario-specific flows correspond to recourse trades. Integer investment variables distinguish this model from the continuous farmer LP. The source’s reported economic effects belong to its case data and are not generic benefits guaranteed by stochastic optimization.

Source: Bhurtyal et al. (2024), §3, pp. 190–191, Eqs. (1)–(5).

Mitigate now, restore after a flood

Zhang and Alipour (2023) choose pre-event bridge mitigation and construction actions, then scenario-specific restoration resources and techniques after weather-related damage. Traffic assignment represents network use during construction and recovery. Their formulation combines investment and disruption consequences with expected post-event costs and contains integer decisions and nonlinear network behavior.

The two-stage restriction keeps the mitigation choice common across flood scenarios. Recovery may vary because damage is then observed. Calling this second-stage action recourse distinguishes it from a WS benchmark, which would also allow the earlier mitigation decision to use perfect information. User-equilibrium traffic assignment should not be interpreted as a general guarantee of minimum total system travel time.

Source: Zhang & Alipour (2023), §3.3, pp. 416–417, first-stage formulation (1)–(15) and recourse formulation (16)–(27).

CO₂ transport investment as capacity changes

Zhang, Anjos, and Chalmers (2026) plan pipeline and ship infrastructure while future capture capacity may expand, close, or reactivate. Decisions adapt over several information stages. At a common scenario-tree node, scenarios with the same observed history share the investment decision; a model that allowed a different early investment for each full future path would anticipate information.

The Humber-cluster study uses a 40-year horizon with seven stages, including a final operating stage without new construction. Those are case assumptions. The general lesson is the evolving information structure and the cost of committing capacity before demand for transport is resolved. A two-stage simplification would need to state when the remaining uncertainty becomes known and which subsequent choices it allows.

Source: Zhang, Anjos & Chalmers (2026), §§2.1–2.2, physical PDF pp. 5–6 and Fig. 1; article 11, rather than an eleven-page range.

Process systems: match the model to the uncertainty

Li and Grossmann’s 2021 review surveys stochastic programming for process design, planning, scheduling, and control. Table 1 pairs applications with uncertain quantities, including supply, prices, feed composition, demand, yields, and technology or clinical outcomes. These are entry points for choosing a modeling structure; a common vocabulary does not make their feasible sets or information assumptions interchangeable.

For a process-design application, identify installed equipment and sizing decisions, then identify the operating actions available after uncertain feeds or demands are observed. A scheduling application may instead place decisions at several release times. Risk-neutral expected cost, tail-risk criteria, chance constraints, and robust requirements answer different questions.

Source: Li & Grossmann (2021), §1 and Table 1, pp. 2–3. Its literature counts describe the review’s historical search window, not a current field census.

For another complete formulation and executable Block example, see A Small Two-Stage Production Plan. It is optional reading; the farmer remains the common example of the two lecture handouts.

Probability of compliance and further directions

For a specified probability law P\mathbb P and allowed violation probability 0≤ε<10\le\varepsilon<1, a chance constraint requires

P{g(x,Ξ)≤0}≥1−ε.\mathbb P\{g(\mathbf{x},\boldsymbol\Xi)\le0\}\ge1-\varepsilon.

A joint chance constraint instead protects all listed requirements together:

P{gj(x,Ξ)≤0 for every j=1,…,m}≥1−ε.\mathbb P\{g_j(\mathbf{x},\boldsymbol\Xi)\le0\text{ for every }j=1,\ldots,m\}\ge1-\varepsilon.

Separate bounds Pr⁡(gj≤0)≥1−εj\Pr(g_j\le0)\ge1-\varepsilon_j imply only the union-bound guarantee Pr⁡(gj≤0 ∀j)≥1−∑jεj\Pr(g_j\le0\ \forall j)\ge1-\sum_j\varepsilon_j, without further assumptions. For example, among 20 equal-probability outcomes, let only outcome 1 violate A and only outcome 2 violate B. Each requirement holds with probability 0.95; both hold together with probability 0.90. Independence is not assumed in this construction.

Twenty equally likely outcomes with separate failures of two requirements.

Chance constraints limit violation probability under the declared law. CVaR instead measures the severity of unfavorable outcomes in a chosen tail. Neither criterion corrects an incorrectly specified probability law. In an empirical chance constraint, meeting the retained sample requirement is also different from certifying population reliability. See Pagnoncelli, Ahmed & Shapiro (2009), §1, Eq. (1), p. 400 and §2, for the sampled formulation and its theory.

Application and theory entry points

  • Process optimization: Li, Arellano-Garcia & Wozny (2008), §§2–3, uses probability requirements for uncertain operating quantities. State whether one requirement or simultaneous compliance is protected.

  • Assistive-robot activities: Bevilacqua et al. (2021), §III, p. 3953, concerns activity/point-of-interest selection with probabilistic resource limits. This is not a general guarantee of low-level physical safety.

  • Sampling: Pagnoncelli et al., §§2–4, develops SAA theory with portfolio and blending applications. Sampling error and the chosen violation probability are separate quantities.

Information can arrive repeatedly or depend on decisions

Multistage programming adapts decisions as information arrives. Scenarios sharing the observed history must share decisions at that node. Ding, Hu & Bie (2018), §II, pp. 319 onward, applies nonanticipativity to joint power and natural-gas expansion. The CO₂ transport case is another multistage example. A full-path scenario index alone does not enforce the correct information restriction.

Decision-dependent uncertainty permits decisions to alter the probability law, the information available later, or both. Yin & Hou (2024), §2, reviews these distinctions in power systems. The farmer’s exogenous yield generator does not model either effect: planting changes harvest quantity at a realized yield, rather than changing the yield distribution.

Distributionally robust chance constraints require probability compliance across an ambiguity set P\mathcal P of candidate laws, for example

inf⁡P∈PP{g(x,Ξ)≤0}≥1−ε.\inf_{\mathbb P\in\mathcal P}\mathbb P\{g(\mathbf{x},\boldsymbol\Xi)\le0\}\ge1-\varepsilon.

Jiang & Guan (2016), §1.2, pp. 294–295 and §3, Theorem 1, p. 298, studies a particular ϕ\phi-divergence ambiguity set. Its reformulation should not be generalized to arbitrary ambiguity sets. Ambiguity about a law differs from variation of outcomes under one known law.

For alternative risk measures and a complete fixed-production case, see Risk Measures and Portfolio Optimization. For replicated optimality-gap estimation and quadrature detail, see Advanced Topics in Stochastic Programming. Those pages supplement the handouts; their assumptions and sign conventions must be carried into any comparison.

References
  1. Bravo-Palacios, G., Prete, A. D., & Wensing, P. M. (2020). One Robot for Many Tasks: Versatile Co-Design Through Stochastic Programming. IEEE Robotics and Automation Letters, 5(2), 1680–1687. 10.1109/lra.2020.2969948
  2. Bhurtyal, S., Hernandez, S., Eksioglu, S., & Yves, M. (2023). A two-stage stochastic optimization model for port infrastructure planning. Maritime Economics & Logistics, 26(2), 185–211. 10.1057/s41278-023-00262-0
  3. Zhang, N., & Alipour, A. (2023). A stochastic programming approach to enhance the resilience of infrastructure under weather-related risk. Computer-Aided Civil and Infrastructure Engineering, 38(4), 411–432. 10.1111/mice.12843
  4. Zhang, L., Anjos, M. F., & Chalmers, H. (2026). A stochastic programming model for planning CO2 transport infrastructure with uncertainty. Computational Management Science, 23(1). 10.1007/s10287-026-00555-8
  5. Li, C., & Grossmann, I. E. (2021). A Review of Stochastic Programming Methods for Optimization of Process Systems Under Uncertainty. Frontiers in Chemical Engineering, 2. 10.3389/fceng.2020.622241
  6. Pagnoncelli, B. K., Ahmed, S., & Shapiro, A. (2009). Sample Average Approximation Method for Chance Constrained Programming: Theory and Applications. Journal of Optimization Theory and Applications, 142(2), 399–416. 10.1007/s10957-009-9523-6
  7. Li, P., Arellano-Garcia, H., & Wozny, G. (2008). Chance constrained programming approach to process optimization under uncertainty. Computers & Chemical Engineering, 32(1–2), 25–45. 10.1016/j.compchemeng.2007.05.009
  8. Bevilacqua, P., Frego, M., Palopoli, L., & Fontanelli, D. (2021). Activity Planning for Assistive Robots Using Chance-Constrained Stochastic Programming. IEEE Transactions on Industrial Informatics, 17(6), 3950–3961. 10.1109/tii.2020.3012094
  9. Ding, T., Hu, Y., & Bie, Z. (2018). Multi-Stage Stochastic Programming With Nonanticipativity Constraints for Expansion of Combined Power and Natural Gas Systems. IEEE Transactions on Power Systems, 33(1), 317–328. 10.1109/tpwrs.2017.2701881
  10. Yin, W., & Hou, Y. (2024). Models and applications of stochastic programming with decision‐dependent uncertainty in power systems: A review. IET Renewable Power Generation, 18(14), 2819–2834. 10.1049/rpg2.13082
  11. Jiang, R., & Guan, Y. (2015). Data-driven chance constrained stochastic program. Mathematical Programming, 158(1–2), 291–327. 10.1007/s10107-015-0929-7