Short note. A compact reference, correct as far as it goes. It will be expanded into a full guide with a worked example and an interactive demonstration.
Problem
Some decisions have to be made before uncertainty resolves (how much capacity to build, how much inventory to pre-position), while other, corrective decisions can be made after (how much to actually produce, once demand is known). A deterministic model that just plugs in expected demand systematically gets this trade-off wrong — it’s why “plan to the average” so often underperforms in practice.
When to use it
Capacity planning, pre-positioning inventory ahead of a demand season, network design under uncertain future demand — anywhere a “commit now, adjust later” structure exists and you have (or can construct) a set of plausible future scenarios.
Formulation
First-stage decision x (made before uncertainty), second-stage recourse
decision y_s for each scenario s with probability p_s:
minimize c^T x + sum_s(p_s * q^T y_s)
subject to:
A x = b # first-stage constraints
W y_s = h_s - T x for all s # second-stage constraints, per scenario
x >= 0, y_s >= 0
The second-stage constraints depend on both the scenario’s data (h_s) and
the first-stage decision (T x) — that coupling is what makes this
genuinely a two-stage problem rather than just solving each scenario
independently and averaging.
Code sketch
import pulp
scenarios = {"low": (0.3, 300), "mid": (0.5, 500), "high": (0.2, 800)}
prob = pulp.LpProblem("two_stage", pulp.LpMinimize)
capacity = pulp.LpVariable("capacity", lowBound=0) # first-stage
produced = {s: pulp.LpVariable(f"produced_{s}", lowBound=0) for s in scenarios}
shortage = {s: pulp.LpVariable(f"shortage_{s}", lowBound=0) for s in scenarios}
prob += (build_cost * capacity +
pulp.lpSum(p * (prod_cost * produced[s] + shortage_penalty * shortage[s])
for s, (p, _) in scenarios.items()))
for s, (_, demand) in scenarios.items():
prob += produced[s] <= capacity
prob += produced[s] + shortage[s] >= demand
Pitfalls
- The number of second-stage variables grows with the number of scenarios — with enough scenarios this becomes a genuinely large LP/MILP. For big scenario trees, Benders decomposition (solve the first stage, then solve scenario subproblems and feed cuts back) is the standard scaling trick, not brute-force enumeration.
- Scenario quality matters more than model sophistication. Three badly-chosen scenarios will mislead you more than “plan to the average” would — invest in getting a representative scenario set (or sampling approach) before adding modeling complexity.
- This is fundamentally different from robust optimization, which optimizes against the worst case rather than the expected case — pick based on whether your business cares about average performance or worst-case protection.