Skip to content

LP Formulation

This spec presents the complete stage subproblem LP for the Cobre SDDP solver: the objective function with its cost taxonomy, all constraint families, slack/penalty variables, and the Benders cut interface to the future cost function. It uses the parallel blocks formulation by default.

Reading order: SDDP algorithmsystem elementsthis specequipment formulations

For what each physical element represents and its decision variables, see system elements. For variable naming conventions and index sets, see notation conventions.

The objective function includes three categories of penalty/cost terms plus resource costs. This taxonomy aligns with Penalty System. Understanding these categories is essential for setting appropriate parameter values and interpreting solution reports.

1.1 Resource Costs (Actual Operating Expenses)

Section titled “1.1 Resource Costs (Actual Operating Expenses)”

Resource costs represent actual generation or contractual expenditures:

CostSymbolUnitsObjective Term
Thermal generationcj,sthc^{th}_{j,s}$/MWhj,k,sτkcj,sthgj,k,s\sum_{j,k,s} \tau_k \cdot c^{th}_{j,s} \cdot g_{j,k,s}
Contract dispatchccctrc^{ctr}_c$/MWhc,kτkccctrχc,k\sum_{c,k} \tau_k \cdot c^{ctr}_c \cdot \chi_{c,k}

Contract prices are positive for imports (cost) and negative for exports (revenue), so a single summation naturally handles both directions. See system elements §8 for the unidirectional contract model.

1.2 Category 1: Recourse Slacks (LP Feasibility)

Section titled “1.2 Category 1: Recourse Slacks (LP Feasibility)”

These ensure the SDDP algorithm has relatively complete recourse — every subproblem must be feasible regardless of scenario realization:

PenaltySymbolUnitsPurpose
Deficitcb,sdefc^{def}_{b,s}$/MWhValue of unserved energy (piecewise)
Excess generationcbexcc^{exc}_b$/MWhAbsorb uncontrollable surplus

1.3 Category 2: Constraint Violation Penalties (Policy Shaping)

Section titled “1.3 Category 2: Constraint Violation Penalties (Policy Shaping)”

These provide slack for physical or operational constraints that may be impossible to satisfy under extreme conditions. Their cost must be high enough to affect the value function in earlier stages:

PenaltySymbolUnitsViolated Constraint
Storage below minimumchsvc^{sv-}_h$/hm³vhVhv_h \geq \underline{V}_h
Filling target shortfallchfillc^{fill}_h$/hm³vhVttargetv_h \geq V^{\text{target}}_t (per-stage filling floor)
Turbined flow minimumchtvc^{tv-}_h$/(m³/s·h)qh,kQhq_{h,k} \geq \underline{Q}_h
Outflow minimumchovc^{ov-}_h$/(m³/s·h)oh,kOho_{h,k} \geq \underline{O}_h
Outflow maximumchov+c^{ov+}_h$/(m³/s·h)oh,kOˉho_{h,k} \leq \bar{O}_h
Generation minimumchgvc^{gv-}_h$/MWhgh,kGhg_{h,k} \geq \underline{G}_h
Evaporation violationchevc^{ev}_h$/(m³/s·h)Evaporation within physical limits
Withdrawal violationchwvc^{wv}_h$/(m³/s·h)Water withdrawal commitment (bidirectional: under/over)

1.4 Category 3: Regularization Costs (Solution Guidance)

Section titled “1.4 Category 3: Regularization Costs (Solution Guidance)”

Small costs that guide the solver toward physically preferred solutions when the LP would otherwise be indifferent. Must be orders of magnitude smaller than any economic cost:

CostSymbolUnitsPurpose
Spillagechspillc^{spill}_h$/(m³/s·h)Prefer turbining over spilling when indifferent
FPHA turbined flowchfphac^{fpha}_h$/(m³/s·h)Prevent interior FPHA solutions (FPHA-only)
Diversionchdivc^{div}_h$/(m³/s·h)Prefer main channel flow
Curtailmentcrcurtc^{curt}_r$/MWhPrioritize using available NCS generation
Exchangecexchc^{exch}_\ell$/MWhPrevent unnecessary power flows

The following ordering must be maintained (from highest to lowest):

csv>cdef>ctv,cov±,cgv,cev,cwv>cth,cctr>cspill,cfpha,cdiv,ccurt,cexchc^{sv-} > c^{def} > c^{tv-}, c^{ov\pm}, c^{gv-}, c^{ev}, c^{wv} > c^{th}, c^{ctr} > c^{spill}, c^{fpha}, c^{div}, c^{curt}, c^{exch}

with the filling-target penalty pinned below deficit on a separate rung: cdef>cfillc^{def} > c^{fill}.

  1. Storage violation (csvc^{sv-}): Highest penalty — reservoir below dead volume risks dam safety, so it must exceed deficit
  2. Deficit (cdefc^{def}): Value of lost load; exceeds any generation cost
  3. Constraint violations (ctvc^{tv-}, cov±c^{ov\pm}, cgvc^{gv-}, cevc^{ev}, cwvc^{wv}): Exceed typical marginal cost but allow violation when physically necessary
  4. Resource costs (cthc^{th}, cctrc^{ctr}): Market-based or fuel-based
  5. Regularization (cspillc^{spill}, cfphac^{fpha}, cdivc^{div}, ccurtc^{curt}, cexchc^{exch}): Near-zero
  6. Filling target (cfillc^{fill}): Pinned below deficit — a commissioning fill schedule is not defended as hard as load serving. Its position relative to the operational-constraint tier (item 3) is left to study calibration.

For the full penalty specification, cascade resolution, and stage-varying overrides, see Penalty System.

The complete stage objective is:

min  Cresourcethermal, contracts+Crecoursedeficit, excess+Cviolationconstraint slacks+Cregularizationspillage, exchange, ...+θ\min \; \underbrace{C^{resource}}_{\text{thermal, contracts}} + \underbrace{C^{recourse}}_{\text{deficit, excess}} + \underbrace{C^{violation}}_{\text{constraint slacks}} + \underbrace{C^{regularization}}_{\text{spillage, exchange, ...}} + \theta

where each component is summed over blocks with appropriate time weighting:

