Skip to content

System Element Modeling Overview

This chapter describes the physical components of a hydrothermal power system as modeled by Novomodelo: what each element represents, its decision variables, how it connects to other elements, and its role in the optimization objective. It is the conceptual foundation for the equipment formulations and the stage LP — the reader should understand what is being optimized before seeing how the constraints are assembled.

Reading order: this chapter → Equipment-Specific Formulations → LP Formulation

For variable naming conventions and index sets, see Notation Conventions. For equivalent terms in other planning tools, see the Glossary.

All decision variables in Novomodelo use rate units: electrical quantities in MW, hydraulic flows in m³/s. Storage (hm³) is inherently an absolute quantity. The block duration τk\tau_k [hours] enters the LP as an external multiplier — it appears in the objective function coefficients and in the coefficients that convert a rate to a volume or an energy, such as the water balance conversion factor.

Rate units keep every variable bound and the load-balance and production-function coefficients independent of the block durations, and the FPHA hyperplanes relate these rates to storage. The convention has these consequences across the formulation:

AspectConsequence
Objective functionAll cost terms are scaled by τk\tau_k: the coefficient for a thermal with marginal cost cthc^{th} is τk×cth\tau_k \times c^{th}
Variable boundsIndependent of the block durations — a capacity bound gˉ\bar{g} is the same whatever the block duration
Constraint matrixClean coefficients: load balance has ±1, production function uses ρ\rho [MW/(m³/s)] directly
DualsLoad balance dual πb,k\pi_{b,k} has units $/MW. To obtain the marginal cost (CMO) in $/MWh, divide by τk\tau_k
Cut coefficientsUnaffected — coupling state variables (storage in hm³, AR lags in m³/s) are independent of this choice

The objective row can carry large coefficients: the product of a long block duration and a steep deficit penalty dwarfs the coefficients of ordinary cost terms by several orders of magnitude. This conditioning is applied offline, before the solve, so the solver receives an already-conditioned matrix — see LP Layout and Scaling for the derivation.

A hydrothermal power system in Novomodelo consists of interconnected physical elements that work together to meet electricity demand at minimum cost under inflow uncertainty:

System element overview — buses, a hydro plant with its reservoir (inflow in), a thermal plant, an NCS (wind/solar) source, a transmission line between buses, a demand draw (D) off each bus, and a deficit slack (δ, dashed) backstopping unserved load. Key flow variables are labelled (f line flow, q hydro turbined, g thermal, gⁿᶜ NCS, δ deficit, D demand).

Bus 1Bus 2inflow aReservoir vₕHydro qThermal gNCS gⁿᶜ(wind / solar)Demand DDemand DDeficit δ aq → gggⁿᶜ line flow f±DD δ (unserved)

The optimizer determines generation and flow decisions at each stage to minimize total expected cost (thermal generation + deficit penalties + regularization costs) while respecting physical constraints and preparing for uncertain future inflows.

Most entity types may enter service or be decommissioned partway through the horizon, so that planning studies can represent new plants coming online and aging units retiring. An entity carries an optional commissioning window defined by two stages, its entry stage and its exit stage. The window is half-open: the entity is active from its entry stage up to, but not including, its exit stage — the entry stage is inclusive and the exit stage is exclusive, so the entity is gone from its exit stage onward. An entity without an entry stage is active from the first stage, one without an exit stage is never decommissioned, and one that declares neither is active at every stage.

This window applies uniformly to transmission lines, thermal plants, non-controllable sources, pumping stations, and contracts (and, for generation, to hydro plants). Outside its window an entity contributes nothing to the dispatch: its decision columns are present in the LP but pinned to zero, so it injects no power, withdraws no power, and consumes no resource. A hydro plant is the exception to this simple zero-pin: outside its commissioning window a hydro without filling enters the PreFilling state (§5), which pins its turbine, spillage, and diversion to zero but freezes its storage and routes natural inflow past the not-yet-in-service site rather than trapping or injecting water. Decommissioning is symmetric to commissioning — both are expressed by the same window. The per-phase LP treatment of hydros and the out-of-window pins of the other entities are tabulated in LP Formulation — Lifecycle Phases.

Two element-specific lifecycle mechanisms layer on top of this generic window:

  • Hydro dead-volume filling (§5) — a hydro plant may exist but be unable to generate while its reservoir is still filling toward the dead volume; this is a distinct commissioning state with its own per-stage storage floors, not just a presence gate.
  • Anticipated thermals (§4) — a commitment column is opened only when its delivery stage lies inside the study or on a declared post-study stage and the plant is commissioned at that delivery stage, whatever its status at the decision stage.

Independent of the commissioning window, every entity also carries a required start date, the calendar date on which it enters operation. The date is provenance and the canonical ordering key only: it fixes the entity’s position in the canonical entity order of Notation Conventions, which every state block, the LP column layout, and the output column order follow (Determinism & Provenance). It does not compute or gate commissioning. Presence in the dispatch is decided solely by the entry and exit stages of the window above: the window and the start date are independent, and neither is derived from the other.

A bus represents a node in the power network where electrical energy balance must be maintained. The granularity is user-defined: a bus may represent a large regional subsystem, a single substation, or any aggregation level in between. The model scales from a handful of buses to hundreds or thousands without structural changes.

VariableUnitsDescription
δb,k,s\delta_{b,k,s}MWLoad deficit (unserved energy) at bus bb, block kk, segment ss
ϵb,k\epsilon_{b,k}MWExcess generation at bus bb, block kk

Each bus serves as the energy balance node where:

  • Inflows: Generation from hydro plants, thermal plants, and import contracts connected to the bus
  • Outflows: Demand, export contracts, pumping station consumption, and transmission to other buses
ParameterUnitsDescription
Db,kD_{b,k}MWLoad demand at bus bb, block kk
cb,sdefc^{def}_{b,s}$/MWhDeficit cost (value of unserved energy), segment ss
cbexcc^{exc}_b$/MWhExcess generation penalty (regularization)
dˉb,s\bar{d}_{b,s}MWDeficit segment depth

Each bus contributes the cost of its deficit, piecewise over its deficit segments s∈Sbs \in \mathcal{S}_b, and the cost of its excess generation, both weighted by the block durations (LP Formulation §2).

  • Deficit cost: Very high penalty representing the value of lost load
  • Excess cost: Small regularization term to eliminate spurious slack generation

For each bus b∈Bb \in \mathcal{B} and block kk, the load balance constraint equates the generation injected at the bus, the power entering over lines and import contracts, and the deficit, less the power leaving over lines and export contracts, the pumping consumption and the excess, to the demand Db,kD_{b,k} (LP Formulation §3).

A transmission line represents the interconnection between two buses, allowing power transfer subject to capacity limits. Lines are bidirectional and lossless in the dispatch LP — transmission losses are computed and reported as an output quantity, not modeled as a reduction of delivered power (see the Configure/I·O tabs).

VariableUnitsDescription
fn,k+f^+_{n,k}MWDirect flow on line nn (source → target), block kk
fn,k−f^-_{n,k}MWReverse flow on line nn (target → source), block kk

Each line connects exactly two buses:

  • Source bus: Exports fn,k+f^+_{n,k}, receives fn,k−f^-_{n,k}
  • Target bus: Receives fn,k+f^+_{n,k}, exports fn,k−f^-_{n,k}
ParameterUnitsDescription
Fˉn+\bar{F}^+_nMWCapacity limit (direct direction); may vary by stage and, optionally, by block
Fˉn−\bar{F}^-_nMWCapacity limit (reverse direction); may vary by stage and, optionally, by block
cnexchc^{exch}_n$/MWhExchange cost (regularization)

