The last two lessons fed point estimates into an optimizer and dealt with the consequences. This lesson takes the uncertainty seriously from the start. Lesson 6 gave you quantiles, intervals and, implicitly, distributions over outcomes. Now the optimizer gets to see them.
There are three main families of methods, and they differ mainly in what they assume about the uncertainty and how pessimistic they are:
- Stochastic programming: optimize the expected outcome over a set of scenarios.
- Robust optimization: optimize the worst case over an uncertainty set.
- Risk-averse optimization: optimize or constrain a risk measure, such as tail losses or the probability of failure.
Why point forecasts give fragile plans
Take an inventory problem with a shared warehouse. Plug in the mean demand forecast for each SKU and the optimizer fills the warehouse exactly to match. Any SKU that sells above forecast stocks out; any that sells below wastes space that another SKU needed. The plan is optimal for one future, the average one, which will not happen.
This “flaw of averages” is general. The value of a plan evaluated at average inputs is not the average value of the plan across possible inputs, whenever there are capacities, minimums, or any other non-linearity. And business decisions are full of them.
Stochastic programming with scenarios
The most practical approach: generate a few hundred scenarios of the uncertain quantities (from quantile models, conformal intervals, simulation, or bootstrapped history) and optimize the average outcome across them.
The key modeling idea is two stages. First-stage decisions are made now, before uncertainty resolves (how much of each SKU to order). Second-stage recourse decisions are made after, separately in each scenario (how much to sell, whether to expedite, what to mark down). The optimizer chooses the first stage knowing it’ll be able to react in the second.
from ortools.linear_solver import pywraplp
def order_plan(skus, scenarios, price, cost, salvage, capacity):
"""scenarios: list of dicts {sku: demand}, equally likely.
Sample average approximation of a two-stage newsvendor
with a shared warehouse capacity."""
S = len(scenarios)
solver = pywraplp.Solver.CreateSolver("GLOP") # pure LP
# Stage 1: order quantities, same in every scenario
q = {k: solver.NumVar(0, solver.infinity(), f"q[{k}]") for k in skus}
solver.Add(solver.Sum(q.values()) <= capacity)
# Stage 2: sales per scenario, limited by stock and demand
profit_terms = []
for s, demand in enumerate(scenarios):
for k in skus:
sold = solver.NumVar(0, demand[k], f"sold[{s},{k}]")
solver.Add(sold <= q[k])
# leftover (q - sold) is salvaged at a loss
profit_terms.append((price[k] - salvage[k]) * sold / S)
total_cost = solver.Sum((cost[k] - salvage[k]) * q[k] for k in skus)
solver.Maximize(solver.Sum(profit_terms) - total_cost)
solver.Solve()
return {k: q[k].solution_value() for k in skus}
This is called sample average approximation (SAA). With one SKU and no capacity, it reproduces the newsvendor quantile from Lesson 6. With a shared capacity, it does something the per-SKU rule can’t: it decides which SKUs deserve buffer stock given that space is limited, favouring those with high margins and volatile demand.
Two practical notes. Scenario quality matters more than scenario count: they need realistic correlation between items (a heatwave lifts all cold drinks together). And check stability: solve with several independent scenario sets; if the plans differ a lot, you need more scenarios.
Robust optimization
Stochastic programming needs a probability distribution. Sometimes you don’t trust one, or the stakes demand protection against bad cases regardless of their probability. Robust optimization asks instead: find the plan that performs best in the worst case over an uncertainty set, such as every demand vector within its conformal interval.
Protecting against every parameter hitting its worst value simultaneously is usually absurdly conservative. If 500 SKUs each have a 90% interval, the chance that all of them land at the top of their intervals at once is negligible. The budget of uncertainty approach (Bertsimas and Sim) limits how many parameters can deviate to their worst case together, with a parameter Γ:
- Γ = 0 gives the nominal plan (no protection).
- Γ = number of parameters gives full worst-case protection.
- In between, a tunable dial. A Γ around a few times the square root of the number of parameters often gives strong protection at modest cost.
Robust formulations remain LPs or MIPs (the worst case can be dualized into extra constraints), so they run in the same solvers. Use robust optimization when constraint violations are unacceptable (capacity you physically can’t exceed, safety limits), and when you’d rather pay a known premium than trust a distribution.
Chance constraints
Often the business states the requirement as a probability: “the chance of a stockout on critical parts must be under 2%”, “the probability of overspending the budget must be under 5%”. These are chance constraints.
With scenarios, approximate them directly: add a binary indicator per scenario for “constraint violated here” and require at most 2% of scenarios to have it switched on. This makes the model a MIP, which is fine for moderate scenario counts. For a single uncertain quantity, it’s simpler: a chance constraint P(demand ≤ stock) ≥ 0.98 is just stock ≥ the 98th percentile of demand, which a quantile model or conformal interval gives you directly.
Risk measures: CVaR
Expected-value objectives are risk-neutral: a plan that earns $10M on average with a 3% chance of losing $50M looks better than one that earns $9M with no chance of loss. Many businesses aren’t risk-neutral, for good reason.
Conditional value at risk (CVaR) at level 95% is the average loss in the worst 5% of scenarios. It’s a well-behaved risk measure, and with scenarios it’s linear-programmable (Rockafellar and Uryasev), so you can:
- Maximize expected profit subject to CVaR₉₅ of loss ≤ some limit, or
- Maximize a blend: expected profit − β × CVaR, where β expresses risk aversion.
This is how portfolio managers, insurers and energy traders set positions, and it’s directly useful for any decision with heavy-tailed downside: credit portfolios, large inventory bets, capacity commitments.
Choosing a formulation
| Situation | Formulation |
|---|---|
| Costs roughly linear, no capacity limits, risk-neutral | Expected value (point forecasts) is fine |
| Shared capacity, recourse available, trustworthy distributions | Two-stage stochastic (SAA) |
| Violations unacceptable, distributions untrusted | Robust with budgeted uncertainty |
| Requirement stated as a probability | Chance constraint |
| Rare large losses are unacceptable | CVaR objective or constraint |
Always compare against the expected-value plan on held-out scenarios. The difference, called the value of the stochastic solution, tells you whether the extra modeling is buying anything. Sometimes it’s 0.5% and not worth the complexity. Often, for capacity-constrained or heavy-tailed problems, it’s 5 to 15%.
That completes the optimization layer. But every value the optimizer has used so far assumed we knew the effect of each action. Module 4 tackles the hardest part of decision modeling: estimating what an action actually changes.