Ccomponent=kKτk(cost terms for component)C^{component} = \sum_{k \in \mathcal{K}} \tau_k \cdot (\text{cost terms for component}) minkKτk[jTscj,sthgj,k,sThermal cost+cCccctrχc,kContract cost\min \sum_{k \in \mathcal{K}} \tau_k \Bigg[ \underbrace{\sum_{j \in \mathcal{T}} \sum_s c^{th}_{j,s} g_{j,k,s}}_{\text{Thermal cost}} + \underbrace{\sum_{c \in \mathcal{C}} c^{ctr}_c \chi_{c,k}}_{\text{Contract cost}} +bBsSbcb,sdefδb,k,sDeficit (piecewise)+bBcbexcϵb,kExcess + \underbrace{\sum_{b \in \mathcal{B}} \sum_{s \in \mathcal{S}_b} c^{def}_{b,s} \delta_{b,k,s}}_{\text{Deficit (piecewise)}} + \underbrace{\sum_{b \in \mathcal{B}} c^{exc}_b \epsilon_{b,k}}_{\text{Excess}} +hHchspillsh,k+hHfphachfphaqh,k+hHchdivuh,kHydro regularization + \underbrace{\sum_{h \in \mathcal{H}} c^{spill}_h s_{h,k} + \sum_{h \in \mathcal{H}^{fpha}} c^{fpha}_h q_{h,k} + \sum_{h \in \mathcal{H}} c^{div}_h u_{h,k}}_{\text{Hydro regularization}} +rRcrcurt(Argr,knc)Curtailment (regularization)+lLclexch(fl,k++fl,k)Exchange (regularization) + \underbrace{\sum_{r \in \mathcal{R}} c^{curt}_r (A_r - g^{nc}_{r,k})}_{\text{Curtailment (regularization)}} + \underbrace{\sum_{l \in \mathcal{L}} c^{exch}_l (f^+_{l,k} + f^-_{l,k})}_{\text{Exchange (regularization)}} +Constraint violation penaltiesSee §9] + \underbrace{\text{Constraint violation penalties}}_{\text{See §9}} \Bigg] +hH[chsvσhv+chfillσhfill]Storage violations (not per-block)+  θ+ \underbrace{\sum_{h \in \mathcal{H}} \Big[ c^{sv-}_h \sigma^{v-}_h + c^{fill}_h \sigma^{fill}_h \Big]}_{\text{Storage violations (not per-block)}} + \; \theta

For each bus bBb \in \mathcal{B} and block kKk \in \mathcal{K}:

hHbgh,k+jTbsgj,k,s+rRbgr,knc+cCbimpχc,k\sum_{h \in \mathcal{H}_b} g_{h,k} + \sum_{j \in \mathcal{T}_b} \sum_s g_{j,k,s} + \sum_{r \in \mathcal{R}_b} g^{nc}_{r,k} + \sum_{c \in \mathcal{C}^{imp}_b} \chi_{c,k} +l:target=bηlfl,k++l:source=bηlfl,k+ \sum_{l: \text{target}=b} \eta_l f^+_{l,k} + \sum_{l: \text{source}=b} \eta_l f^-_{l,k} l:source=bfl,k+l:target=bfl,kcCbexpχc,kjPbγjpj,k+sSbδb,k,sϵb,k=Db,k- \sum_{l: \text{source}=b} f^+_{l,k} - \sum_{l: \text{target}=b} f^-_{l,k} - \sum_{c \in \mathcal{C}^{exp}_b} \chi_{c,k} - \sum_{j \in \mathcal{P}_b} \gamma_j p_{j,k} + \sum_{s \in \mathcal{S}_b} \delta_{b,k,s} - \epsilon_{b,k} = D_{b,k}

Dual variable: πb,klb\pi^{lb}_{b,k} (marginal cost of energy at bus bb, block kk, in $/MW; divide by τk\tau_k for $/MWh — see Variable Units Convention)

For the physical meaning of each element in the balance, see system elements.

For each hydro hHh \in \mathcal{H} (parallel blocks formulation):

vh=vhin+bh,1in+ζ[ah+kKwk(iUh(qi,k+si,k+ui,k)Inflow from upstream+i:div=hui,kDiverted inflow+j:dest=hpj,kPumped inflowv_h = v^{in}_h + b^{\mathrm{in}}_{h,1} + \zeta \Bigg[ a_h + \sum_{k \in \mathcal{K}} w_k \Big( \underbrace{\sum_{i \in \mathcal{U}_h} (q_{i,k} + s_{i,k} + u_{i,k})}_{\text{Inflow from upstream}} + \underbrace{\sum_{i: \text{div}=h} u_{i,k}}_{\text{Diverted inflow}} + \underbrace{\sum_{j: \text{dest}=h} p_{j,k}}_{\text{Pumped inflow}} qh,ksh,kuh,kOutflowseh,kEvaporationj:src=hpj,kPumped outflow)rhWithdrawal (signed target)] - \underbrace{q_{h,k} - s_{h,k} - u_{h,k}}_{\text{Outflows}} - \underbrace{e_{h,k}}_{\text{Evaporation}} - \underbrace{\sum_{j: \text{src}=h} p_{j,k}}_{\text{Pumped outflow}} \Big) - \underbrace{r_h}_{\text{Withdrawal (signed target)}} \Bigg]

where:

  • vhinv^{in}_h = incoming storage LP variable, pinned to the previous stage’s value via column bounds (§4a)
  • bh,1inb^{\mathrm{in}}_{h,1} = in-transit volume maturing at the current stage on a declared travel-time arc into hh (hm³), added directly as a stage-level inflow (already a volume, so it sits outside the ζ\zeta conversion). When a cascade arc carries a travel time, the upstream release is delayed through this term instead of entering the same-stage upstream inflow sum above; see §5d. Absent (zero) for instantaneous arcs
  • aha_h = incremental inflow (from AR model, see PAR(p) inflow model); equivalently zhz_h from the z-inflow constraint (§5b)
  • rhr_h = water withdrawal target (m³/s), a signed fixed RHS parameter from water_withdrawal_m3s (not a per-block LP decision variable). rh>0r_h > 0 schedules a consumptive removal; rh<0r_h < 0 schedules an inter-basin return/addition that adds water to the reservoir. The realized withdrawal removed from the reservoir is Rh=rhσhw+σhw+R_h = r_h - \sigma^{w-}_h + \sigma^{w+}_h; see §9 for the slack bounds that prevent the realized flow from flipping sign. Withdrawal is applied at the stage level, outside the per-block summation
  • eh,ke_{h,k} = signed net evaporation flow (m³/s): positive values represent net evaporative loss subtracted from storage; negative values represent net rainfall input on the lake surface that adds to storage through the same coefficient (the leading - sign on eh,ke_{h,k} flips the contribution automatically — see penalty system §5)
  • wk=τk/jτjw_k = \tau_k / \sum_j \tau_j = block weight
  • ζ=0.0036×kτk\zeta = 0.0036 \times \sum_k \tau_k = time conversion factor