The exchange cost is a regularization term — a small per-unit cost on interchange flow — that prevents degenerate solutions with unnecessary power circulation and guides the solver toward physically meaningful flow patterns (Equipment-Specific Formulations §2).

Each flow direction is bounded by its capacity; the direct flow leaves the source bus and enters the target bus, and the reverse flow does the opposite (Equipment-Specific Formulations §2).

A thermal plant represents dispatchable generation using fuel (natural gas, coal, oil, biomass, nuclear). Each plant has one marginal cost per MWh of generation.

VariableUnitsDescription
gj,kg_{j,k}MWGeneration at thermal plant jj, block kk
  • Bus connection: Each thermal plant connects to exactly one bus, contributing to its energy balance
  • No cascade coupling: Unlike hydro plants, thermals are independent of each other
ParameterUnitsDescription
Gˉj\bar{G}_j, G‾j\underline{G}_jMWGeneration bounds (capacity, minimum stable load)
cjthc^{th}_j$/MWhMarginal cost of generation (fuel + O&M)

Thermal costs represent actual operating expenses, which vary by fuel type, and constitute the primary controllable cost in the objective function (Equipment-Specific Formulations §1.1).

The generation of each block lies between the plant’s minimum generation and its capacity (Equipment-Specific Formulations §1.1).

A thermal plant may be flagged as anticipated. The physical motivation is fuel-ordering lead time: LNG terminals, long-haul coal contracts and similar arrangements require the dispatch quantity to be committed before the energy is physically delivered. An anticipated plant therefore decides the commitment of each delivery at a stage before its delivery stage mm, and the plant’s generation at stage mm must match that commitment.

The lead is a stage count or a physical lead time, and it fixes the decision stage of each delivery (see State Augmentation §5). A delivery decided within its own stage — a physical lead shorter than the duration of that delivery stage — is not anticipated, and at that stage the plant dispatches as an ordinary thermal. Plants without the anticipation flag use the standard thermal model from the preceding subsections.

Every commitment the plant has decided but not yet delivered is state: the plant holds it in its commitment ring, carried through the Bellman recursion alongside hydro storage and inflow lags. A commitment keeps one slot of the ring from its decision stage to its delivery stage mm; the slots and the ring depth KiK_i (the lead itself under a stage-count lead) are defined in State Augmentation — Hold Ring. Anticipated thermals are the only non-hydro elements with state variables in the SDDP formulation.

The figure follows one delivery through the ring under a stage-count lead. The commitment gi,tag^{\mathrm{a}}_{i,t} decided at stage t=m−Kit = m - K_i is deposited into the ring slot of its delivery stage mm, carried unchanged at stages t+1,…,m−1t + 1, \ldots, m - 1, and delivered at stage mm, where the plant’s generation must match it. A delivery decided before the study seeds its slot at the first stage instead (the dashed path), and is then carried and delivered in the same way. A physical lead changes which stage decides each delivery, not how the ring holds it.

stage t = m − Kᵢdeposit in its ring slotdecided before the studyseeds its slot at the first stagestages t + 1 … m − 1carry it unchangedstage mdeliver it

Each stage tt carries a commitment column gi,tag^{\mathrm{a}}_{i,t} for the delivery it decides, fixed at zero when it decides none. Its bounds are the plant’s generation limits at the delivery stage mm, not at the decision stage, and it is zero when the plant is out of service at stage mm, so a commitment is never placed for a delivery the plant cannot honour. At the delivery stage, the energy of the plant’s per-block generation equals the committed rate times the stage hours HmH_m (State Augmentation — Ring Rows). A physical lead coarse enough that one decision stage would decide more than one delivery is rejected when the study is set up, and a lead longer than the whole study horizon is accepted only when it reaches a declared post-study stage.

A delivery past the horizon can be decided in the study only when the case declares a post-study calendar that reaches it. Its commitment is then bounded by the capability that calendar declares for the delivery stage, priced on its decision column (see the cost subsection below), carried in its slot to the terminal stage, and valued there by the terminal boundary — at zero, with a setup warning, when no boundary is loaded. See Post-Study Boundary & Chained Studies for the boundary formulation.

Every delivery the plant decided before the study is declared with its committed rate, a zero rate included. The in-study ones seed their slots at the first stage, each matched to its delivery stage by date, and are then carried and delivered like any other commitment. Those past the horizon are fixed commitments: they hold no ring slot and are priced only through the terminal boundary (see Post-Study Boundary & Chained Studies). The declaration shape lives in the Configure tab of Equipment-Specific Formulations.

A commitment is charged at its decision stage tt and discounted from its delivery stage mm. Its commitment column is priced at the plant’s unit cost ci(m)c_i(m) in $/MWh over the hours HmH_m of the delivery stage (a post-study stage’s declared cost and duration past the horizon) and discounted from the delivery stage back to the decision stage (Discount Rate Formulation §5), so that the commitment cost reaches stage 1 discounted exactly once. The plant’s per-block generation carries no cost at a stage where it delivers a commitment, so the same energy is priced once (State Augmentation — Objective contributions).

Each ring slot is a state coordinate with its own cut coefficient, through which the marginal value of a commitment reaches its decision directly, whatever the lead (State Augmentation — Ring-Slot Cut Coefficient).

Hydro plants are the central elements of the SDDP formulation because:

  1. Reservoir storage creates temporal coupling (water saved today is available tomorrow)
  2. Inflows are stochastic (uncertain future rainfall/snowmelt)
  3. The water value (opportunity cost of using water now vs. saving it) emerges from the optimization

A hydro plant converts the potential energy of stored water into electricity. Each plant has a reservoir (storage), turbines (conversion), and spillways (excess water release). Hydro plants are typically arranged in cascades where upstream releases become downstream inflows.

A hydro plant’s turbines are organized into one or more unit groups, each declaring its own bus connection and its own turbined-flow and generation bounds. Groups sharing a bus form one (hydro, bus) cell; a plant is partitioned into as many cells as it has distinct group buses, and every plant declares at least one group. Storage, spillage, diversion, and inflow remain single reservoir-level quantities tracked once per plant regardless of cell count — only turbined flow and generation are tracked per cell, and each cell injects its generation at its own bus rather than at a single plant-level bus.

A plant whose groups all share one bus has exactly one cell and reduces to the single-bus model used throughout the rest of this chapter. A plant whose groups span several buses has one turbined-flow variable and one generation variable per cell instead of per plant; see LP Formulation §3–§4 for how a cell’s generation enters the bus load balance and a plant’s cells sum into its water balance, and LP Formulation §6, §8 for how a cell’s bounds compose from its member groups.

Novomodelo names two hydro plant subsets by their lifecycle phase at a stage; a PreFilling hydro belongs to neither:

SubsetSymbolDescription
OperatingHop\mathcal{H}^{op}Plants that can generate electricity; subject to generation constraints
FillingHfill\mathcal{H}^{fill}New plants under commissioning, filling dead volume; no generation

Most plants are in Hop\mathcal{H}^{op}. Filling hydros have per-stage target-storage floors instead of generation constraints. Some plants have negligible storage capacity (run-of-river) and must pass all inflows through turbines and spillways within the same stage. At each stage a hydro is PreFilling, Filling or Operating, and LP Formulation — Lifecycle Phases tabulates what each phase fixes.

The figure traces one hydro’s phase across the study, from the first stage (the start oval). A hydro without filling is Operating inside its commissioning window and PreFilling outside it, before its entry stage and from its exit stage on, so a hydro that leaves service stays PreFilling for the rest of the study. A hydro without filling and without a window is Operating at every stage. A filling hydro is PreFilling before its filling start stage, Filling from that stage up to its entry stage, and Operating from its entry stage on. A filling hydro has no exit stage, so it never returns to PreFilling.

