Skip to content

Toy Four-Reservoir SDDP Walkthrough

This chapter extends the single-reservoir walkthrough in Toy Single-Reservoir Walkthrough to a four-reservoir, four-bus system, still using a 0-order (seasonal-sampling) inflow model. The chapter demonstrates two phenomena that the single-reservoir case cannot exhibit:

  • Multi-dimensional cuts. The cut becomes a hyperplane with one storage coefficient per reservoir; the optimiser must balance releases across plants by reading the per-storage reduced cost of each pinned incoming-storage column.
  • Per-bus dispatch with independent supply. Each bus carries its own demand and is served by its local hydro and a local thermal; the LP solves the regional dispatches simultaneously inside one stage problem.

Cobre ships an actual reference case at examples/4ree/ modelling the four-region Brazilian interconnected system (SUDESTE, SUL, NORDESTE, NORTE) with 24 monthly stages, 126 thermals, and two transmission lines (SUDESTE–SUL and SUDESTE–NORDESTE). The shipped case has no inter-reservoir cascade coupling (each region’s reservoir is independent), no spatial inflow correlation, and uses a constant hydro productivity model. This walkthrough preserves those properties but replaces the production-scale numbers with hand-traceable values: 4 stages, 3 openings, one thermal per bus, no transmission. This walkthrough is a pedagogical caricature, not a reproduction of the shipped case.

The chapter does not cover transmission, cascade coupling, the FPHA production model, an infinite-horizon cyclic policy, multi-resolution coupling, or risk measures. Those topics are addressed in the chapters cited in section 8.


The system has four buses, each with one local hydro (productivity 1.0), one local thermal, and one local demand block. The four reservoirs are independent — no cascade coupling. There is no transmission between buses in this walkthrough; each bus self-balances dispatch from its local resources.

graph LR
    A1["Inflow a₁<br/>(0-order)"] --> H1["H1<br/>cap 100"]
    A2["Inflow a₂<br/>(0-order)"] --> H2["H2<br/>cap 100"]
    A3["Inflow a₃<br/>(0-order)"] --> H3["H3<br/>cap 80"]
    A4["Inflow a₄<br/>(0-order)"] --> H4["H4<br/>cap 80"]
    H1 --> B1["Bus 1<br/>D = 25"]
    H2 --> B2["Bus 2<br/>D = 20"]
    H3 --> B3["Bus 3<br/>D = 15"]
    H4 --> B4["Bus 4<br/>D = 12"]
    T1["Thermal₁<br/>cost 50"] --> B1
    T2["Thermal₂<br/>cost 50"] --> B2
    T3["Thermal₃<br/>cost 50"] --> B3
    T4["Thermal₄<br/>cost 50"] --> B4

Per-hydro parameters:

HydroBusVˉh\bar V_hv^h,0\hat v_{h,0}μh\mu_hσh\sigma_hProductivity
H1B1100301551.0
H2B2100301241.0
H3B380201031.0
H4B48020831.0

Per-bus demand:

BusDbD_b
B125
B220
B315
B412

System-level parameters:

ParameterSymbolValue
StagesTT4
Openings per stageNN3
Thermal marginal costcthc^{th}50 $/MWh
Deficit costcdefc^{def}1000 $/MWh
Discount factordd1.0

Each bus’s demand exceeds its local hydro mean inflow (D1μ1=10D_1 - \mu_1 = 10, D2μ2=8D_2 - \mu_2 = 8, D3μ3=5D_3 - \mu_3 = 5, D4μ4=4D_4 - \mu_4 = 4), so reservoirs deplete over the four-stage horizon and thermal generation appears in later stages.


The stage-tt LP is assembled from the general formulation in LP Formulation, specialised to four hydros, four buses with one local thermal each, four storage state variables, and 0-order inflow (no AR-lag state).

Objective (minimise current-stage cost plus future cost):

minb=14(50gbth+1000δb)+θ\min \quad \sum_{b=1}^{4} \bigl( 50\, g^{th}_b + 1000\, \delta_b \bigr) + \theta

Per-bus load balance (each bus self-balances, no transmission):

gb+gbth+δb=Db,b=1,2,3,4g_b + g^{th}_b + \delta_b = D_b, \qquad b = 1, 2, 3, 4