Dual variable: πhwb\pi^{wb}_h (water value — captures the marginal value of incoming storage as seen through the hydro balance, but is not used directly as a cut coefficient; the cut coefficient comes from the reduced cost of the pinned incoming-storage column, see §4a and cut management)

The water balance (§4), FPHA hyperplanes (§6), and generic constraints (§10) all involve the incoming storage value v^h\hat{v}_h. Rather than embedding v^h\hat{v}_h as a constant in the RHS of each of these constraints (which would require collecting duals from all of them to compute cut coefficients), Cobre introduces an explicit incoming storage LP variable vhinv^{in}_h that every such constraint references, and pins it to the trial value.

For each hydro hHh \in \mathcal{H}, the incoming-storage column (storage_in, §4b) is pinned by setting equal lower and upper column bounds:

vhin=vˉhin=v^h\underline{v}^{in}_h = \bar{v}^{in}_h = \hat{v}_h

where:

  • vhinv^{in}_h = LP variable representing the incoming storage for hydro hh
  • v^h\hat{v}_h = incoming state value (end-of-stage storage from the previous stage), written into both bounds per scenario via bound patching

The variable vhinv^{in}_h then appears as an LP variable (not a constant) in all constraints that depend on incoming storage: the water balance (§4), the FPHA average storage computation (§6), and any generic constraints (§10) that reference incoming storage.

Cut coefficient: the storage cut coefficient πhv\pi^v_h is the reduced cost of the pinned storage_in column (unscaled by its prescaler column factor — see §12 and cut management). No fixing-constraint dual is involved.

Why this design: By LP duality, when a column is pinned at x=xˉ\underline{x} = \bar{x} its reduced cost equals the sensitivity Qt/v^h\partial Q_t / \partial \hat{v}_h of the optimal value to the pinned bound — exactly the multiplier the equivalent equality row vhin=v^hv^{in}_h = \hat{v}_h would have carried (KKT parity). This sensitivity automatically accounts for all downstream effects through water balance, FPHA, and generic constraints, so a single reduced-cost value suffices — no combination of duals from multiple constraint types is needed. This is the same “fishing” technique used by SDDP.jl, realised through column bounds rather than a fixing row, and is analogous to how the AR lags (section 5a) are pinned for inflow history.

Column count: NN pinned incoming-storage columns, where N=HN = |\mathcal{H}| is the number of operating hydros. There is no corresponding fixing-row block.

LP column layout — state variables (storage, AR lags) first for contiguous reduced-cost extraction, dispatch variables per block, and θ (future cost) last for the Benders cuts, assembled with the constraint-row families below.

Decision-variable columns — fixed contiguous order in xConstraint-row families1 · State (coupling)vₕ storage · aₕ,ℓ AR lagspinned by column bounds; reduced costs → π2 · Dispatch (per block k)flow · hydro · turbined · spillthermal · NCS · deficit3 · Futureθ future cost (bounded by cuts)Load balance — per bus, per blockWater balance — per hydro, per blockFixing — incoming-state couplingBenders cuts: θ ≥ α + πᵀx assembled into the stage LP

The stage LP uses a fixed column and row layout that places state variables first, followed by auxiliary and equipment columns. State is pinned by column bounds on the incoming-state columns (§4a, §5a, §5c, §5d), and cut coefficients are read as the reduced costs of those columns — so the fixed column order, not a fixed row order, is what enables contiguous coefficient extraction. With N=HN = |\mathcal{H}| hydros, LL = maximum AR order, AA = number of anticipated thermals, K=Kmax=maxiKiK = K_{\max} = \max_i K_i, and BB = total in-transit bucket count (the sum, over receiving plants, of each plant’s maturity-lag depth — §5d):

Column layout:

RegionCountDescription
storageNNOutgoing storage volumes (state) — first
inflow_lagsNLNLAR lag variables (state) — after storage
transit_buckets_outBBOutgoing in-transit bucket volumes (state, plant-major lag-minor) — after lags
anticipated_stateKAK ARing-buffer slots for anticipated thermals (state, slot-major plant-minor)
z_inflowNNRealized inflow (auxiliary, not state) — after the state block
storage_inNNIncoming storage volumes (auxiliary, for §4a) — after z-inflow
transit_buckets_inBBIncoming in-transit bucket volumes (auxiliary, pinned for §5d) — after storage_in
theta11Future cost variable — last of the state prefix

Equipment columns (turbine, spillage, diversion, thermal, anticipated-decision and anticipated-state-out, line flows, deficit, excess, slacks) follow immediately after theta. The transit_buckets_out block is an identity-resolved state carrier (like storage, and mirroring the anticipated-state-out carrier of §5c): it holds the volume still in transit on each cascade arc, defined by in-LP ring shift and deposit rows rather than pinned, and the transit_buckets_in block is the matching pinned incoming copy read for the delayed-arrival water-balance entry and the cut coefficient (§5d). Two auxiliary blocks are reserved for anticipated thermals: AA anticipated-decision columns dtid^i_t carrying the commitment placed at this stage for delivery KiK_i stages later, and AA anticipated-state-out columns ytiy^i_t used to decouple the post-shift state from the decision-write coefficient — see §5c.

The z_inflow region holds one free column per hydro representing the total realized inflow zh=ahz_h = a_h (m³/s) for each hydro at the current stage. These are auxiliary columns (zero objective cost, unbounded) whose primal values after solving give the realized inflow. They participate in the water balance (§4) and are defined by the z-inflow constraints (§5b).

Row layout (equality-constraint prefix):

Because state is pinned by column bounds (§4a, §5a, §5c, §5d) rather than by equality rows, there are no state-fixing rows: the former storage_fixing (NN), lag_fixing (NLNL), transit_bucket_fixing (BB), and anticipated_state_fixing (KAKA) row blocks are permanent empty sentinels (zero rows). The equality-constraint prefix therefore begins directly with the z-inflow definitions:

RegionCountDescription
z_inflowNNRealized-inflow definition constraints (§5b) — first equality block

Equipment rows (water balance, load balance, FPHA, evaporation, outflow bounds, anticipated-fishing and anticipated-state-out equalities, transit-bucket shift and deposit definitions, generic constraints, etc.) follow after the z-inflow rows.