startPreFillingno dam: water passes byFillingstorage accumulates, no turbiningOperatinggenerates before the entry orfilling start stagefilling fromthe first stagein service fromthe first stagefilling start stageentry stage,no fillingentry stageexit stage,no filling
VariableUnitsDescription
vhv_hhm³End-of-stage reservoir storage (state variable)
ah,ℓa_{h,\ell}m³/sAR lag ℓ\ell for inflow model (state variable, see note below)
qh,b,kq_{h,b,k}m³/sTurbined flow at cell (h,b)(h,b) (§ Unit Groups above), block kk; the plant total qh,k=∑bqh,b,kq_{h,k} = \sum_b q_{h,b,k} is what enters the water balance
sh,ks_{h,k}m³/sSpillage (released without generation), block kk
uh,ku_{h,k}m³/sDiversion flow (bypassed to separate channel), block kk
ehe_h / eh,ke_{h,k}m³/sSigned net evaporation for a plant with an evaporation model, outside PreFilling: a bounded column tied to storage by its evaporation row, one stage-level ehe_h on a parallel stage and one per block on a chronological stage
gh,b,kg_{h,b,k}MWHydro generation at cell (h,b)(h,b), block kk; injects at bus bb (§ Unit Groups above)
oh,ko_{h,k}m³/sTotal outflow: oh,k=qh,k+sh,ko_{h,k} = q_{h,k} + s_{h,k} (downstream channel flow)

State variables (vhv_h and ah,ℓa_{h,\ell}) link stages through the Bellman recursion. The storage vhv_h tracks reservoir volume and is a true decision variable within each stage (the optimizer chooses its end-of-stage value). The AR lags ah,ℓa_{h,\ell} carry inflow history for the PAR(p) model: they are state variables in the SDDP sense (passed between stages; whether a cut carries them is set by the cut-state projection), but are fixed at the beginning of each stage to the realized inflow values — they are not free for the optimizer to choose. All other variables are control variables determined within each stage.

  • Bus connection: Each of a plant’s cells connects to its own bus for energy delivery; a single-cell plant connects to exactly one bus (§ Unit Groups above)
  • Cascade topology: Upstream plants’ turbined and spilled flow (q+sq + s, with qq the plant’s total turbined flow summed over its cells) becomes the downstream plant’s inflow, within the release stage or, on a declared travel-time arc, partly in later stages (see Cascade Travel Time below)
  • Diversion targets: Some plants can divert water to a declared target plant, bounded by Uˉh\bar{U}_h; a plant’s diverted flow reaches only its diversion target and is never part of its release to the plant downstream
  • Pumping stations: May receive pumped water (increasing storage) or supply water to pumps (decreasing storage)