where gbg_b is the local hydro generation at bus bb.

Hydro generation (constant productivity at all four plants):

gh  =  1.0qh,h=1,2,3,4g_h \;=\; 1.0 \cdot q_h, \qquad h = 1, 2, 3, 4

Water balances (each reservoir is independent — no cascade):

vh  =  vhin+ahqh,h=1,2,3,4v_h \;=\; v^{in}_h + a_h - q_h, \qquad h = 1, 2, 3, 4

(For this walkthrough ζ=1\zeta = 1: one m³/s of turbining per stage withdraws one hm³.)

Incoming-storage pinning (the reduced cost of each pinned column becomes the cut coefficient):

vhin  =  v^h,t1,h=1,2,3,4v^{in}_h \;=\; \hat{v}_{h,t-1}, \qquad h = 1, 2, 3, 4

Bounds: 0vhVˉh0 \leq v_h \leq \bar V_h; qh,gh,gbth,δb0q_h, g_h, g^{th}_b, \delta_b \geq 0; θ0\theta \geq 0.

Future cost variable θ\theta: as in the single-reservoir case, the terminal stage carries no cuts; cuts of the form θα+hπhvvh\theta \geq \alpha + \sum_h \pi^v_h\, v_h are added by the backward pass to earlier stages’ LPs. With four reservoirs the cut is now a 4-coefficient hyperplane in storage state space, with each πhv0\pi^v_h \leq 0 reflecting that storage at hydro hh reduces future cost.

Note again the absence of any AR-lag state: 0-order inflow has no memory, so storage is the entire state vector.


3. The 0-Order Inflow Model with Four Hydros

Section titled “3. The 0-Order Inflow Model with Four Hydros”

Each hydro hh samples its own inflow independently from a normal distribution with hydro-specific seasonal mean and standard deviation:

ah,t  =  μh+σhεh,t,εh,tN(0,1)a_{h,t} \;=\; \mu_h + \sigma_h\, \varepsilon_{h,t}, \qquad \varepsilon_{h,t} \sim \mathcal{N}(0, 1)

with hydro-specific (μh,σh)(\mu_h, \sigma_h) from the parameter table in section 1. The innovations are independent across hydros — there is no correlation.json file in the actual examples/4ree/ case, and this walkthrough preserves that property.

If a correlation.json file were supplied, the spatial structure Σ\Sigma between innovations would be applied via the spectral factorisation ε=Lz\varepsilon = L\, z with LL=ΣL L^{\top} = \Sigma — see PAR Inflow Model section 8 for the multivariate case. For this walkthrough the four innovations are drawn independently.

The three openings used in the backward pass correspond to a single shared ε{1,0,+1}\varepsilon \in \{-1, 0, +1\} applied uniformly to all four hydros (a simplifying choice — in the production code each hydro draws its own ε\varepsilon and the joint distribution lives over a larger sample-tree). Each opening has equal probability p=1/3p = 1/3.


The forward pass samples one trajectory using εt=0\varepsilon_t = 0 for every stage, so each hydro receives its mean inflow at every stage. With θ\theta free at zero (no cuts in iteration 1), the LP minimises the per-bus thermal and deficit costs at each stage.

Because at the initial state every bus has enough hydro plus storage to meet demand from hydro alone, stages 1–3 carry zero cost. Stage 4 runs water-short on three of the four buses.

Stage 1 — incoming storage v^0=(30,30,20,20)\hat v_0 = (30, 30, 20, 20), inflows (15,12,10,8)(15, 12, 10, 8). At every bus, qh=Dbq_h = D_b (demand met from hydro); end-of-stage storage:

v1=(30+1525,  30+1220,  20+1015,  20+812)=(20,22,15,16).v_1 = (30 + 15 - 25,\; 30 + 12 - 20,\; 20 + 10 - 15,\; 20 + 8 - 12) = (20, 22, 15, 16).

Stage cost: 00 (no thermal, no deficit).

Stage 2v^1=(20,22,15,16)\hat v_1 = (20, 22, 15, 16). Same pattern: each bus meets demand from hydro; storage decreases by DbμhD_b - \mu_h:

v2=(10,14,10,12).v_2 = (10, 14, 10, 12).

Stage cost: 00.

Stage 3v^2=(10,14,10,12)\hat v_2 = (10, 14, 10, 12). Bus 1 reaches a knife edge: water available =v^1,2+a1=10+15=25=D1= \hat v_{1,2} + a_1 = 10 + 15 = 25 = D_1; the LP turbines all available water and ends with v1,3=0v_{1,3} = 0. Other buses still have surplus.

v3=(0,6,5,8).v_3 = (0, 6, 5, 8).

Stage cost: 00.

Stage 4v^3=(0,6,5,8)\hat v_3 = (0, 6, 5, 8), inflows (15,12,10,8)(15, 12, 10, 8). Now three buses go water-short:

Busv^3\hat v_3aaAvail.DDqqgthg^{th}δ\delta
B1015152515100
B261218201820
B351015151500
B48816121200

Stage 4 cost: 50×10+50×2=500+100=60050 \times 10 + 50 \times 2 = 500 + 100 = 600.

Trajectory upper-bound estimate: 0+0+0+600=6000 + 0 + 0 + 600 = 600.


The backward pass walks stages 414 \to 1. At each stage it pins the incoming storage to the trial point from the forward pass, evaluates all three openings, reads the four pinned-column reduced costs, and aggregates into a 4-coefficient cut. The mechanics follow Cut Management sections 2–3, generalised from one storage coefficient (single-reservoir case) to four (this case).

Trial point: v^3=(0,6,5,8)\hat v_3 = (0, 6, 5, 8). Inflows under the three shared openings (all four hydros move together with ε\varepsilon):

Openingε\varepsilona1a_1a2a_2a3a_3a4a_4
ω1\omega_11-110875
ω2\omega_2001512108
ω3\omega_3+1+120161311

For each opening, each bus solves its local dispatch independently (no transmission, no θ\theta at the terminal stage):

ω1\omega_1 (dry):

BusAvail.DDqqgthg^{th}Stage costπhv\pi^v_h
B11025101575050-50
B2142014630050-50
B3121512315050-50
B41312120000

Q4(ω1)=750+300+150+0=1200Q_4(\omega_1) = 750 + 300 + 150 + 0 = 1200.

The storage cut coefficient at each bus — the reduced cost of the pinned incoming-storage column — follows the single-reservoir logic: water-limited buses with thermal active have πhv=50\pi^v_h = -50; buses where demand is met by hydro alone have πhv=0\pi^v_h = 0.

ω2\omega_2 (mean):

BusAvail.DDqqgthg^{th}Stage costπhv\pi^v_h
B11525151050050-50
B2182018210050-50
B31515150000
B41612120000

Q4(ω2)=600Q_4(\omega_2) = 600. B3 is at a knife edge (D=D = avail.); the walkthrough takes the basis returning π3v(ω2)=0\pi^v_3(\omega_2) = 0 (extra storage flows to terminal v4v_4 which has zero value).

ω3\omega_3 (wet):

BusAvail.DDqqgthg^{th}Stage costπhv\pi^v_h
B1202520525050-50
B22220200000
B31815150000
B41912120000

Q4(ω3)=250Q_4(\omega_3) = 250.

Per-opening intercepts α^4(ω)=Q4(ω)hπhv(ω)v^h,3\hat\alpha_4(\omega) = Q_4(\omega) - \sum_h \pi^v_h(\omega)\, \hat{v}_{h,3}:

OpeningQ4Q_4hπhvv^h,3-\sum_h \pi^v_h \hat v_{h,3}α^4\hat\alpha_4
ω1\omega_11200120050(0)+50(6)+50(5)+0(8)=55050(0) + 50(6) + 50(5) + 0(8) = 55017501750
ω2\omega_260060050(0)+50(6)+0(5)+0(8)=30050(0) + 50(6) + 0(5) + 0(8) = 300900900
ω3\omega_325025050(0)+0(6)+0(5)+0(8)=050(0) + 0(6) + 0(5) + 0(8) = 0250250

(The signs flip because πv<0\pi^v < 0 and the formula subtracts a negative-times-positive product.)

Aggregation (p=1/3p = 1/3):