Cut coefficients are not read from a contiguous dual slice over a fixing-row prefix. Instead, each incoming-state coordinate is pinned on its own LP column — storage_in for storage, inflow_lags for AR lags, transit_buckets_in for in-transit buckets, anticipated_state for anticipated-thermal slots — and its cut coefficient is the reduced cost of that column (§4a, cut management). The map from a state coordinate to its pinned column is fixed (state_to_lp_incoming_column), and each of these incoming-state column regions is contiguous, so all storage, inflow-lag, in-transit bucket, and anticipated-state coefficients are still gathered by reading a few contiguous slices — of the reduced-cost vector rather than the dual vector.

Worked example (N=3N = 3, L=2L = 2, A=0A = 0, B=0B = 0 — no travel-time arcs): the storage region holds 3 columns, the AR lag region holds 6 (3 hydros × 2 lags), the z-inflow region holds 3, and the incoming-storage region holds 3, so theta is the 16th column. The state count (outgoing storage + AR lags) is N(1+L)=9N(1 + L) = 9. With B=0B = 0 the layout is byte-for-byte the bucket-free layout.

The incremental inflow aha_h is determined by the PAR(p) autoregressive model:

ah=(μt=1Phψμt)deterministic base+=1Phψah,lag contribution+σtηtstochastic innovationa_h = \underbrace{\left( \mu_t - \sum_{\ell=1}^{P_h} \psi_\ell \mu_{t-\ell} \right)}_{\text{deterministic base}} + \underbrace{\sum_{\ell=1}^{P_h} \psi_\ell \cdot a_{h,\ell}}_{\text{lag contribution}} + \underbrace{\sigma_t \cdot \eta_t}_{\text{stochastic innovation}}

To maintain the Markov property, lagged inflows ah,a_{h,\ell} are promoted to state variables pinned by column bounds — see section 5a below.

See PAR(p) inflow model for the complete PAR(p) model specification.

The AR dynamics equation (section 5) uses lagged inflows ah,a_{h,\ell} as LP variables. To maintain the Markov property in the SDDP decomposition, each lag variable is pinned to its incoming state value via equal lower and upper column bounds on the inflow_lags column. This binds the lag variables to the known incoming state, and the reduced cost of each pinned column provides the cut coefficient πh,lag\pi^{lag}_{h,\ell} for the corresponding inflow-lag dimension of the Benders cuts (section 11). Whether these lag dimensions actually enter the cut is governed by the stage’s state_variables selection (which defaults to storage-only): when inflow_lags is disabled the lag columns are still pinned for the AR dynamics, but their reduced costs are projected out of the cut, yielding a storage-only cut even under a PAR(pp) fit — see cut management.

For each hydro hHh \in \mathcal{H} and each lag {0,,L1}\ell \in \{0, \ldots, L-1\}:

ah,=aˉh,=a^h,\underline{a}_{h,\ell} = \bar{a}_{h,\ell} = \hat{a}_{h,\ell}

where:

  • ah,a_{h,\ell} = LP variable representing the inflow at lag \ell for hydro hh
  • a^h,\hat{a}_{h,\ell} = incoming state value (inflow observation from \ell stages ago), written into both bounds via bound patching
  • LL = maximum AR order across all hydros (uniform lag storage convention)

Column count: N×LN \times L pinned lag columns, where N=HN = |\mathcal{H}| is the number of operating hydros and LL is the system-wide maximum lag. All hydros store LL lags regardless of their individual AR order PhP_h; hydros with Ph<LP_h < L have zero-valued AR coefficients (ψ=0\psi_\ell = 0 for >Ph\ell > P_h) in the dynamics equation, but their lag columns are still present and pinned. This uniform layout keeps the inflow_lags columns contiguous, so all lag cut coefficients are read in a single slice of the reduced-cost vector (section 4b). There is no corresponding lag-fixing row block.

Cut coefficient: πh,lag\pi^{lag}_{h,\ell} (marginal value of inflow history at lag \ell for hydro hh) is the reduced cost of the pinned inflow_lags column, unscaled by its prescaler column factor (§12) — see cut management.

5b. Realized-Inflow Definition Constraints (z-inflow)

Section titled “5b. Realized-Inflow Definition Constraints (z-inflow)”

For each hydro hHh \in \mathcal{H}, the LP includes an auxiliary variable zhz_h representing the total realized inflow (m³/s) at the current stage. These variables are defined by equality constraints that combine the deterministic base, lag contributions, and stochastic noise:

zh=bh,m(t)+=1Phψm(t),ah,+σm(t)ηtz_h = b_{h,m(t)} + \sum_{\ell=1}^{P_h} \psi_{m(t),\ell} \cdot a_{h,\ell} + \sigma_{m(t)} \cdot \eta_t

where:

  • zhz_h = LP variable representing the realized inflow for hydro hh (free column, zero cost)
  • bh,m(t)b_{h,m(t)} = deterministic base (precomputed from seasonal means and AR coefficients — see PAR(p) model §7.4)
  • ψm(t),\psi_{m(t),\ell} = original-unit AR coefficients (constraint matrix entries, set once at LP construction)
  • ah,a_{h,\ell} = LP variables for lagged inflows (state variables, fixed by §5a)
  • σm(t)ηt\sigma_{m(t)} \cdot \eta_t = noise innovation (patched into the constraint RHS per scenario)

The z-inflow variable zhz_h then enters the water balance constraint (§4) in place of the raw inflow term aha_h, and its primal value after solving gives the realized inflow for reporting and simulation extraction.

The z-inflow columns sit between the AR lag columns and the incoming storage columns in the column layout (section 4b). Because state is pinned by column bounds rather than fixing rows, their constraint rows form the first equality block (section 4b). The RHS is patched per scenario with bh,m(t)+σm(t)ηtb_{h,m(t)} + \sigma_{m(t)} \cdot \eta_t, where ηt\eta_t is the effective noise (possibly clamped for inflow non-negativity — see Inflow Non-Negativity). These are not state variables and do not contribute to cut coefficients.

Constraint count: NN total constraints, where N=HN = |\mathcal{H}| is the number of operating hydros. See section 4b for the row layout.