The figure draws the water paths that meet at one plant. The incremental inflow aha_h enters the reservoir. The upstream plant’s release q+sq + s arrives within the release stage for the share νh′,t,0\nu_{h',t,0}; the rest enters the in-transit bucket and reaches the reservoir in later stages as it matures (the dashed edge). The bucket exists only on a main cascade arc with a nonzero travel time; on any other arc the share is 11 and the whole release arrives within the release stage. This plant releases its own q+sq + s to the downstream plant and diverts uhu_h to its diversion target. A pumping station moves pyp_y from its source reservoir to its destination; the figure draws one pumping from the downstream plant into this reservoir. This plant also receives the diversion of any plant that targets it and supplies any station that pumps from it; these are the same edges seen from the other plant. Evaporation ehe_h and withdrawal rhr_h leave the modeled system, each signed, so a negative value adds water. Cascade Travel Time defines the share and the bucket, and LP Formulation §4 writes the balance row that sums these paths.

UpstreamplantInflow aIn-transitbucketReservoir vₕDownstreamplantDiversiontargetPumping stationEvaporation eWithdrawal r aq + s(share ν)q + s(rest) maturesq + supp er
ParameterUnitsDescription
Vˉh\bar{V}_h, V‾h\underline{V}_hhm³Storage bounds
Qˉh,b\bar{Q}_{h,b}, Q‾h,b\underline{Q}_{h,b}m³/sTurbined flow bounds (machine limits), composed per cell from its member unit groups’ declared bounds — see LP Formulation §8
Oˉh\bar{O}_h, O‾h\underline{O}_hm³/sOutflow bounds (environmental flow, flood control)
Gˉh,b\bar{G}_{h,b}, G‾h,b\underline{G}_{h,b}MWGeneration bounds (user-defined per unit group, not derived from flow), composed per cell the same way — see LP Formulation §6
Uˉh\bar{U}_hm³/sMaximum diversion flow
ρh\rho_hMW/(m³/s)Productivity (constant model)
FPHA hyperplanes—The fitted plane set of the FPHA production model (Hydro Production Function Models)
aha_hm³/sIncremental inflow (stochastic, from PAR model)
rhr_hm³/sSigned water-withdrawal target of the stage (negative = inter-basin return), relaxed by the stage-level slacks σhw−\sigma^{w-}_h, σhw+\sigma^{w+}_h
v^h\hat{v}_h, a^h,ℓ\hat{a}_{h,\ell}hm³, m³/sIncoming state (from previous stage)

The reservoir dynamics account for all water flows in and out of the plant:

TermDirectionDescription
v^h\hat{v}_hInitialIncoming storage from previous stage
aha_hInflowIncremental inflow (lateral catchment, stochastic)
∑h′∈Uhνh′,t,0 (qh′+sh′)\sum_{h' \in \mathcal{U}_h} \nu_{h',t,0}\,(q_{h'} + s_{h'})InflowTurbined and spilled release of the upstream plants; the share νh′,t,0\nu_{h',t,0} (defined in Cascade Travel Time below) arrives in the release stage (11 without a travel time)
In-transit arrivalInflowUpstream release maturing at this stage after its travel time
∑h′:div=huh′\sum_{h':\text{div}=h} u_{h'}InflowDiverted water received from other plants
∑y:dest=hpy\sum_{y:\text{dest}=h} p_yInflowPumped water received from pumping stations
qh+shq_h + s_hOutflowTurbined and spilled flow, released to the downstream plant
uhu_hOutflowDiverted flow, sent to the diversion target
ehe_hOutflowEvaporation (reservoir surface loss; can be negative for net precipitation; stage-level on a parallel stage, per block on a chronological stage)
rhr_hOutflowWater withdrawal (stage-level target, relaxed by σhw−\sigma^{w-}_h, σhw+\sigma^{w+}_h)
∑y:src=hpy\sum_{y:\text{src}=h} p_yOutflowPumped water extracted by pumping stations

Every row except the incoming storage and the in-transit arrival, both already volumes, is a flow rate: the balance converts it to volume over the stage or block duration and adds or subtracts it as its Direction column states, pumping included. The canonical row with every term is LP Formulation §4.

In a cascade, an upstream plant’s outflow (q+sq + s) becomes a downstream plant’s inflow. By default this transfer is instantaneous — water released this stage reaches the downstream reservoir within the same stage. When the reach between two plants is long enough that travel is not negligible, a plant h′h' may declare a water travel time on its main cascade arc: a release made during stage tt delivers the share νh′,t,0=(Ht−Δh′tt)+/Ht\nu_{h',t,0} = (H_t - \Delta^{tt}_{h'})^+ / H_t to the downstream reservoir within the stage, with Δh′tt\Delta^{tt}_{h'} the travel time and HtH_t the stage duration, both in hours, and the rest reaches it in later stages.

Novomodelo carries the water in transit on such an arc as additional in-transit state through the Bellman recursion, discretized into maturity lags — one aggregated in-transit bucket per receiving plant per lag. The bucket maturing at a given stage enters the receiving plant’s water balance as a delayed inflow; the remaining in-transit volume is carried forward to later stages. Under the chronological-blocks formulation the delayed arrival is spread across the arrival stage’s blocks by a fixed density; under parallel blocks it is a single stage-level inflow. Because this is a genuine state augmentation — its in-transit buckets join the state vector and every cut — it is formulated in State Augmentation §6. Confluent arcs feeding one plant are summed into that plant’s single in-transit block.

Scope. Travel time applies to the main cascade arc only — the arc from a plant to its downstream plant. Diversion and pumping transfers are instantaneous. A travel time that is absent or zero is treated as an instantaneous transfer, adding no state.

Downstream commissioning window. A release made at a stage before the downstream plant’s commissioning entry stage — while it is PreFilling or still filling — is rejected at load. A release made while the downstream plant is in service whose arrival reaches the plant’s exit stage is accepted, and the water maturing from the exit stage on passes to the first downstream plant that is not PreFilling, leaving the modeled system only when there is none (State Augmentation).

Horizon limitation. Without a loaded terminal boundary, in-transit water that would mature after the last stage is dropped; with one, it is carried to the terminal stage and priced there (State Augmentation — Horizon limitation, Post-Study Boundary & Chained Studies).

Novomodelo supports three production-model names for converting turbined flow to electrical generation:

  1. Constant Productivity: gh,b,k=ρh⋅qh,b,kg_{h,b,k} = \rho_h \cdot q_{h,b,k} (per cell (h,b)(h,b)) — simple linear relationship with fixed ρh\rho_h [MW/(m³/s)], suitable for plants with stable head.

  2. Linearized Head (reserved alias): A reserved model name that resolves to constant productivity in every phase — training and simulation alike. No distinct head-dependent model is applied; requesting it yields the constant-productivity model above. See Hydro Production Function Models §3.

  3. FPHA (approximate hydroelectric production function): Piecewise-linear approximation via hyperplanes that captures head variation with storage level and accounts for tailrace effects from spillage, fitted once per plant. Each cell’s generation is bounded above by every plane of the plant’s fitted set, evaluated at the cell’s turbined flow, the plant’s spillage and the average storage (of the stage, or of each block on a chronological stage), with the plane’s flow-independent terms apportioned by the cell’s share of the plant’s declared turbine capacity — see Hydro Production Function Models.

The production model can vary by stage or season per hydro.

Several hydro constraints are enforced as soft constraints with slack variables and penalties:

ConstraintMeaningSlack Variable
vh≥V‾hv_h \geq \underline{V}_h (filling hydro, from its entry stage)Minimum storage once filledσhv−\sigma^{v-}_{h}
qh,b,k≥Q‾h,bq_{h,b,k} \geq \underline{Q}_{h,b} (per cell)Minimum turbined flow (equipment limits)σh,b,kq−\sigma^{q-}_{h,b,k}
oh,k≥O‾ho_{h,k} \geq \underline{O}_hMinimum outflow (environmental flow)σh,ko−\sigma^{o-}_{h,k}
oh,k≤Oˉho_{h,k} \leq \bar{O}_hMaximum outflow (flood control)σh,ko+\sigma^{o+}_{h,k}
gh,b,k≥G‾h,bg_{h,b,k} \geq \underline{G}_{h,b} (per cell)Minimum generation (grid services)σh,b,kg−\sigma^{g-}_{h,b,k}
eh,ke_{h,k} feasibleEvaporation within physical limitsσh,ke±\sigma^{e\pm}_{h,k} (stage-level on a parallel stage, per block on a chronological stage)
realized withdrawal meets rhr_hWater withdrawal commitmentσhw−\sigma^{w-}_h, σhw+\sigma^{w+}_h (stage-level)
vh≥Vttargetv_h \geq V^{\text{target}}_t (filling floor)Per-stage filling target during commissioningσhfill\sigma^{fill}_{h}

Soft constraints allow the optimizer to violate bounds when physically necessary (e.g., drought conditions preventing minimum outflow), with high penalty costs signaling undesirable operation. Maximum storage (Vˉh\bar{V}_h) is a hard physical limit — excess water is handled by spillage, not a slack variable. The dead volume is a hard bound too, except for a filling hydro once it operates and a plant that is not in service (see LP Formulation §8). For the penalty priority ordering and cost magnitudes, see Penalty System. For the complete hydro constraint formulations, see LP Formulation.

A hydro is PreFilling whenever it does not yet exist in the dispatch — a filling hydro before its filling start stage, or a non-filling hydro at any stage outside its commissioning window (before its entry stage, or from its exit stage onward). Unlike the other entity types, this is not a bare column zero-pin: because the physical site is absent, the plant is reformulated so the river flows past it. In this state,

  • Turbine, spillage, and diversion are pinned to zero (qh,k=sh,k=uh,k=0q_{h,k} = s_{h,k} = u_{h,k} = 0) — a dam that does not yet exist can neither generate nor spill.
  • Storage is decoupled by a frozen identity vh=vhinv_h = v^{in}_h: the reservoir volume is held constant, injecting no phantom storage and trapping no water.
  • Natural inflow is passed through to the first downstream plant that is not PreFilling (or leaves the modeled system if none), so incremental inflow, upstream releases, the flows diverted into the plant and the in-transit water maturing into it reach the cascade below without being lost at the un-built site; the plant’s withdrawal target moves with them to that plant.

The defining contrast with the filling phase below is spillage: it is frozen to zero while PreFilling (there is no reservoir to shed from), and becomes a free decision once filling begins. LP Formulation — Lifecycle Phases gives the LP treatment of every phase.

Novomodelo models the commissioning of new hydro plants with a filling period (from the plant’s filling start stage up to, not including, its entry stage) during which the reservoir accumulates water to reach the dead volume (V‾h\underline{V}_h). During this period:

  • The hydro has no generation: qh,k=0q_{h,k} = 0, gh,k=0g_{h,k} = 0 (hard constraint)
  • Natural inflow flows freely through the ordinary water balance — there is no retention or impound cap diverting inflow to storage
  • Storage is allowed below V‾h\underline{V}_h (the operating min-storage slack is absent)
  • Outflow is limited to spillage (turbines not operational), with the minimum-outflow slack σh,ko−\sigma^{o-}_{h,k} if environmental flow cannot be met. Spillage is a free decision during filling — a real impounding reservoir can shed inflow it cannot yet hold — in contrast to the PreFilling state, where spillage is frozen to zero
  • A per-stage filling floor vh≥Vttargetv_h \geq V^{\text{target}}_t requires the reservoir to stay on a minimum-accumulation schedule set by its minimum filling rate. The floor reaches the dead volume exactly at the last filling stage; Novomodelo expects its slack σhfill\sigma^{fill}_h to be priced below deficit (a fill schedule is not defended as hard as load serving)

When the plant enters service at its entry stage, its dead volume returns as a soft floor with slack σhv−\sigma^{v-}_h. For the per-stage floor trajectory and the penalty ordering, see Penalty System.

Each hydro contributes the regularization costs of its spillage, turbined flow and diversion, each weighted by its block duration, and the penalties of its slacks (LP Formulation §2).

  • Spillage cost: Small regularization that makes storing water preferable to spilling it when the solver is otherwise indifferent
  • Turbined cost: Small regularization on the turbined flow of every hydro; above the spillage cost, it makes spilling preferable to turbining water that produces no value (Penalty System — Category 3).
  • Diversion cost: Small regularization, typically higher than spillage (water leaves main cascade)
  • Slack penalties: High costs for constraint violations — storage below a filled plant’s dead volume, outflow violations, generation violations, evaporation violations, water withdrawal under- or over-delivery. See Penalty System for the full penalty taxonomy and priority ordering.
  • No generation cost: Hydro generation has zero marginal fuel cost — its “cost” is the opportunity cost of depleting storage, captured through the value function Vt+1(vh)V_{t+1}(v_h)

Each hydro’s water balance carries its storage from the incoming to the outgoing value through the inflows and outflows above (LP Formulation §4). Each hydro’s inflow lags are pinned to their incoming values (State Augmentation §4). The generation constraint of each cell follows the plant’s production model (Hydro Production Function Models).

A non-controllable source represents intermittent generation (wind farms, solar plants, small run-of-river hydros, etc.) whose available output depends on external conditions (weather, river flow) rather than dispatch decisions. The solver receives the availability as data, a per-scenario draw of the source’s availability model (Scenario Generation §5.4) when it has one and its stage’s available generation otherwise, and can only curtail generation below that availability — it cannot dispatch upward beyond what nature provides.

Non-controllable sources have near-zero marginal cost. The cost of curtailing available generation is a regularization penalty (Category 3 in the Penalty System), analogous to the spillage cost of a hydro — curtailment discards available “free” energy.

A per-source flag χrcurt∈{curtailable,must-run}\chi^{curt}_r \in \{\text{curtailable}, \text{must-run}\} selects between two LP behaviours for the realized availability Ar,kA_{r,k}:

  • Curtailable (default): the generation column has bounds [0,Ar,k][0, A_{r,k}], the LP is free to curtail any amount below Ar,kA_{r,k}, and curtailment is regularised by the curtailment cost crcurtc^{curt}_r. This is the standard model for stand-alone wind and solar plants where ramping down is physically feasible and economically justified by the regularisation cost.
  • Must-run: the generation column is pinned to the realized availability, gr,knc=Ar,kg^{nc}_{r,k} = A_{r,k}, by setting both lower and upper bounds to Ar,kA_{r,k} on every scenario. Nothing is curtailed, and the objective carries the constant curtailment term of Equipment-Specific Formulations §6. This model is required when the scenario pipeline feeds the LP with an aggregate that has already been pre-netted from demand by an upstream model — for example, a non-simulated-generation total (small hydro, distributed thermal, wind, solar, distributed micro-generation) that the upstream model already subtracted from the gross demand series. Allowing the LP to curtail such an aggregate double-discounts the must-run contribution and understates the hydrothermal cost; pinning it as must-run avoids the double count while keeping per-source observability in the simulation outputs.

For a source with stochastic availability, the availability ratio ξr=clamp(μrnc+srnc⋅εrnc, 0, 1)\xi_r = \mathrm{clamp}(\mu^{nc}_r + s^{nc}_r \cdot \varepsilon^{nc}_r,\,0,\,1) and the block factor fr,kf_{r,k} multiply the installed capacity exactly as in the curtailable case, Ar,k=Gˉr ξr fr,kA_{r,k} = \bar{G}_r \, \xi_r \, f_{r,k}; any other source takes its stage’s available generation in place of Gˉr ξr\bar{G}_r \, \xi_r, times fr,kf_{r,k}. Only the lower bound differs.

Non-controllable sources follow the generic commissioning window (§1, Entity Commissioning Windows): outside it the generation column of every block is fixed at zero, must-run or not (LP Formulation — Lifecycle Phases).

VariableUnitsDescription
gr,kncg^{nc}_{r,k}MWGeneration at non-controllable source rr, block kk

Curtailment is not a separate LP decision variable — it is derived as κr,k=Ar,k−gr,knc\kappa_{r,k} = A_{r,k} - g^{nc}_{r,k}, with Ar,kA_{r,k} the block’s available generation for the current stage and scenario; the LP prices it through a negative cost on gr,kncg^{nc}_{r,k} (Equipment-Specific Formulations §6). For a must-run source κr,k=0\kappa_{r,k} = 0 by construction and its term is the constant −τkcrcurtAr,k-\tau_k c^{curt}_r A_{r,k}.

  • Bus connection: Each source connects to exactly one bus, contributing generation to its energy balance
ParameterUnitsDescription
Gˉr\bar{G}_rMWInstalled capacity; it does not cap the available generation Ar,kA_{r,k}
Ar,kA_{r,k}MWAvailable generation of block kk for the current (stage, scenario), Gˉr ξr fr,k\bar{G}_r \, \xi_r \, f_{r,k} for a source with stochastic availability
fr,kf_{r,k}—Block factor of block kk (11 when none is given)
crcurtc^{curt}_r$/MWhCurtailment cost (regularization penalty)

The curtailment cost is a small regularization term that rewards dispatching the available generation; Equipment-Specific Formulations §6 relates it to the curtailment penalty.

The generation of each block lies between zero and the available generation Ar,kA_{r,k} for a curtailable source, equals Ar,kA_{r,k} for a must-run source, and is injected at the connected bus (Equipment-Specific Formulations §6).

A pumping station transfers water from one reservoir (source) to another (destination), consuming electrical power in the process. Pumping enables elevation transfer, basin transfer, and storage arbitrage (pumping during low-demand periods, generating during high-demand).

VariableUnitsDescription
py,kp_{y,k}m³/sPumped water flow at station yy, block kk
  • Source hydro: Water is withdrawn from this reservoir
  • Destination hydro: Water is added to this reservoir
  • Bus connection: Pumping consumes power at the connected bus
ParameterUnitsDescription
P‾y\underline{P}_ym³/sMinimum pumped flow
Pˉy\bar{P}_ym³/sMaximum pumped flow
ρypump\rho^{pump}_yMW/(m³/s)Power consumption rate

Pumping stations have no cost term in the objective function: the energy they consume is load at the connected bus and is priced there (Equipment-Specific Formulations §4).

The pumped flow of each block lies between its minimum and maximum, leaves the source plant’s water balance and enters the destination’s, and consumes power at the connected bus (Equipment-Specific Formulations §4).

Contracts represent agreements to buy (import) or sell (export) electricity with external systems outside the modeled region, providing flexibility during shortages and revenue opportunity for surplus.

Each contract is unidirectional: it is either an import contract or an export contract.

VariableUnitsDescription
χc,k\chi_{c,k}MWDispatched power for contract cc, block kk

Each contract connects to exactly one bus, contributing to its energy balance:

  • Import contracts (c∈Cimpc \in \mathcal{C}^{imp}): Add χc,k\chi_{c,k} to the bus (power entering the system)
  • Export contracts (c∈Cexpc \in \mathcal{C}^{exp}): Remove χc,k\chi_{c,k} from the bus (power leaving the system)
ParameterUnitsDescription
C‾c\underline{C}_c, Cˉc\bar{C}_cMWMinimum and maximum contract dispatch limits
ccctrc^{ctr}_c$/MWhContract price: positive for imports (cost), negative for exports (revenue)

An import contract adds its cost to the objective and an export contract subtracts its revenue (Equipment-Specific Formulations §3).

The dispatched power of each block lies between the contract’s minimum and maximum, a non-zero minimum being a take-or-pay floor (Equipment-Specific Formulations §3).

9. Summary: Physical Elements to LP Components

Section titled “9. Summary: Physical Elements to LP Components”

The following table maps each physical system element to its LP representation:

Physical ElementState VariablesControl VariablesKey ConstraintsObjective Role
Bus—δb,k,s\delta_{b,k,s}, ϵb,k\epsilon_{b,k}Load balanceDeficit penalty (high), Excess penalty (low)
Transmission Line—fn,k+f^+_{n,k}, fn,k−f^-_{n,k}Capacity boundsExchange cost (regularization)
Thermal Plant—gj,kg_{j,k}Generation boundsFuel cost
Thermal (anticipated)commitment-ring slots (State Augmentation §5)gi,tag^{\mathrm{a}}_{i,t}, gi,kg_{i,k}Ring deposit, carry and delivery rowsCommitment cost at the decision stage, discounted from the delivery stage
Hydro Plantvhv_h, ah,ℓa_{h,\ell}, in-transit buckets (State Augmentation §6)qh,b,kq_{h,b,k}, sh,ks_{h,k}, uh,ku_{h,k}, gh,b,kg_{h,b,k}Water balance, Generation functionSpillage, turbined-flow and diversion costs (regularization)
Non-Controllable—gr,kncg^{nc}_{r,k}Availability boundCurtailment penalty (regularization)
Pumping Station—py,kp_{y,k}Flow bounds (min/max)None (cost via energy consumption)
Contract—χc,k\chi_{c,k}Dispatch bounds (min/max)Import cost or Export revenue

Key insight: The hydro storage, the inflow lags, the in-transit buckets of the cascade arcs that declare a water travel time and the commitment-ring slots of the anticipated thermals are the four state families that link stages through the Bellman recursion (State Augmentation §1). All other elements contribute control variables that are determined within each stage. This structure enables SDDP’s decomposition: the stage subproblem optimizes all control variables given the incoming state, and Benders cuts approximate the future cost as a function of the outgoing state.

The methodology above defines every system element; the tabs below cover how Novomodelo’s software surface configures and reports the network layer — buses and transmission lines — and how the Configure tab sets up the hydro plant registry. The remaining elements (thermals, non-controllable sources, pumping stations, contracts), the hydro files and outputs, and the hydro production-model selection are covered by their own chapters’ Implementation tabs (Equipment-Specific Formulations, Hydro Production Function Models).

Novomodelo’s electrical network is configured through two case-directory files: system/buses.json (nodes) and system/lines.json (edges). This tab covers their field-level configuration; the bus’s piecewise deficit cost curve is priced through the shared penalty cascade. Its segment fields are in System Entity Files and its resolution order is on the Penalty System Configure tab.

Every generator and every load attaches to a bus (§2). A single-bus (copper-plate) system still requires buses.json — see “Single-Bus vs Multi-Bus” below.

{
"buses": [
{
"id": 0,
"name": "SIN",
"operational_start_date": "1999-01-01",
"deficit_segments": [{ "depth_mw": null, "cost": 1000.0 }]
}
]
}
FieldTypeRequiredDescription
idintegerYesUnique non-negative bus identifier, referenced by every entity’s bus_id.
namestringYesHuman-readable bus name, used in output files and validation messages.
operational_start_datestring (ISO-8601 date)YesCalendar date (YYYY-MM-DD) the bus enters the registry’s operational history. Provenance and the canonical (operational_start_date, id) ordering key (see Notation Conventions) — independent of any commissioning window.
deficit_segmentsarrayNoBus-level override of the piecewise deficit cost curve. The segment fields are in System Entity Files and the global default is in penalties.json; the two-tier resolution order is on the Penalty System Configure tab.

excess_cost has no per-bus field at all: every bus shares the global excess_cost default from penalties.json, optionally refined per stage — again documented in the Penalty System chapter, not here.

system/lines.json — Transmission Line Registry

Section titled “system/lines.json — Transmission Line Registry”

A single-bus system carries an empty lines array. A multi-bus system connects buses with one or more line entries:

{
"lines": [
{
"id": 0,
"name": "North-South Interconnection",
"source_bus_id": 0,
"target_bus_id": 1,
"operational_start_date": "2003-07-01",
"entry_stage_id": null,
"exit_stage_id": null,
"capacity": { "direct_mw": 1000.0, "reverse_mw": 800.0 },
"losses_percent": 2.5,
"exchange_cost": 1.0
}
]
}
FieldTypeRequiredDescription
idintegerYesUnique non-negative line identifier.
namestringYesHuman-readable line name.
source_bus_idintegerYesBus at the source end — defines the direct-flow direction (source → target). Must reference an existing buses.json id.
target_bus_idintegerYesBus at the target end — defines the reverse-flow direction (target → source). Must reference an existing buses.json id.
operational_start_datestring (ISO-8601 date)YesCalendar date (YYYY-MM-DD) the line enters the registry’s operational history. Provenance and the canonical (operational_start_date, id) ordering key (see Notation Conventions) — independent of entry_stage_id/exit_stage_id below, which alone gate commissioning.
entry_stage_idinteger or nullNoCommissioning window start — see Entity Commissioning Windows in the body above. null means active from stage 0.
exit_stage_idinteger or nullNoCommissioning window end (exclusive). null means never decommissioned.
capacity.direct_mwnumberYesHard upper bound on the direct-direction flow variable, MW.
capacity.reverse_mwnumberYesHard upper bound on the reverse-direction flow variable, MW.
losses_percentnumberNoTransmission loss as a percentage of transmitted power, used for reporting only. Defaults to 0.0. It does not enter the dispatch LP (load-balance flow coefficients are exactly ±1\pm 1); Novomodelo computes losses_mw = (losses_percent/100)·(f⁺+f⁻) post-hoc and writes it under simulation/exchanges/ (see the I·O tab).
exchange_costnumber or nullNoEntity-level override of the flow regularization cost. Falls back to the global penalties.json default — see the Penalty System Configure tab for the full resolution cascade.

Stage-varying capacity is supplied via constraints/line_bounds.parquet, which accepts sparse (line_id, stage_id) rows carrying direct_mw and/or reverse_mw, optionally narrowed to one block via an optional block_id column; absent rows fall back to capacity.direct_mw/capacity.reverse_mw above. Values are absolute MW rather than a multiplier on the base capacity — a row may set direct_mw = 0.0 to close a line in one direction for one block, which a strictly-positive multiplicative factor could never express. Per-stage refinement of exchange_cost is supplied by a separate file — see the Inputs & Outputs tab.

A single-bus (copper-plate) system aggregates all generation and load into one node: no flow limits, no transmission losses, and no locational price differentiation. lines.json carries an empty array, and every entity’s bus_id points at the same bus. This is the right starting point when isolating dispatch economics from network effects, or when the internal transmission network is not the object of study.

A multi-bus system connects two or more buses with lines.json entries. Once a line’s capacity binds, each bus resolves its own locational marginal price (the load-balance dual, body §2 LP Constraint Preview), and dispatch in one bus cannot freely substitute for a shortfall in another. Extending a single-bus case to multi-bus is additive: add a bus entry, add a line entry connecting it to an existing bus, and repoint the relevant entities’ bus_id — no structural change to the rest of the case is required.

The sections below configure the hydro plants of §5 Hydro Plants. The plant’s generation block and the production-model selection (system/hydro_production_models.json) are on the Hydro Production Function Models Configure tab.

system/hydros.json — Hydro Plant Registry

Section titled “system/hydros.json — Hydro Plant Registry”

Hydro plants are defined in system/hydros.json. The top-level object has a single key "hydros" containing an array of plant objects:

{
"hydros": [
{
"id": 1,
"name": "UHE Tucuruí",
"downstream_id": null,
"operational_start_date": "1984-11-22",
"entry_stage_id": 60,
"exit_stage_id": null,
"reservoir": {
"min_storage_hm3": 50.0,
"max_storage_hm3": 45000.0
},
"outflow": {
"min_outflow_m3s": 1000.0,
"max_outflow_m3s": 100000.0
},
"generation": {
"model": "constant_productivity",
"min_turbined_m3s": 500.0,
"max_turbined_m3s": 22500.0,
"min_generation_mw": 0.0,
"max_generation_mw": 8370.0
},
"unit_groups": [
{
"id": 0,
"name": "UG1",
"bus_id": 0,
"min_turbined_m3s": 500.0,
"max_turbined_m3s": 22500.0,
"min_generation_mw": 0.0,
"max_generation_mw": 8370.0
}
],
"tailrace": {
"type": "polynomial",
"coefficients": [5.0, 0.001]
},
"hydraulic_losses": {
"type": "factor",
"value": 0.03
},
"efficiency": {
"type": "constant",
"value": 0.93
},
"evaporation": {
"coefficients_mm": [
80.0, 75.0, 70.0, 65.0, 60.0, 55.0, 60.0, 65.0, 70.0, 75.0, 80.0, 85.0
]
},
"diversion": {
"downstream_id": 2,
"max_flow_m3s": 200.0
},
"filling": {
"start_stage_id": 48,
"filling_min_rate_m3s": 100.0
},
"penalties": {
"spillage_cost": 0.01,
"diversion_cost": 0.1,
"turbined_cost": 0.05,
"storage_violation_below_cost": 10000.0,
"filling_target_violation_cost": 6000.0,
"turbined_violation_below_cost": 500.0,
"outflow_violation_below_cost": 500.0,
"outflow_violation_above_cost": 500.0,
"generation_violation_below_cost": 1000.0,
"evaporation_violation_cost": 5000.0,
"water_withdrawal_violation_cost": 1000.0,
"inflow_nonnegativity_cost": 1000.0
}
}
]
}

Only id, name, operational_start_date, reservoir, outflow, generation, and unit_groups are required. All other top-level keys (tailrace, hydraulic_losses, efficiency, evaporation, diversion, filling, penalties) are optional and default to off when absent.

FieldTypeRequiredDescription
idintegerYesUnique non-negative identifier. Must be unique across all hydro plants. Referenced by initial_conditions.json and by other plants via downstream_id.
namestringYesHuman-readable plant name. Used in output files, validation messages, and log output.
downstream_idinteger or nullNoIdentifier of the plant that receives this plant’s outflow. Absent or null means the plant is at the bottom of its cascade — outflow leaves the system.
operational_start_datestring (ISO-8601 date)YesCalendar date (YYYY-MM-DD) the plant enters the registry’s operational history. Provenance and the canonical (operational_start_date, id) ordering key (see Notation Conventions) — independent of entry_stage_id/exit_stage_id below, which alone gate commissioning.
entry_stage_idinteger or nullNoStage index at which the plant enters service (inclusive). null means the plant is available from stage 0.
exit_stage_idinteger or nullNoStage index at which the plant is decommissioned. The commissioning window is half-open [entry_stage_id, exit_stage_id): the plant is active through exit_stage_id - 1, and outside the window it is not in service. For a hydro the out-of-service state is PreFilling — turbine, spillage, and diversion pinned to zero, storage frozen, and natural inflow routed past the site (see Hydro Production Function Models — Implementation in Novomodelo and Not-Yet-Commissioned Hydros (PreFilling)) — rather than a bare column zero-pin. null means the plant is never decommissioned.

Storage is tracked in hm³ (cubic hectometres; 1 hm³ = 10⁶ m³).

"reservoir": {
"min_storage_hm3": 0.0,
"max_storage_hm3": 1000.0
}
FieldTypeDescription
min_storage_hm3numberPhysical minimum storage (dead volume). Water below this level cannot reach the turbine intakes. For plants that can empty completely, use 0.0.
max_storage_hm3numberPhysical maximum storage. Must be greater than or equal to min_storage_hm3 (equal is allowed — a fixed-storage plant).

This is the plant’s physical, stage-invariant range: stored-energy outputs and the useful-range mean productivity (Hydro Production Function Models §5.3) read it, while constraints/hydro_bounds.parquet storage overrides (for example a flood-control ceiling) tighten only the per-stage operative bounds of the LP storage variable.

Total outflow equals turbined flow plus spillage.

"outflow": {
"min_outflow_m3s": 0.0,
"max_outflow_m3s": 50.0
}
FieldTypeDescription
min_outflow_m3snumberMinimum total outflow required at all times [m³/s]. Set to the ecological flow requirement or minimum riparian right. Use 0.0 if there is no minimum requirement.
max_outflow_m3snumber or nullMaximum total outflow [m³/s]. null means no upper bound on outflow.

When the solver cannot meet the minimum-outflow bound, a violation slack is added at the cost of outflow_violation_below_cost in the penalties block.

A plant’s generation envelope is split into one or more turbine groups, each on its own bus. unit_groups is required — every hydro declares at least one group; an absent, null, or empty array is rejected at load. There is no top-level hydro.bus_id: a plant’s bus association lives exclusively on its groups’ bus_id.

"unit_groups": [
{
"id": 0,
"name": "UG1",
"bus_id": 0,
"min_turbined_m3s": 500.0,
"max_turbined_m3s": 22500.0,
"min_generation_mw": 0.0,
"max_generation_mw": 8370.0
}
]
FieldTypeDescription
idintegerGroup identifier, unique within the owning plant (not globally).
namestringHuman-readable group name.
bus_idintegerBus to which this group’s generation is injected. Must match an id in buses.json.
min_turbined_m3snumberMinimum turbined flow for this group [m³/s].
max_turbined_m3snumberMaximum turbined flow for this group [m³/s].
min_generation_mwnumberMinimum electrical generation for this group [MW].
max_generation_mwnumberMaximum electrical generation for this group [MW].

Bus-partitioned dispatch. A plant is partitioned into one cell per distinct bus_id among its groups: the LP carries one turbine column and one FPHA-generation column per cell, and each cell injects its generation at that cell’s bus. A plant whose groups all share one bus collapses to a single cell — the same LP shape as a plant with no groups declared beyond the required one.

Each group’s own turbine bounds must be internally consistent (min_turbined_m3s <= max_turbined_m3s, and likewise for generation), and group ids are unique within the plant. Across the whole plant, the sum of every group’s max_turbined_m3s (and, separately, max_generation_mw) cannot exceed the plant’s own declared maximum from its generation block — a plant’s own value is the envelope, so groups cannot raise it — and the sum of every group’s minima must be able to reach the plant’s own declared minimum.

Stage-varying, optionally per-block overrides of a group’s four bounds are supplied by constraints/hydro_unit_group_bounds.parquet — see the Hydro Production Function Models Inputs & Outputs tab.

The downstream_id field creates a directed chain of hydro plants: water released from an upstream plant (turbined or spilled) enters the downstream plant’s reservoir in the same stage. To model a three-plant cascade where plant 0 flows into plant 1, which flows into plant 2:

{ "id": 0, "downstream_id": 1, ... }
{ "id": 1, "downstream_id": 2, ... }
{ "id": 2, "downstream_id": null, ... }

The downstream graph is validated to be acyclic — no chain of downstream_id references may return to a plant already in the chain. Plants with downstream_id: null are tailwater plants; each connected component of the cascade graph must have exactly one tailwater plant.

The optional travel_time_hours field declares how long a release takes to travel down the main cascade arc to downstream_id.

{ "id": 0, "downstream_id": 1, "travel_time_hours": 48.0 }
FieldTypeRequiredDescription
travel_time_hoursnumber or nullNoTravel time on the main cascade arc, in hours. When present, strictly positive, and the plant has a downstream_id, the part of each release that the travel time carries past the end of the release stage reaches the downstream reservoir in later stages; the rest arrives within the stage (see Cascade Travel Time). Absent, null, or 0.0 means an instantaneous transfer.

When an arc is declared, Novomodelo carries the water in transit as additional Bellman state (see State Augmentation §6 and Cascade Travel Time). Travel time applies to the main cascade arc only — diversion and pumping transfers are always instantaneous. Declaring an arc requires seeding the pre-study releases already in transit via initial_conditions.past_defluences, and the downstream plant must have entered service by the first study stage — see Hydro Production Function Models — Implementation in Novomodelo.

These fields enable more detailed physical modeling and are all optional.

Tailrace model — the tailrace block models downstream water level as a function of total outflow; when absent, tailrace elevation is zero. Two entity-level variants are supported:

"tailrace": { "type": "polynomial", "coefficients": [5.0, 0.001] }

coefficients is an ascending-power polynomial: coefficients[0] is the height at zero outflow (m), coefficients[1] the coefficient for Q¹, and so on.

"tailrace": {
"type": "piecewise",
"points": [
{ "outflow_m3s": 0.0, "height_m": 3.0 },
{ "outflow_m3s": 5000.0, "height_m": 4.5 }
]
}

Points must be sorted in ascending outflow_m3s order; the solver interpolates linearly between them. A third, file-level source — the optional per-plant piecewise-quartic tailrace curves with backwater families (Hydro Production Function Models §2.3.1) — replaces the entity-level model in the computed-FPHA fit for any plant that has rows in that file; the equivalent-productivity derivation keeps reading the entity-level model.

Hydraulic losses — the hydraulic_losses block models head loss in the penstock; absent means lossless.

"hydraulic_losses": { "type": "factor", "value": 0.03 }

value is a dimensionless fraction (e.g. 0.03 = 3% of the gross head, the forebay level minus the tailrace level) for "factor", or a fixed metres value (value_m) for "constant".

Efficiency model — the efficiency block scales hydraulic power to electrical power; absent means 100% efficiency. Only "constant" is supported:

"efficiency": { "type": "constant", "value": 0.93 }

Evaporation — the evaporation block models net water flux at the reservoir surface; absent means no evaporation. Coefficients are signed: positive values are net evaporative loss, negative values are net rainfall input.

"evaporation": {
"coefficients_mm": [
80.0, 75.0, 70.0, 65.0, 60.0, 55.0,
60.0, 65.0, 70.0, 75.0, 80.0, 85.0
],
"reference_volumes_hm3": [
15000, 12000, 10000, 8000, 6000, 5000,
5500, 7000, 9000, 11000, 13000, 14500
]
}
FieldTypeRequiredDescription
coefficients_mmarrayYesExactly 12 values, one per calendar month (index 0 = January). mm/month; may be negative.
reference_volumes_hm3arrayNoExactly 12 linearization reference volumes [hm³], one per month, within [min_storage_hm3, max_storage_hm3]. Absent defaults to the storage range midpoint.

A hydro that declares coefficients_mm but has no usable area-volume curve to convert it into a flux — see Hydro Production Function Models — Implementation in Novomodelo for exactly what “no usable curve” means and what Novomodelo does about it.

Diversion channel — the diversion block models a diversion that routes flow directly to a downstream plant’s reservoir, bypassing turbines and spillways; absent means no diversion.

"diversion": { "downstream_id": 2, "max_flow_m3s": 200.0 }

Filling configuration — the filling block enables a commissioning fill period, during which the reservoir accumulates water toward min_storage_hm3 before the plant can generate.

"filling": {
"start_stage_id": 48,
"filling_min_rate_m3s": 100.0
}
FieldRequiredDescription
start_stage_idYesStage index at which filling begins (inclusive); filling runs through the stage before entry_stage_id.
filling_min_rate_m3sNoPer-stage minimum accumulation rate [m³/s], defaulting to 0.0 when omitted: anchors a per-stage minimum end-of-stage storage target that ramps to min_storage_hm3 by the last filling stage. It is not an applied inflow and not a cap — natural inflow and upstream cascade releases still flow through the ordinary water balance; there is no retention or impound mechanism that diverts inflow into storage.

The target trajectory and its soft-floor penalty are covered in Penalty System §6; the load-time filling-sufficiency check is listed in that page’s Configure tab.

Penalties — the penalties block overrides the global defaults from penalties.json for one plant; when absent, the plant uses the global values.

"penalties": {
"spillage_cost": 0.01,
"diversion_cost": 0.1,
"turbined_cost": 0.05,
"storage_violation_below_cost": 10000.0,
"filling_target_violation_cost": 6000.0,
"turbined_violation_below_cost": 500.0,
"outflow_violation_below_cost": 500.0,
"outflow_violation_above_cost": 500.0,
"generation_violation_below_cost": 1000.0,
"evaporation_violation_cost": 5000.0,
"water_withdrawal_violation_cost": 1000.0,
"inflow_nonnegativity_cost": 1000.0
}

When present, every field is optional and independently falls back to the global default from penalties.json; the block accepts exactly these twelve keys and rejects any unknown field. The directional evaporation and withdrawal costs have no key in this block; a plant takes each from the block’s matching symmetric cost when the block sets it, else from penalties.json (Penalty System — Directional evaporation and withdrawal costs). For the full field list, units, and the priority ordering across penalty categories, see Penalty System.

Three-tier resolution cascade — penalty values are resolved from the most specific to the most general source:

  1. Stage-level override (stage-specific penalty files, when present)
  2. Entity-level override (the penalties block inside the plant’s JSON object)
  3. Global default (the hydro section of penalties.json, which must always be present and complete)

Stage-varying operational bounds (constraints/hydro_bounds.parquet)

Section titled “Stage-varying operational bounds (constraints/hydro_bounds.parquet)”

Beyond the entity-level diversion and spillage fields above, constraints/hydro_bounds.parquet carries optional, sparse stage-varying bound overrides for a hydro plant, optionally narrowed to one block via an optional block_id column (null applies at the stage level). Three of its columns set a diversion floor and the spillage band:

  • min_diversion_m3s — a minimum diversion-flow floor (m³/s). This is a floor that requires a declared diversion channel on the hydro (see Diversion channel, above): with no channel declared, the diversion column is pinned [0, 0], so a positive floor is infeasible and is rejected at load.
  • min_spillage_m3s, max_spillage_m3s — the spillage band (m³/s); min_spillage_m3s must be <= max_spillage_m3s.

All three are non-negative — a negative value is rejected at load, as is a spillage band with min_spillage_m3s > max_spillage_m3s. An absent row leaves that hydro’s diversion floor and spillage band unset for the stage/block it would have covered (no floor; spillage bounded only by the plant’s other outflow constraints). See Constraint Files — constraints/hydro_bounds.parquet for the complete column table, including the turbined, storage, outflow, generation, diversion-maximum, filling and withdrawal override columns this file also carries.

RuleError ClassDescription
Bus reference integrityReference errorEvery unit_groups[].bus_id must match an id in buses.json.
Downstream reference integrityReference errorEvery non-null downstream_id must match an id in hydros.json.
Unit group bound orderingPhysical feasibilityEach group’s own min_turbined_m3s must be <= max_turbined_m3s, and min_generation_mw must be <= max_generation_mw.
Unit group envelopePhysical feasibilityThe sum of every group’s max_turbined_m3s (and, separately, max_generation_mw) must not exceed the plant’s own declared maximum; the sum of every group’s minima must be able to reach the plant’s own declared minimum.
Cascade acyclicityTopology errorThe directed graph of downstream_id links must be acyclic.
Storage bounds orderingPhysical feasibilitymin_storage_hm3 must be less than or equal to max_storage_hm3 (equal is allowed — a fixed-storage plant).
Outflow bounds orderingPhysical feasibilityWhen max_outflow_m3s is present, it must be >= min_outflow_m3s.
Turbine bounds orderingPhysical feasibilitymin_turbined_m3s must be <= max_turbined_m3s.
Generation bounds consistencyPhysical feasibilitymin_generation_mw must be <= max_generation_mw.
Initial conditions exclusivitySchema errorA hydro appears at most once in storage, at most once in filling_storage, and never in both, in initial_conditions.json; a hydro with no entry is not rejected at load.
Evaporation array lengthSchema errorcoefficients_mm must have exactly 12 values; reference_volumes_hm3, when present, must also have exactly 12 values within [min_storage_hm3, max_storage_hm3].