αˉ4=13(1750+900+250)=29003966.7\bar\alpha_4 = \tfrac{1}{3}(1750 + 900 + 250) = \tfrac{2900}{3} \approx 966.7
Hydroπˉhv\bar\pi^v_h
H113(505050)=50\tfrac{1}{3}(-50 - 50 - 50) = -50
H213(5050+0)=1003\tfrac{1}{3}(-50 - 50 + 0) = -\tfrac{100}{3}
H313(50+0+0)=503\tfrac{1}{3}(-50 + 0 + 0) = -\tfrac{50}{3}
H413(0+0+0)=0\tfrac{1}{3}(0 + 0 + 0) = 0

Cut added to stage 3’s LP:

θ    2900350v11003v2503v3+0v4\theta \;\geq\; \tfrac{2900}{3} - 50\, v_1 - \tfrac{100}{3}\, v_2 - \tfrac{50}{3}\, v_3 + 0 \cdot v_4

Sanity check. At v=v^3=(0,6,5,8)v = \hat v_3 = (0, 6, 5, 8), the cut evaluates to

29003060032503+0=29006002503=20503683.3.\tfrac{2900}{3} - 0 - \tfrac{600}{3} - \tfrac{250}{3} + 0 = \tfrac{2900 - 600 - 250}{3} = \tfrac{2050}{3} \approx 683.3.

The probability-weighted expected stage-4 cost at the trial is Qˉ4=(1/3)(1200+600+250)=2050/3683.3\bar Q_4 = (1/3)(1200 + 600 + 250) = 2050/3 \approx 683.3. The cut is tight at the trial point, as required for cut validity. ✓

The four storage coefficients (50,100/3,50/3,0)(-50, -100/3, -50/3, 0) rank by how much each reservoir reduces expected future cost. H1 is the most valuable: thermal at B1 was active in all three openings, so an extra unit at H1 saves 5050 in every scenario and the average is 50-50. H4 has zero coefficient: B4 was thermal-free in every opening, so storage at H4 has no marginal value at this trial point.

The same procedure repeats at the earlier stages, with the cut from the next stage active in the LP. At each stage:

  1. Pin the incoming storage vector v^t1\hat v_{t-1} (column bounds).
  2. Solve all three opening LPs with the next-stage cut active.
  3. Read four pinned-column reduced costs per opening (one per reservoir).
  4. Compute per-opening intercepts via α^(ω)=Q(ω)hπhv(ω)v^h,t1\hat\alpha(\omega) = Q(\omega) - \sum_h \pi^v_h(\omega)\, \hat v_{h,t-1}.
  5. Aggregate by probability-weighted averaging into one 5-coefficient cut (intercept plus four storage slopes).
  6. Add the cut to the previous stage’s LP.

By the end of the backward pass, stages 1 through 3 each carry one cut; stage 4 has none.


The fundamental change from the single-reservoir case is the dimensionality of the cut. Each cut is now a hyperplane in 4-dimensional storage state space:

V^tk(v)  =  maxi=1,,k{αˉi+πˉ1v,iv1+πˉ2v,iv2+πˉ3v,iv3+πˉ4v,iv4}.\hat{V}_t^k(v) \;=\; \max_{i = 1, \ldots, k} \Bigl\{ \bar\alpha^i + \bar\pi^{v,i}_1\, v_1 + \bar\pi^{v,i}_2\, v_2 + \bar\pi^{v,i}_3\, v_3 + \bar\pi^{v,i}_4\, v_4 \Bigr\}.

Several practical consequences follow:

Per-reservoir marginal water value. The four slopes πˉhv\bar\pi^v_h at the iteration-1 stage-3 cut — 50-50, 100/3-100/3, 50/3-50/3, 00 — encode where storage matters most at the visited trial point. The optimiser at stage 3 sees these slopes and preferentially holds water at H1 (highest marginal value) and releases water at H4 (zero marginal value) when both choices are available.

Cut tightness is local, not global. The cut is a tangent hyperplane at the trial point only; far from the trial point the hyperplane is a loose lower bound on the true value function. The forward pass spreads trial points across the storage state space as iterations progress, and each new trial point produces a cut that tightens the approximation in its neighbourhood. The pointwise maximum over many such hyperplanes converges to the convex value function.