Anticipated thermals (see System Elements §4) introduce a per-plant ring buffer of KiK_i pending commitments and a per-stage commitment column. The lead KiK_i is the integer stage lead resolved from the plant’s lead_stages (a stage count) or lead_time_hours (a physical duration end-anchored on the stage calendar); every commitment is bounded, costed, and commissioning-gated at its delivery stage t+Kit + K_i, not the decision stage. The incoming ring-buffer state is pinned by column bounds (like all other state, §4a); two constraint blocks then couple the remaining variables. The layout is engineered so that the reduced cost on slot 0 of the pinned anticipated-state column at stage t+1t + 1 propagates back to the predecessor’s commitment column via the standard SDDP cut machinery without any decision-side coefficient corrupting the routing.

State pinning (column bounds, one per (slot, plant))

Section titled “State pinning (column bounds, one per (slot, plant))”

For each plant i{0,,A1}i \in \{0, \ldots, A - 1\} and slot s{0,,K1}s \in \{0, \ldots, K - 1\}, the anticipated-state slot column is pinned by equal column bounds:

xs,i,ta  =  xˉs,i,ta  =  x^s,i,ta\underline{x}^{\mathrm{a}}_{s, i, t} \;=\; \bar{x}^{\mathrm{a}}_{s, i, t} \;=\; \widehat{x}^{\mathrm{a}}_{s, i, t}

The value x^s,i,ta\widehat{x}^{\mathrm{a}}_{s, i, t} is the incoming state from the previous stage’s ring-buffer shift (or, at t=0t = 0, the seed past_anticipated_commitments[i].values_mw[s]). The slot is pinned by its column bounds alone; no decision-write coefficient appears anywhere on the slot column. The cut subgradient with respect to the incoming-state coordinate, Qt/x^s,i,ta\partial Q_t / \partial \widehat{x}^{\mathrm{a}}_{s, i, t}, is the reduced cost of the pinned slot column (§4a). Padding slots sKis \geq K_i are pinned to zero by the same bounds; their reduced cost is zero because the slot carries no information.

Fishing equality (one row per anticipated plant, every stage)

Section titled “Fishing equality (one row per anticipated plant, every stage)”

For each plant ii and every stage t[0,T1]t \in [0, T - 1], the per-block generation of plant ii is bound to the matured commitment in slot 0:

b=0B1hbgi,b,t    Htx0,i,ta  =  0\sum_{b = 0}^{B - 1} h_b \cdot g_{i, b, t} \;-\; H_t \cdot x^{\mathrm{a}}_{0, i, t} \;=\; 0

where hbh_b is the block-bb duration and Ht=bhbH_t = \sum_b h_b. The row is active at every study stage; at t<Kit < K_i the slot-0 value comes from the seed values_mw[t] and the LP cannot freely choose the per-block generation. From tKit \geq K_i onward, slot 0 carries a past LP decision delivered via the ring buffer.

State-out equality (one row per active plant)

Section titled “State-out equality (one row per active plant)”

For each plant ii active at stage tt (i.e., t+Ki<Tt + K_i < T), one auxiliary row pins the anticipated-state-out column ytiy^i_t to the decision dtid^i_t:

yti    dti  =  0y^i_t \;-\; d^i_t \;=\; 0

The ring-buffer shift between stages uses ytiy^i_t — not dtid^i_t — as the value written into slot Ki1K_i - 1 of the next stage’s incoming state. The auxiliary yy column carries zero objective cost and serves only as the “carrier” that decouples the post-shift state from the decision column. Without this decoupling, the decision column dtid^i_t would feed directly into the slot whose pinned reduced cost the next stage’s cut reads back, corrupting the subgradient routing at Ki=1K_i = 1 (slot 0 = slot Ki1K_i - 1 collision).

Two terms enter the objective for each anticipated plant:

i=0A1ci(t+Ki)Ht+Kidt+KiNPVdti    ideliver(t)bci(t)hbdtNPVgi,b,t\sum_{i = 0}^{A - 1} c_i(t + K_i) \cdot H_{t + K_i} \cdot d^{\mathrm{NPV}}_{t + K_i} \cdot d^i_t \;-\; \sum_{i \in \mathrm{deliver}(t)} \sum_b c_i(t) \cdot h_b \cdot d^{\mathrm{NPV}}_t \cdot g_{i, b, t}

The first sum is the commitment cost discounted to the delivery stage t+Kit + K_i. The second sum subtracts the standard per-block thermal cost at every delivery stage so the same MWh is not charged twice — once through the matured commitment and once through the per-block dispatch. Anticipated-state columns and anticipated-state-out columns carry zero objective cost; they are pure carriers of state. In run cost output this commitment term is reported as its own anticipated_thermal_cost category (zero when no anticipated plants are present), distinct from the per-block thermal_cost, so the named cost categories sum to the stage’s immediate cost.

When the backward pass returns a subgradient on slot (s,i)(s, i) of stage t+1t + 1 — read as the reduced cost of that pinned slot column — the cut-row builder maps it to a column in the predecessor’s stage problem as follows:

  • s+1=Kis + 1 = K_i (slot 0 viewed from the next stage equals slot Ki1K_i - 1 viewed from this stage): the coefficient targets the predecessor’s commitment column dtid^i_{t} directly. This is the only branch that fires for Ki=1K_i = 1.
  • s+1<Kis + 1 < K_i: the coefficient targets the predecessor’s outgoing-state slot s+1s + 1, which holds the same commitment one stage earlier in its journey through the ring buffer.
  • sKis \geq K_i (padding): identity remap; the reduced cost is structurally zero so the cut coefficient on the padded slot does not propagate any sensitivity.

The recursion guarantees that, no matter how many stages elapse between commitment and delivery, the marginal cost of a future obligation reaches the original did^i column it should price.

5d. Water Travel Time (In-Transit Buckets)

Section titled “5d. Water Travel Time (In-Transit Buckets)”

When an upstream release takes appreciable time to travel down the cascade, the water leaving a plant this stage does not reach its downstream neighbour in the same stage. Cobre models this as an augmented in-transit state: the volume still in transit on a cascade arc is carried through the Bellman recursion as extra state coordinates, exactly like storage (§4a) and AR lags (§5a). This subsection formulates that state, its pinning, the delayed-arrival water-balance entry, the ring that advances it, its cut coefficient, and the horizon limitation.

Scope. A hydro hh declares a travel-time arc when its travel_time_hours is present and strictly positive and it has a downstream plant; the diversion and pumping arcs carry no travel time (main cascade arc only). An absent or zero travel time is an instantaneous transfer — the upstream release enters the downstream water balance in the same stage (§4) and no state is added.

For each receiving (downstream) plant ii that has at least one incoming travel-time arc, the in-transit water destined for ii is discretized into maturity lags d{1,,Li}d \in \{1, \ldots, L_i\}. The bucket bi,db_{i,d} (hm³) holds the aggregate volume — summed over every upstream arc feeding ii — that matures into plant ii‘s reservoir d1d - 1 stages after the current one. Lag d=1d = 1 matures at the current stage; lag d=Lid = L_i is the freshest deposit, furthest from delivery. The per-plant depth LiL_i is the deepest maturity lag any arc into ii can reach on the stage calendar. The confluence of several arcs into one plant collapses into this single aggregated bucket block.

The buckets extend the state vector. With B=iLiB = \sum_i L_i the total bucket count, the state dimension is

nstate=N(1+L)+B+AKmaxn_{\text{state}} = N(1 + L) + B + A \, K_{\max}

The bucket block sits after the AR inflow lags and before the anticipated-thermal slots in the canonical state order (§4b). Buckets are ordered canonically by (plant,lag)(\text{plant}, \text{lag}) — the receiving plant in the same (operational_start_date,id)(\texttt{operational\_start\_date}, \texttt{id}) order every state block uses, then ascending maturity lag. When no arc is declared, B=0B = 0 and the layout reproduces the bucket-free state byte-for-byte.

Like every other incoming state coordinate, each incoming bucket is carried on its own LP column (transit_buckets_in, §4b) and pinned to its trial value by equal lower and upper column bounds:

bi,din=bˉi,din=b^i,d\underline{b}^{\,\mathrm{in}}_{i,d} = \bar{b}^{\,\mathrm{in}}_{i,d} = \hat{b}_{i,d}

where b^i,d\hat{b}_{i,d} is the incoming in-transit volume carried from the previous stage’s ring shift (or, at the first stage, the seed derived from past_defluences — see system elements §5 and the hydro Implementation notes). The reduced cost of the pinned bucket column is the cut coefficient for that in-transit dimension (see below) — the same regime used for storage (§4a) and AR lags (§5a). No fixing row is involved; the transit_bucket_fixing block is a permanent empty sentinel like the other state blocks (§4b).

The bucket maturing at the current stage, bi,1inb^{\mathrm{in}}_{i,1}, delivers its volume into receiving plant ii‘s water balance (§4). Because the bucket is already an accumulated volume (hm³), it enters the balance directly — outside the ζ\zeta flow-to-volume conversion — with a 1.0-1.0 coefficient in the all-variables-on-the-LHS form:

viviinbi,1inζ[]=0v_i - v^{\mathrm{in}}_i - b^{\mathrm{in}}_{i,1} - \zeta\big[\,\cdots\,\big] = 0

Equivalently, bi,1inb^{\mathrm{in}}_{i,1} is a stage-level inflow added to the reservoir. Because the confluence of several upstream arcs is already summed inside the single state coordinate, exactly one delayed-arrival entry appears per receiving plant.

Under the parallel-blocks formulation the maturing bucket is a single stage-level entry. Under the chronological-blocks formulation, the same volume is delivered across the arrival stage’s own blocks, weighted by a fixed arrival density ϕi,k0\phi_{i,k} \ge 0 with kϕi,k=1\sum_k \phi_{i,k} = 1, resolved against the arrival stage’s block partition. The arrival density is a single fixed split per maturing bucket — it does not depend on which source block released the water, an accepted modeling bound when the release and arrival stages partition their hours differently.

Once a travel-time arc is declared for an upstream plant, that plant’s release no longer enters the downstream water balance as the same-stage upstream term of §4; it is routed into the buckets at release and re-enters only as this delayed-arrival term at maturity.

The buckets advance through the recursion with the same generic DeliveryRing primitive that carries the anticipated-thermal ring (§5c): a slot-major, lane-minor grid of BB outgoing and BB incoming columns, one lane per receiving plant. The ring performs a Markov-1 slot advance — the water at maturity lag d+1d + 1 at one stage is at maturity lag dd at the next, one step closer to delivery. Within each stage this is encoded by in-LP shift rows binding the outgoing carrier to the incoming state one slot deeper,

bi,doutbi,d+1inb^{\mathrm{out}}_{i,d} \equiv b^{\mathrm{in}}_{i,d+1}

(one row per interior slot), together with the cross-stage identity that carries the outgoing state into the next stage’s incoming state — never an out-of-LP shift. The outgoing bucket state bi,doutb^{\mathrm{out}}_{i,d} is resolved by identity — it is a genuine LP column, part of nstaten_{\text{state}} — and its freshest slots receive the current stage’s upstream releases through per-arc deposit entries: each release, scaled by the fraction of it maturing at each reachable lag, is written into the corresponding outgoing bucket slots. This is the direct analogue of the anticipated ring’s decision-write, reusing the same skeleton.

Because the incoming bucket column is pinned at equal bounds, its reduced cost is the sensitivity Qt/b^i,d\partial Q_t / \partial \hat{b}_{i,d} of the optimal stage cost to the in-transit volume, unscaled by the column prescaler (§12):

πi,db=cˉi,db/di,dcol\pi^{b}_{i,d} = \bar{c}^{\,b}_{i,d} / d^{col}_{i,d}

Transit buckets are always included in the cut projection — never gated by the per-stage state_variables selection that can drop the storage or inflow-lag dimensions (see cut management). Each cut therefore carries one coefficient per bucket dimension, contiguous with the storage, lag, and anticipated coefficients and read from the same reduced-cost mechanism (§11). In the policy manifest a bucket dimension is tagged with the downstream hydro as its entity and the maturity lag as its sub-index.

In-transit volume that would mature after the study’s last stage is dropped — it is not credited to terminal storage. A release late in the horizon whose travel time carries it past the final stage TT leaves the modeled system without arriving. This is a deliberate methodology limitation: the deepest maturity lag active at stage tt is capped at T1tT - 1 - t, so no bucket ever points beyond the horizon and the share is discarded rather than misdirected onto an earlier lag. Terminal-storage credit for still-in-transit water is deferred.

Cobre supports two production models during training, in increasing order of complexity. A third model (linearized head) is available during simulation only — see hydro production models §3. The model can vary by stage or season per hydro.

Constant Productivity Model (for each hydro hHconsth \in \mathcal{H}^{const}, block kk):

gh,k=ρhqh,kg_{h,k} = \rho_h \cdot q_{h,k}