Per-bus thermal regime drives slope structure. A bus whose thermal is always idle (B4 in this trial) gets πˉhv=0\bar\pi^v_h = 0; the corresponding reservoir’s storage has no marginal value at the visited state. As subsequent iterations sample trial points where B4 also runs short, that reservoir’s slope becomes negative in those cuts, and the cumulative cut pool eventually covers the region where every reservoir’s storage carries a non-zero marginal value.


The relative gap

gapk=zˉkzkmax(1,zˉk)\text{gap}^k = \frac{\bar z^k - \underline z^k}{\max(1, |\bar z^k|)}

narrows as cuts accumulate. Convergence is faster than for one reservoir because the four-reservoir cuts contain four times as much information per iteration, but the state space being explored is also larger; the practical iteration counts depend on the demand-to-inflow ratios, the reservoir capacities, and the variance of the inflows. The stopping rule fires when the gap falls below the configured tolerance or the iteration limit is reached — see Stopping Rules for the available criteria.


The toy walkthrough above keeps to four reservoirs at four buses with no transmission. It does not cover:

  • Transmission networks: the actual examples/4ree/ case has two transmission lines (SUDESTE–SUL, SUDESTE–NORDESTE) that allow cheap thermal at one bus to serve another. With transmission, the load balance becomes a network constraint and the per-bus thermal share depends on line capacities and exchange costs. The cut structure does not change — still one storage coefficient per reservoir — but the per-opening LPs are no longer separable across buses.
  • Cascade coupling: in branched cascades, downstream reservoirs receive upstream releases via a water-balance term vh=vhin+ahqhsh+uupstream(qu+su)v_h = v^{in}_h + a_h - q_h - s_h + \sum_{u \in \text{upstream}}(q_u + s_u); the storage cut coefficient at upstream plants then carries the expected downstream value of released water. See System Elements for cascade topology and Cut Management for how cascade sensitivities propagate through the pinned-column reduced cost.
  • FPHA production model: nonlinear head-dependent productivity approximated by piecewise-linear hyperplanes; one of the planes binds at the optimum and contributes to the storage cut coefficient. See Hydro Production Models.
  • Spatial inflow correlation: when a correlation.json file is supplied, the per-hydro innovations are drawn from a correlated multivariate normal via spectral factorisation. See PAR Inflow Model section 8.
  • Autoregressive inflow memory: a PAR(p) model with p1p \geq 1 adds one lag state variable per hydro per lag and one AR-lag cut coefficient per lag; the cut becomes a hyperplane in storage and lag state space.
  • Risk measures: CVaR weighting that shifts cut aggregation probabilities away from uniform p=1/Np = 1/N toward worst scenarios, raising cut intercepts and slopes in the dry direction. See Risk Measures.
  • Cyclic policy graphs: the four-stage horizon terminates without a cut linking back to stage 1. Cyclic-mode SDDP uses periodic policy graphs where the last stage’s cuts feed into the first stage. See Horizon Modes.
  • Multi-resolution coupling: weekly intra-stage blocks embedded in a monthly horizon, with policy transfer between resolutions. See Multi-Resolution Studies.

  • Toy Single-Reservoir Walkthrough — Single-reservoir baseline; core SDDP loop, 0-order inflow, forward/backward/cut in the simplest setting
  • LP Formulation — Complete stage LP, column and row layout, column-bound state pinning, reduced-cost extraction
  • System Elements — Hydro plant element, cascade topology (not exercised here), water-balance convention, FPHA overview
  • Hydro Production Models — Constant-productivity (used here) and FPHA hyperplane fitting; impact on Benders cut coefficients
  • PAR Inflow Model — Inflow model definition; the p=0p = 0 degenerate case (white noise) used here; spatial correlation factorisation for multivariate cases
  • Cut Management — Dual extraction, per-opening intercepts, single-cut aggregation; sign convention πv=Q/v^\pi^v = \partial Q/\partial \hat v
  • Risk Measures — CVaR definition, EAVaR convex combination, risk-adjusted aggregation weights
  • Horizon Modes — Finite vs cyclic policy graphs and the season-indexed cut pool