FPHA Model (for each plane mMhm \in \mathcal{M}_h, hydro hHfphah \in \mathcal{H}^{fpha}, block kk):

gh,kγ0m+γvmvhavg+γqmqh,k+γsmsh,kg_{h,k} \leq \gamma^m_0 + \gamma^m_v \cdot v^{avg}_h + \gamma^m_q \cdot q_{h,k} + \gamma^m_s \cdot s_{h,k}

where vhavg=(vhin+vh)/2v^{avg}_h = (v^{in}_h + v_h)/2 is the average storage during the stage, with vhinv^{in}_h being the incoming storage LP variable (§4a) and vhv_h the end-of-stage storage.

Generation Bounds (per hydro hh, block kk):

Ghσh,kggh,kGˉh\underline{G}_h - \sigma^{g-}_{h,k} \leq g_{h,k} \leq \bar{G}_h

Generation bounds are user-defined (not derived from turbined flow). The lower bound is soft (with slack σh,kg\sigma^{g-}_{h,k}); the upper bound is hard. See system elements §5.

For details on the FPHA construction and production function model variants, see hydro production models.

Outflow Definition (per hydro hh, block kk):

oh,k=qh,k+sh,ko_{h,k} = q_{h,k} + s_{h,k}

Outflow Bounds (with slacks for soft enforcement):

Ohσh,kooh,kOˉh+σh,ko+\underline{O}_h - \sigma^{o-}_{h,k} \leq o_{h,k} \leq \bar{O}_h + \sigma^{o+}_{h,k}

8. Variable Bounds and Minimum Constraints

Section titled “8. Variable Bounds and Minimum Constraints”
VhσhvvhVˉh\underline{V}_h - \sigma^{v-}_h \leq v_h \leq \bar{V}_h

The lower bound (dead volume) is soft — the slack σhv\sigma^{v-}_h has a very high penalty above deficit cost. The upper bound (reservoir capacity) is hard; excess water is handled by emergency spillage. During the filling period, this Vh\underline{V}_h lower bound is inactive (storage can be anywhere in [0,Vˉh][0, \bar{V}_h]); the per-stage filling floor below takes its place.

Filling floors (for filling hydros, at every stage t[start_stage_id,entry_stage_id)t \in [\text{start\_stage\_id}, \text{entry\_stage\_id})):

vh+σhfillVttarget,Vttarget=min ⁣(Vt+1targetζt+1ratet+1, Vh),VLtarget=Vhv_h + \sigma^{fill}_h \geq V^{\text{target}}_t, \qquad V^{\text{target}}_t = \min\!\Big( V^{\text{target}}_{t+1} - \zeta_{t+1}\,\text{rate}_{t+1},\ \underline{V}_h \Big), \quad V^{\text{target}}_{L} = \underline{V}_h

The minimum end-of-stage storage VttargetV^{\text{target}}_t ramps up at the configured accumulation rate filling_min_rate_m3s (= ratet\text{rate}_t) and reaches the dead volume Vh\underline{V}_h at the last filling stage L=entry_stage_id1L = \text{entry\_stage\_id} - 1; ζt\zeta_t converts the rate over the stage duration into hm³. The slack σhfill\sigma^{fill}_h is priced at chfillc^{fill}_h, which is pinned below deficit (not the system maximum). This replaces the earlier single terminal constraint at entry_stage_id - 1. See Penalty System §6.

Turbined Flow Bounds (per hydro hh, block kk)

Section titled “Turbined Flow Bounds (per hydro hhh, block kkk)”
Qhσh,kqqh,kQˉh\underline{Q}_h - \sigma^{q-}_{h,k} \leq q_{h,k} \leq \bar{Q}_h

Lower bound is soft; upper bound is hard.

Diversion Flow Bounds (per hydro hh, block kk)

Section titled “Diversion Flow Bounds (per hydro hhh, block kkk)”
0uh,kUˉh0 \leq u_{h,k} \leq \bar{U}_h

Both bounds are hard. Diversion cost is a regularization term (see §1.4), not a violation penalty.

Pumping Flow Bounds (per station jj, block kk)

Section titled “Pumping Flow Bounds (per station jjj, block kkk)”
Pjpj,kPˉj\underline{P}_j \leq p_{j,k} \leq \bar{P}_j

Both bounds are hard.

The per-block constraint violation penalties in the objective (referenced from §2) are:

kKτkhH[chtvσh,kq+chovσh,ko+chov+σh,ko++chgvσh,kg+chev+σh,ke++chevσh,ke]\sum_{k \in \mathcal{K}} \tau_k \sum_{h \in \mathcal{H}} \Big[ c^{tv-}_h \sigma^{q-}_{h,k} + c^{ov-}_h \sigma^{o-}_{h,k} + c^{ov+}_h \sigma^{o+}_{h,k} + c^{gv-}_h \sigma^{g-}_{h,k} + c^{ev+}_h \sigma^{e+}_{h,k} + c^{ev-}_h \sigma^{e-}_{h,k} \Big] +hHTchwv(σhw+σhw+)+ \sum_{h \in \mathcal{H}} T \cdot c^{wv}_h (\sigma^{w-}_h + \sigma^{w+}_h)

where T=kτkT = \sum_k \tau_k is the total stage duration in hours. Withdrawal violation slacks (σhw\sigma^{w-}_h, σhw+\sigma^{w+}_h) are stage-level (not per-block) and bidirectional: σhw\sigma^{w-}_h penalizes under-delivery (the realized withdrawal Rh=rhσhw+σhw+R_h = r_h - \sigma^{w-}_h + \sigma^{w+}_h falls short of the target), and σhw+\sigma^{w+}_h penalizes over-delivery. The withdrawal target rhr_h is signed (§4), and the slack bounds ensure the realized withdrawal cannot flip sign relative to the target:

  • rh>0r_h > 0 (scheduled removal): σhwrh\sigma^{w-}_h \leq r_h (under-delivery slack capped at the target magnitude; floors Rh0R_h \geq 0), σhw+\sigma^{w+}_h unbounded.
  • rh<0r_h < 0 (scheduled inter-basin return/addition): σhw+rh\sigma^{w+}_h \leq |r_h| (over-delivery slack capped at rh|r_h|; caps Rh0R_h \leq 0), σhw\sigma^{w-}_h unbounded.
  • rh=0r_h = 0: both slacks are pinned to zero (presolve-eliminated).

This cap guards a degenerate case: an unbounded under-delivery slack would let a run-of-river plant “un-withdraw” past its target and inject phantom water into the reservoir.

Storage violation penalties (chsvσhvc^{sv-}_h \sigma^{v-}_h and chfillσhfillc^{fill}_h \sigma^{fill}_h) appear outside the τk\tau_k sum because they apply to end-of-stage storage — see §2.

The effective penalty for any (entity, stage, penalty_type) tuple follows a three-level cascade:

  1. Stage-specific override (from Parquet files in constraints/)
  2. Entity-specific override (from entity registry JSON)
  3. Global default (from penalties.json)

For the full resolution semantics and all penalty value definitions, see Penalty System.

User-defined linear constraints (per constraint gGg \in \mathcal{G}):

eγg,exe{,=,}bg\sum_{e} \gamma_{g,e} \cdot x_e \quad \{\leq, =, \geq\} \quad b_g

where xex_e can reference any LP variable using expression syntax:

  • hydro_storage(id), hydro_turbined(id), hydro_spillage(id)
  • thermal_generation(id), bus_deficit(id), etc.

The coefficients γg,e\gamma_{g,e} and the RHS bgb_g may be either literal numeric values or named scalar parameters. A scalar parameter resolves to a single number per stage and can carry one of four kinds:

KindValue semantics
constantOne value for every stage
per_stageExplicit value per stage
seasonalOne value per season; stages inherit the value from their season
computedValue derived from a small expression evaluated at LP-build time (e.g., a fraction of installed capacity)

Resolution happens once at LP-build time, so the LP coefficients are still numeric at solve time — the parameter mechanism does not introduce LP-variable coupling between constraints. Methodology relevance: it lets the corpus express ramping limits, capacity caps, and operator-imposed quotas that vary by stage or season without authoring a separate constraint per stage. Coefficient and RHS values can therefore be stage-varying constants, not just literal numbers.

Generic constraints can have optional slack variables with configurable penalties.

Row materialization: a constraint bound declared with block_id = None over a block-independent expression — one whose every term references a stock variable (incoming storage vhinv^{in}_h, outgoing storage vhv_h, evaporation outflow eh,ke_{h,k} collapsed to its stage average, or an anticipated-thermal commitment) — is materialized as a single stage-level row priced by the total stage hours TT, since per-block rows would be identical. A block_id = None bound on a block-level expression, or any block_id = Some(k) bound, still produces one row per relevant block. This is an LP row-count optimization that is cost- and parity-neutral.

For each active cut ii from previous iterations:

θαi+hHπi,hvvh+h,πi,h,lagah,\theta \geq \alpha_i + \sum_{h \in \mathcal{H}} \pi^v_{i,h} \cdot v_h + \sum_{h,\ell} \pi^{lag}_{i,h,\ell} \cdot a_{h,\ell}

where:

  • αi\alpha_i = cut intercept (RHS)
  • πi,hv\pi^v_{i,h} = coefficient for storage state variable
  • πi,h,lag\pi^{lag}_{i,h,\ell} = coefficient for AR lag state variable

When anticipated thermals are present, the cut carries one additional coefficient per anticipated-state slot (§5c), read from the same reduced-cost mechanism. When travel-time arcs are present, it likewise carries one coefficient per in-transit bucket dimension (§5d); unlike the storage and lag coefficients, the bucket coefficients are always part of the cut projection.

Cuts live in an append-only pool at stable slot indices: every cut ever generated is retained for the lifetime of the run, and only the active subset is baked into each iteration’s stage template. Deactivation toggles a cut row’s bound to a trivially-satisfied ±\pm\infty sentinel rather than removing the row, so slot indices stay stable and reactivation is exact. See cut management.

For cut coefficient derivation, aggregation, and selection strategies, see cut management.

The stage LP is numerically conditioned via a three-step scaling procedure applied once at template construction time. Scaling improves solver convergence by reducing the condition number of the constraint matrix without changing the optimization argmin.

All objective coefficients (except the future cost variable θ\theta) are divided by a constant factor K=1000K = 1000:

c~j=cjKfor all jθ\tilde{c}_j = \frac{c_j}{K} \quad \text{for all } j \neq \theta

The θ\theta variable retains its coefficient of 1.0 because the Benders cuts enforce θαscaled\theta \geq \alpha_{scaled} where αscaled=Qsuccessor/K\alpha_{scaled} = Q_{successor} / K, so θ\theta already operates in scaled cost space. The LP objective is jc~jxj+1.0θ\sum_j \tilde{c}_j x_j + 1.0 \cdot \theta, and the total scaled objective equals (Cstage+Cfuture)/K(C_{stage} + C_{future}) / K. All cost-domain outputs (objective values, duals, cost breakdowns) are multiplied by KK at the reporting boundary to recover original units.

After cost scaling, each column jj is assigned a geometric-mean scale factor — the standard matrix-equilibration heuristic (Curtis & Reid, 1972):

djcol=1maxiAijminiAijd_j^{col} = \frac{1}{\sqrt{\max_i |A_{ij}| \cdot \min_i |A_{ij}|}}

where the max and min are taken over nonzero entries in column jj. Columns with no nonzero entries receive djcol=1d_j^{col} = 1. The transformation replaces:

  • Matrix entries: A~ij=Aijdjcol\tilde{A}_{ij} = A_{ij} \cdot d_j^{col}
  • Objective coefficients: c~j=cjdjcol\tilde{c}_j = c_j \cdot d_j^{col}
  • Column bounds: l~j=lj/djcol\tilde{l}_j = l_j / d_j^{col}, u~j=uj/djcol\tilde{u}_j = u_j / d_j^{col}

After column scaling, each row ii is assigned a scale factor using the same geometric-mean formula applied to the already column-scaled matrix:

dirow=1maxjA~ijminjA~ijd_i^{row} = \frac{1}{\sqrt{\max_j |\tilde{A}_{ij}| \cdot \min_j |\tilde{A}_{ij}|}}

The transformation replaces:

  • Matrix entries: A^ij=A~ijdirow\hat{A}_{ij} = \tilde{A}_{ij} \cdot d_i^{row}
  • Row bounds: l^irow=lirowdirow\hat{l}_i^{row} = l_i^{row} \cdot d_i^{row}, u^irow=uirowdirow\hat{u}_i^{row} = u_i^{row} \cdot d_i^{row}

Column bounds and objective coefficients are not modified by row scaling.

The combined scaling produces the standard DrADcD_r \cdot A \cdot D_c form where DrD_r and DcD_c are diagonal scaling matrices.