Pular para o conteúdo

Hydro Production Function Models

Este conteúdo não está disponível em sua língua ainda.

This spec defines the hydro generation constraint models supported by Cobre, which relate turbined flow and reservoir storage to electrical output. Two models are available during training (policy construction): constant productivity and FPHA. A third model — linearized head — is available only during simulation (policy evaluation) as a higher-fidelity enhancement. The choice among training models trades off accuracy vs. computational cost, and can vary by stage per hydro.

All decision variables use rate units (MW, m³/s) — see the Variable Units Convention in system elements. For variable definitions see notation conventions; for LP integration see LP formulation; for hydro element descriptions see system elements.

The simplest model assumes a linear relationship:

gh,k=ρh,tqh,kg_{h,k} = \rho_{h,t} \cdot q_{h,k}

where ρh,t\rho_{h,t} (MW per m³/s) is the hydro productivity for stage tt:

ρh,t=9.81×ηh×Hh,tref1000\rho_{h,t} = \frac{9.81 \times \eta_h \times H^{ref}_{h,t}}{1000}

with:

  • ηh\eta_h = turbine efficiency (a dimensionless factor below 1), from the hydro object’s efficiency field
  • Hh,trefH^{ref}_{h,t} = reference net head (meters), evaluated at the reference storage level and varying by stage

Per-stage productivity: the productivity coefficient is authored per (hydro, stage) rather than per plant. A plant can therefore carry a stage-varying constant productivity — useful when the reference head differs between near-term and far-future stages of the same study, or when the constant model is being used as a coarse approximation that needs different operating points across the horizon. Section 5.1 describes how that per-(hydro, stage) value is resolved at load time.

LP treatment: 1 equality constraint per hydro per block. The generation variable gh,kg_{h,k} is fully determined by qh,kq_{h,k} — no free generation variable is needed. Simple and fast, but ignores head variation with storage within a stage.

Data requirements: a per-stage productivity scalar per hydro plant. No geometry or hyperplane data needed.

2. FPHA (Função de Produção Hidrelétrica Aproximada)

Section titled “2. FPHA (Função de Produção Hidrelétrica Aproximada)”

For accurate modeling of hydroelectric generation, FPHA (Aproximate Hydropower Production Function) captures the nonlinear relationship between storage, flow, spillage, and generation through a piecewise-linear approximation. The approach follows the piecewise-linear hydro production model of Diniz & Maceira (2008); that model is four-dimensional (forebay/head, turbined flow, and tailrace/spillage), whereas Cobre fits a reduced variant over storage and turbined flow at spillage = 0, capturing the spillage effect through the per-plane lateral-flow secant of §2.7 rather than an explicit spillage axis.

This section uses consistent notation with the LP formulation. The following table maps Cobre symbols to equivalent Portuguese terminology for practitioners familiar with DECOMP/DESSEM/NEWAVE:

CobrePortuguese (practitioner reference)DescriptionUnits
ϕ\phiFPHHydro production functionMW
vvVVReservoir storagehm³
qqQQTurbined flowm³/s
ssSS / QverQ_{ver}Spillagem³/s
ghg_hGHHydro generationMW
hforeh_{fore}hmonh_{mon} (montante)Forebay (upstream) levelm
htailh_{tail}hjush_{jus} (jusante)Tailrace (downstream) levelm
hneth_{net}hliqh_{liq} (líquida)Net headm
hlossh_{loss}hPerdHh_{PerdH} (perda hidráulica)Hydraulic lossesm
qoutq_{out}QjusQ_{jus}Total downstream outflowm³/s

The exact hydroelectric production function relates generation to the operating state:

ϕ(v,q,s)=ρespqhnet\phi(v, q, s) = \rho_{esp} \cdot q \cdot h_{net}

where:

  • vv = reservoir storage volume (hm³)
  • qq = turbined flow (m³/s)
  • ss = spillage flow (m³/s)
  • hneth_{net} = net head (m), clamped to 0\ge 0
  • ρesp=9.81ηh/1000\rho_{esp} = 9.81\,\eta_h / 1000 (MW·s/m⁴), with constant efficiency ηh\eta_h per plant (default 1.0)

The net head is computed as:

hnet(v,q,s)=hfore(v)htail(q+s)hlossh_{net}(v, q, s) = h_{fore}(v) - h_{tail}(q + s) - h_{loss}

clamped to max(hnet,0)\max(h_{net}, 0), where:

  • hfore(v)h_{fore}(v) = forebay (upstream reservoir) level as function of storage
  • htail(q+s)h_{tail}(q + s) = tailrace (downstream channel) level as function of total outflow
  • hlossh_{loss} = hydraulic head losses (from the factor or constant model)

Why linearization is needed: ϕ\phi is nonlinear in (v,q)(v, q) due to the bilinear product q×hnetq \times h_{net}, nonlinear topology functions hfore(v)h_{fore}(v) and htail(qout)h_{tail}(q_{out}), and flow-dependent hydraulic losses. For LP formulation, Cobre approximates ϕ\phi with a set of linear hyperplanes.

Cobre uses tabular data with linear interpolation for the forebay curve — more transparent and easier to validate against surveyed data than polynomial fits.

Forebay Level hfore(v)h_{fore}(v)

Section titled “Forebay Level hfore(v)h_{fore}(v)hfore​(v)”

The upstream water level is read from the volume-height curve in hydro_geometry.parquet. For storage vv where viv<vi+1v_i \leq v < v_{i+1}:

hfore(v)=hi+hi+1hivi+1vi×(vvi)h_{fore}(v) = h_i + \frac{h_{i+1} - h_i}{v_{i+1} - v_i} \times (v - v_i)

Tailrace Level htail(qout)h_{tail}(q_{out})

Section titled “Tailrace Level htail(qout)h_{tail}(q_{out})htail​(qout​)”

The downstream water level depends on total outflow. Three representations are supported:

Polynomial model (type: "polynomial"):

htail(qout)=c0+c1qout+c2qout2+c3qout3+c4qout4h_{tail}(q_{out}) = c_0 + c_1 q_{out} + c_2 q_{out}^2 + c_3 q_{out}^3 + c_4 q_{out}^4

Piecewise-linear model (type: "piecewise"): tabular breakpoints with linear interpolation between points.

Piecewise-quartic families (exact tailrace): an optional per-plant tailrace table provides piecewise degree-4 polynomial segments (evaluated via Horner’s method), grouped into backwater families keyed by the downstream reservoir’s reference forebay level — see §2.3.1.

Total downstream flow in LP: qout=q+sq_{out} = q + s (turbined flow + spillage). For FPHA fitting, spillage is fixed at s=0s = 0 when building the generation cloud; the lateral-flow secant (§2.7) captures the spillage correction per plane.

2.3.1 Piecewise-Quartic Tailrace Families (Backwater Coupling)

Section titled “2.3.1 Piecewise-Quartic Tailrace Families (Backwater Coupling)”

When plants are hydraulically close, plant ii‘s tailrace level depends on the downstream reservoir’s forebay. An optional per-plant tailrace table provides piecewise-quartic tailrace segments: the flow domain [Qjus,lo,Qjus,hi][Q_{jus,lo}, Q_{jus,hi}] is split into contiguous segments, each a degree-4 polynomial

htail(k)(qjus)=a0(k)+a1(k)qjus+a2(k)qjus2+a3(k)qjus3+a4(k)qjus4h_{tail}^{(k)}(q_{jus}) = a_0^{(k)} + a_1^{(k)} q_{jus} + a_2^{(k)} q_{jus}^2 + a_3^{(k)} q_{jus}^3 + a_4^{(k)} q_{jus}^4

evaluated via Horner’s method for numerical stability. C0 continuity between segments is enforced to ~1 mm. These segments are grouped into backwater families keyed by the downstream reservoir’s reference forebay level (HrefJusHrefJus, in metres). At fitting time, the active family is linearly interpolated by the downstream plant’s resolved stage reference level — clamped to the calibrated level range, never extrapolated. Plants with a single keyless family (no backwater coupling) evaluate that family directly regardless of the downstream level. Plants without a tailrace table use the entity-level polynomial or piecewise-linear tailrace.

Two models are supported:

Factor model — proportional to gross head:

hloss=kloss×(hforehtail)h_{loss} = k_{loss} \times (h_{fore} - h_{tail})

where klossk_{loss} is a small dimensionless fraction of the gross head.

Constant model — fixed head loss:

hloss=Δhconst(metres)h_{loss} = \Delta h_{const} \quad \text{(metres)}

The specific productivity ρesp\rho_{esp} (MW per (m³/s · m)) converts hydraulic power to electrical power:

ρesp,h=9.81×ηh1000\rho_{esp,h} = \frac{9.81 \times \eta_h}{1000}

so the exact production is ϕ=ρespqhnet\phi = \rho_{esp} \cdot q \cdot h_{net} in MW. Cobre uses constant efficiency ηh\eta_h per plant.

FPHA plants author ρesp\rho_{esp} directly — not a single scalar productivity ρ\rho. The equivalent productivity ρeq\rho_{eq} at the reference operating point is derived from ρesp\rho_{esp} and the VHA geometry; the derivation is documented in section 5.1.

The FPHA approximation replaces the nonlinear production function ϕ(v,q,s)\phi(v, q, s) with a set of MM linear hyperplanes that form a concave outer approximation of the exact surface. Each hyperplane mm defines an upper bound on generation:

gh,kγ0m+γvmvhavg+γqmqh,k+γsmsh,kg_{h,k} \leq \gamma_0^m + \gamma_v^m \cdot v_h^{avg} + \gamma_q^m \cdot q_{h,k} + \gamma_s^m \cdot s_{h,k}

Physical interpretation of coefficients:

CoefficientSignMeaning
γ0\gamma_0> 0Intercept (MW at zero storage, flow, spillage)
γv\gamma_v≥ 0Higher storage → higher forebay → more generation
γq\gamma_q≥ 0More turbined flow → more generation
γs\gamma_s≤ 0More spillage → higher tailrace → less net head

Source of hyperplanes: planes are either pre-computed (read from fpha_hyperplanes.parquet) or computed from topology data during preprocessing. The computed fit is described in §2.6.

The computed-FPHA path produces hyperplanes from topology data in four stages: convex-hull fit → αFPHA\alpha_{FPHA} correction → lateral-flow secant → optional plane reduction. The fit is resolved per production-model entry (per season or stage range), so planes can differ across the horizon for the same plant. Results are expanded to per-stage hyperplane rows.

The fitter evaluates the exact production function ϕ=ρespqhnet\phi = \rho_{esp} \cdot q \cdot h_{net} on a uniform two-dimensional grid over the fitting window, with spillage s=0s = 0 and lateral flow =0= 0. The grid has:

  • Volume axis: volume_discretization_points uniformly spaced values spanning the fitting window [vmin,vmax][v_{min}, v_{max}] (default 5 points). The window is set by the optional fitting_window block inside fpha_config (absolute volume_min_hm3 / volume_max_hm3 or percentile bounds), defaulting to the plant’s full forebay storage range. It is distinct from reference_volume (§5.1), which fixes an operating point, not this window.
  • Flow axis: turbine_discretization_points uniformly spaced values spanning [0,qmax][0, q_{max}] (default 5 points). The axis starts at q=0q = 0, where generation is zero; this zero-flow column anchors the lower closure of the cloud and eliminates the need for any synthetic closing point.

Each cloud point is capped at the plant’s installed capacity Gˉh\bar{G}_h. Spillage is not a cloud dimension — it is fixed at zero throughout.

Run-of-river plants: when the plant has a single fitting volume (vminvmaxv_{min} \approx v_{max}), two volume samples ~1% of useful storage apart are synthesized to keep the 3-D hull non-degenerate. The resulting γV\gamma_V residual is then snapped to exactly 0, enforcing the correct run-of-river semantics (γV=0\gamma_V = 0).

Determinism: the cloud points and the hull output are canonically sorted, so the fitted hyperplanes are bit-identical regardless of input ordering and MPI rank count.

The 3-D convex hull of the (V,Q,generation)(V, Q, \text{generation}) cloud is computed via the qhull library. The upper-envelope facets — those whose outward normal has a positive generation component — are selected. Each is read as generation=γ0+γVV+γQQ\text{generation} = \gamma_0 + \gamma_V V + \gamma_Q Q. The result is a concave outer approximation (the smallest concave function lying above ϕ\phi); non-concave regions of ϕ\phi fall inside the hull and their facets drop out. Near-parallel coplanar facets arising from hull triangulation are deduplicated by exact coefficient comparison.

2.6.3 Least-Squares αFPHA\alpha_{FPHA} Correction

Section titled “2.6.3 Least-Squares αFPHA\alpha_{FPHA}αFPHA​ Correction”

The raw hull envelope FPHA0FPHA_0 is optimistic where ϕ\phi is non-concave and pessimistic where it is concave. A single scalar αFPHA\alpha_{FPHA} corrects the bias by minimising the mean-squared error between αFPHAFPHA0\alpha_{FPHA} \cdot FPHA_0 and the exact ϕ\phi over the spill=0=0 grid:

αFPHA=i,jFPHA0(Vi,Qj)ϕ(Vi,Qj)i,jFPHA0(Vi,Qj)2,FPHA=αFPHAFPHA0\alpha_{FPHA} = \frac{\sum_{i,j} FPHA_0(V_i,Q_j)\,\phi(V_i,Q_j)}{\sum_{i,j} FPHA_0(V_i,Q_j)^2}, \qquad FPHA = \alpha_{FPHA}\cdot FPHA_0

Key properties:

  • The regression uses the pointwise minimum over the raw hull planes as FPHA0(Vi,Qj)FPHA_0(V_i, Q_j), because the LP applies planes as gγ0m+γVmV+γQmQg \le \gamma_0^m + \gamma_V^m V + \gamma_Q^m Q for every mm, so the binding cap is the minimum, not the maximum.
  • The regression is over the spill=0=0 grid only — adding a spillage axis would pull αFPHA\alpha_{FPHA} toward the larger-deviation spill region and degrade the no-spill operating region.
  • αFPHA\alpha_{FPHA} scales the whole affine function (γ0,γV,γQ\gamma_0, \gamma_V, \gamma_Q alike), not just the intercept. This is why αFPHAκ\alpha_{FPHA} \ne \kappa (the old intercept-only shrink).
  • αFPHA\alpha_{FPHA} may be greater or less than 1 (an MSE balance, not a one-sided shrink). Validation requires αFPHA>0\alpha_{FPHA} > 0. A degenerate denominator (all-zero production) yields the neutral αFPHA=1\alpha_{FPHA} = 1.
  • Validation also requires γV0\gamma_V \ge 0, γQ0\gamma_Q \ge 0, γS0\gamma_S \le 0.

Fit-quality diagnostic: after the full pipeline, the relative mean-absolute-deviation of the emitted min-envelope vs the exact ϕ\phi over the spill=0=0 grid is computed. A warning is emitted (in canonical plant/stage order) when it exceeds 5% — typically indicating a strongly non-concave surface that no single αFPHA\alpha_{FPHA} can track well.

Precomputed input: when using source: "precomputed", hyperplanes are read directly from fpha_hyperplanes.parquet. That file retains a kappa column (defaulting to 1.0); for precomputed planes it is still applied as an intercept-only scale (γ0γ0κ\gamma_0 \leftarrow \gamma_0 \cdot \kappa) and is validated to lie in (0,1](0, 1]. Only the computed path retired κ\kappa — there the whole-affine αFPHA\alpha_{FPHA} correction above replaces it.

Spillage raises the tailrace level, lowering net head and generation. The γS\gamma_S coefficient for each plane is fit by a per-plane 1-D ordinary-least-squares secant of generation vs lateral flow (in the current default: own spillage ss) over 9 evenly-spaced samples of qlat[0,Smax]q_{lat} \in [0, S_{max}], evaluated at the plane’s representative (active-maximum) operating point:

Smax={2MLTif MLT>02qmaxif MLT=0S_{max} = \begin{cases}2 \cdot \text{MLT} & \text{if MLT} > 0 \\ 2 \cdot q_{max} & \text{if MLT} = 0\end{cases}

where MLT is the long-term mean inflow (m³/s). The representative operating point for each plane is the spill=0=0 grid point where that plane is the active (tightest) upper bound and attains the largest generation — the operating region the plane actually governs. The secant samples the uncapped production function ϕ\phi (not clipped at installed capacity Gˉh\bar{G}_h), so the spillage sensitivity is read from the raw head curve and is not flattened wherever the capacity ceiling binds — unlike the cloud and the αFPHA\alpha_{FPHA} regression (§2.6), which both use the capacity-capped output.

Sign: γS0\gamma_S \le 0 (more lateral flow raises the tailrace, reduces generation). Near-zero slopes γS<1010|\gamma_S| < 10^{-10} of either sign are snapped to exactly 0 to protect LP column scaling from near-zero structural coefficients.

Default lateral axis: own spillage (qlat=sq_{lat} = s). The more general QjusQ_{jus} composition (upstream defluences, post-incremental inflows with participation factors) is a deferred future extension.

An optional post-fit step merges consecutive near-parallel or near-coincident planes into their mean hyperplane to shrink the LP. Two mutually-exclusive methods are supported, configured via an fpha_plane_reduction block:

  • Angle (method: angle, tolerance in degrees): merge consecutive pairs whose normal-vector angle θ=arccos(n1n2/n1n2)\theta = \arccos(\mathbf{n}_1 \cdot \mathbf{n}_2 / \|\mathbf{n}_1\|\|\mathbf{n}_2\|) satisfies θ<ε\theta < \varepsilon (strict). Fully deterministic from coefficients.
  • Distance (method: distance, tolerance in percent + sample count): merge consecutive pairs whose normalised mean-squared generation difference δ=EQM/Gˉh2<ε/100\delta = \text{EQM}/\bar{G}_h^2 < \varepsilon/100. Uses a deterministically-seeded PRNG (seeded from stable plant/stage/plane-pair identity, never from wall clock or MPI rank), so results are bit-identical across input ordering and rank count.

Origin-plane invariant: the plane through the origin (γ00γV0\gamma_0 \approx 0 \wedge \gamma_V \approx 0, generating zero power at zero turbining) is never merged. This guarantees the zero-generation floor.

Off by default: no reduction is applied unless an fpha_plane_reduction block is present in the production-models config.

For each hydro hh using FPHA, block kk, and plane mMhm \in \mathcal{M}_h:

gh,kγ0m+γvmvhavg+γqmqh,k+γsmsh,kg_{h,k} \leq \gamma_0^m + \gamma_v^m \cdot v_h^{avg} + \gamma_q^m \cdot q_{h,k} + \gamma_s^m \cdot s_{h,k}

The coefficients are already αFPHA\alpha_{FPHA}-scaled — there is no separate pre-scaling step. These are hard constraints — no slack variables. Feasibility is ensured through the turbined_cost regularization mechanism (see section 2.10).

The average storage vhavgv^{avg}_h over the stage:

vhavg=vhin+vh2v^{avg}_h = \frac{v^{in}_h + v_h}{2}

where vhinv^{in}_h is the incoming storage LP variable (pinned to v^h\hat{v}_h via column bounds — see LP Formulation §4a) and vhv_h is end-of-stage storage. Both are LP variables, so vhinv^{in}_h appears in the FPHA constraint with coefficient γvm/2\gamma_v^m / 2. The LP solver automatically accounts for this in the reduced cost of the pinned vhinv^{in}_h column.

When using FPHA, the generation variable gh,kg_{h,k} is not directly computed from turbined flow. Instead:

  1. Generation is a free LP variable bounded by [0,Gˉh][0, \bar{G}_h] (user-defined bounds from hydros.json)
  2. FPHA constraints (one per plane mm) provide upper bounds relating generation to storage, flow, and spillage
  3. The optimizer maximizes generation subject to FPHA constraints
  4. At optimum, generation lies on one of the FPHA hyperplane facets

Key insight: Because minimizing cost includes maximizing hydro generation (which has zero fuel cost), the optimizer naturally pushes generation to the FPHA surface boundary. The turbined_cost regularization (section 2.10) ensures the solution lies on the boundary rather than at an interior point.

A small regularization cost chtc^{t}_h is applied to the turbined flow variable of every hydro in the objective:

kτkchtqh,k\sum_{k} \tau_k \cdot c^{t}_h \cdot q_{h,k}

This cost must satisfy cht>chspillc^{t}_h > c^{spill}_h for each plant. The rule serves two purposes.

For hydros using the FPHA production model, the regularization keeps the solver on the FPHA surface boundary rather than at an interior point: without it, the optimizer could find degenerate solutions where turbined flow and spillage are both artificially high (with net generation unchanged), because the FPHA surface has a flat region where increasing qq and ss simultaneously can maintain the same gg. The penalty making every unit of turbined flow carry a small additional cost collapses the degenerate interior region.

For hydros using constant productivity, the same regularization is applied uniformly so that the LP tie-breaks (turbined, spillage) decompositions deterministically. Applying it to constant-productivity plants — not to FPHA plants alone — keeps every turbine column priced, so the dispatch stays well-determined even on cases that mix the two production models. Plants using linearized_head (simulation-only, see section 3) are not subject to this regularization during training because training uses only constant_productivity and fpha; during simulation, the cost is irrelevant because no policy is being constructed.

For the full penalty taxonomy and priority ordering, see Penalty System.

The FPHA formulation affects water value computation. Because the incoming storage variable vhinv^{in}_h appears in the FPHA constraint (via vhavg=(vhin+vh)/2v^{avg}_h = (v^{in}_h + v_h)/2, section 2.9), the FPHA hyperplane duals contribute to the marginal value of incoming storage. However, the implementation does not require manually combining duals from the water balance and FPHA constraints. Instead, pinning vhinv^{in}_h to v^h\hat{v}_h by column bounds (see LP Formulation §4a) makes its reduced cost capture the total sensitivity Qt/v^h\partial Q_t / \partial \hat{v}_h automatically — the LP solver propagates the FPHA contribution through vhinv^{in}_h.

The cut coefficient for storage is simply the reduced cost of the pinned vhinv^{in}_h column:

πhv=cˉhin/dhcol\pi^v_h = \bar{c}^{in}_h / d^{col}_h

This dual implicitly includes the water balance contribution (πhwb\pi^{wb}_h), the FPHA contribution (12mπmfphaγvm\frac{1}{2} \sum_m \pi_m^{fpha} \cdot \gamma_v^m), and any generic constraint contributions — all resolved by the LP solver without explicit dual combination.

For the complete cut coefficient computation, see cut management.

When a hydro transitions between production models across stages:

TransitionCut InterpretationAction
Constant → FPHACuts at stage tt use constant modelCut valid but conservative
FPHA → ConstantStage t+1t+1 backward pass uses constantMay overestimate value
FPHA → FPHA (different params)Parameters changeCuts remain valid if conservative

Recommendation: When using stage-dependent FPHA configuration, ensure the FPHA at stage tt is at least as conservative as stage t+1t+1 for cut validity.

3. Linearized Head Model (Simulation-Only Enhancement)

Section titled “3. Linearized Head Model (Simulation-Only Enhancement)”

An intermediate model between constant productivity and full FPHA that captures first-order head variation with storage:

gh,k=ρrefqh,k(k0+kVvhavg)g_{h,k} = \rho_{ref} \cdot q_{h,k} \cdot \left( k_0 + k_V \cdot v_h^{avg} \right)

where:

  • k0,kVk_0, k_V are linearization coefficients derived from hfore(v)h_{fore}(v)
  • k0=1kVVrefk_0 = 1 - k_V \cdot V_{ref} (normalization at reference volume)
  • kV=1HrefdhforedVVrefk_V = \frac{1}{H_{ref}} \cdot \frac{dh_{fore}}{dV}\bigg|_{V_{ref}}

The product qh,kvhavgq_{h,k} \cdot v_h^{avg} is a bilinear term — both qq and vavgv^{avg} are LP variables. To maintain LP linearity, the standard approach fixes vavgv^{avg} from the previous SDDP iteration (or from a reference volume on the first iteration), converting the constraint to a linear equality. However, this means the LP constraint coefficients change between iterations: the effective productivity ρref(k0+kVvfixedavg)\rho_{ref} \cdot (k_0 + k_V \cdot v^{avg}_{fixed}) is different after each forward pass updates the storage trajectory.

This violates a foundational assumption of SDDP: each stage must have a fixed LP structure across all iterations. Benders cuts generated under one linearization point encode dual information about a specific LP. When the LP changes (because vfixedavgv^{avg}_{fixed} changed), previously generated cuts are not guaranteed to be valid — they may cut off the true optimal solution or produce inconsistent value function approximations. This breaks the convergence guarantees of the algorithm.

During simulation, linearized head is safe because simulation executes a single forward pass through the policy — there are no cuts being accumulated, no convergence to verify. The model provides a higher-fidelity generation estimate than constant productivity without the preprocessing cost of fitting FPHA hyperplanes.

The linearized head model fills a practical gap in the simulation step:

  • More accurate than constant productivity: Captures how reservoir level affects generation — important for plants with significant head variation that are modeled with constant_productivity during training for computational reasons
  • Cheaper than FPHA: Requires only the Volume-Height-Area curve (hydro_geometry.parquet), no hyperplane fitting
  • Single constraint: One equality constraint per hydro per block, compared to MM inequality constraints for FPHA

Typical use: plants where full FPHA accuracy is justified for near-term training stages but far-future stages use constant_productivity during training, then linearized_head during simulation for improved analytics.

productivity_mw_per_m3s from hydros.json plus hydro_geometry.parquet for the Volume-Height-Area curve.

Only constant_productivity and fpha are valid during training. The linearized head model is excluded because it changes the LP between iterations (see section 3.1).

ScenarioRecommended ModelRationale
High-head storage reservoirsFPHASignificant head variation (>20%)
Large storage variation plantsFPHAOperating across wide volume range
Run-of-river plantsFPHA or ConstantHull now supports run-of-river
Initial algorithm testingConstant productivityFast iteration, debug focus
Near-term stagesFPHAAccuracy for operational decisions
Far-future stagesConstant productivityComputational efficiency

All three models are available during simulation. The linearized head model is particularly useful as a simulation-only upgrade for plants that used constant_productivity during training:

ScenarioRecommended ModelRationale
Plants trained with FPHAFPHAConsistency with training model
Plants trained with constant, low head variationConstant productivityNo benefit from head correction
Plants trained with constant, significant head variationLinearized headBetter analytics without FPHA fitting cost
Post-optimization validationCompare all modelsVerify approximation quality

The production model may vary by stage or by season per hydro, configured via the stage_ranges and seasonal selection modes in hydro_production_models.json.

The three production models of sections 1–3 describe how generation depends on the operating state. For accounting purposes — natural-inflow energy (ENA), stored reservoir energy (EARM), and per-stage MW/MWh reporting — Cobre reduces each plant’s production model to a small set of per-(hydro, stage) scalars at a representative operating point. These scalars are computed once at study setup and reused on every stage of every scenario.

5.1 Equivalent Productivity ρeq\rho_{eq}

Section titled “5.1 Equivalent Productivity ρeq\rho_{eq}ρeq​”

The equivalent productivity ρeq,h,t\rho_{eq,h,t} (MW per m³/s) is the single-scalar productivity that the plant would carry at the reference operating point (Vh,tref,Qh,tref)(V^{ref}_{h,t},\, Q^{ref}_{h,t}). The derivation depends on the active generation model at stage tt:

Generation modelρeq,h,t\rho_{eq,h,t} derivation
constant_productivityA per-(hydro, stage) numeric value authored by the case — see “Authoring sources” below.
linearized_headA per-(hydro, stage) numeric value authored by the case — same resolution as constant_productivity.
fphaρeq,h,t=ρesp,hheq(Vh,tref,Qh,tref)\rho_{eq,h,t} = \rho_{esp,h} \cdot h_{eq}(V^{ref}_{h,t},\, Q^{ref}_{h,t}), where heqh_{eq} is the net head computed from the VHA geometry (section 2.3) at the reference point. FPHA hydros do not author a separate ρeq\rho_{eq} scalar in the production-models input — it is derived. A parquet-level override is still accepted (see “FPHA override path”).

Reference volume: the reference operating volume Vh,trefV^{ref}_{h,t} is declared by a single reference_volume field in the production-model config entry (stage_range or seasonal). The field takes exactly one of two forms: an absolute volume_hm3 value, or a percentile (fraction of useful volume between VminV_{min} and VmaxV_{max}); when absent it defaults to 65% of useful volume. This single field is the source of truth for the ρeq\rho_{eq} derivation above and for the reservoir reference level at which the computed-FPHA piecewise-quartic tailrace families are interpolated (§2.3.1 — a plant’s reference volume sets the downstream forebay level seen by the plant immediately upstream). It does not set the computed-FPHA volume fitting window [Vmin,Vmax][V_{min}, V_{max}]: that window is configured separately by the optional fitting_window block inside fpha_config (§2.6.1), defaulting to the plant’s full forebay storage range.

The reference flow QrefQ^{ref} is typically set at installed turbine capacity.

Two complementary inputs supply ρeq,h,t\rho_{eq,h,t} for constant_productivity and linearized_head plants:

  1. Range-level productivity in the hydro production models input. Each stage_range or seasonal entry may carry a single productivity_mw_per_m3s value that applies to every stage the entry covers. This is the natural authoring shape for “this productivity is the same for the next five stages” — declarative, low-volume.
  2. Per-stage productivity in the hydro energy productivity input (a per-row table indexed by (hydro_id, stage_id)). Each row may name a specific stage or carry a per-hydro default (no stage_id set). This is the natural authoring shape for “this productivity changes every stage” — tabular, high-volume.

Either source — but not both for the same (hydro, stage) — may supply the value. The contract is symmetric with the generic-constraint authoring contract: a declarative JSON file owns model selection plus range-level values, and a tabular parquet file owns per-stage numerical refinement.

For a non-FPHA hydro hh at study stage tt, the value of ρeq,h,t\rho_{eq,h,t} is resolved in this order:

  1. A per-stage row in the energy-productivity input whose stage_id matches tt exactly.
  2. A per-hydro default row in the energy-productivity input (no stage_id set) for hydro hh.
  3. The productivity_mw_per_m3s value from the matching stage_range or seasonal entry in the production-models input.

The first three options are mutually exclusive at load time (see “Conflict and coverage” below), so this ordering is descriptive rather than a precedence in the sense of “earlier source overrides later”; it is the order in which the resolver consults sources, stopping at the first hit.

Two load-time invariants are enforced:

  • Conflict: when the per-stage parquet row (or the per-hydro default) and the production-models JSON entry both supply a value for the same (h,t)(h, t), the case is rejected at load time. The error names both files and the offending (h,t)(h, t).
  • Coverage: when neither source supplies a value for some study stage tt of a non-FPHA hydro hh, the case is rejected at load time. The error names the offending (h,t)(h, t) pair.

A coverage failure never reaches the dispatch pipeline — it is caught alongside the other case-validation rules. This is the same boundary discipline used elsewhere in the load pipeline: structural problems surface as load-time errors, never as deeper dispatch-time panics.

For FPHA hydros, ρeq\rho_{eq} is derived from VHA geometry and ρesp\rho_{esp} at the reference operating point. The per-stage energy-productivity input remains available as an override: if a row supplies a value for an FPHA (h,t)(h, t), it replaces the derived value. The production-models JSON file does not accept productivity_mw_per_m3s for FPHA — that field is rejected at parse time. FPHA hydros are therefore exempt from the non-FPHA conflict and coverage rules.

A resolved ρeq,h,t=0\rho_{eq,h,t} = 0 is accepted for non-FPHA hydros and a resolved ρesp,h=0\rho_{esp,h} = 0 is accepted for FPHA hydros. Both are interpreted as a planned outage for the affected stage: the LP uses these scalars as multipliers (never divisors), so zero productivity produces zero generation cleanly without any divide-by-zero or feasibility hazard. The same relaxation applies to the parquet ρeq\rho_{eq} override, the parquet ρesp\rho_{esp} column, and the JSON range-level productivity. Negative values are still rejected at load time as nonsensical.

This relaxation lets real-world cases mark a plant as out-of-service for specific stages without the case author needing to remove it from the system definition, restructure the cascade, or work around a strict-positivity check.

5.2 Accumulated Cascade Productivity ρacum\rho_{acum}

Section titled “5.2 Accumulated Cascade Productivity ρacum\rho_{acum}ρacum​”

The accumulated productivity ρacum,h,t\rho_{acum,h,t} (MW per m³/s) is the energy that one m³/s of incremental inflow into plant hh contributes once it is routed through plant hh and every plant downstream of hh along the cascade:

ρacum,h,t  =  ρeq,h,t  +  hdownstream(h)ρeq,h,t\rho_{acum,h,t} \;=\; \rho_{eq,h,t} \;+\; \sum_{h' \,\in\, \text{downstream}(h)} \rho_{eq,h',t}

The sum is taken in topological order over the cascade (see system elements for the cascade topology). Plants with no downstream successors have ρacum=ρeq\rho_{acum} = \rho_{eq}. The accumulation is per-stage because each summand can vary by stage.

ρacum\rho_{acum} converts hydraulic quantities to energy units that downstream reporting expects:

Incremental inflow energy (MW):

ENAh,k  =  ρacum,h,tah,k\text{ENA}_{h,k} \;=\; \rho_{acum,h,t} \cdot a_{h,k}

This is the rate-form natural energy inflow in MW. Stagewise energy (MWh) is recovered by multiplying by block duration τk\tau_k in hours.

Stored reservoir energy (MWh):

EARMhinit  =  (VhinitVhmin)ρacum,h,t1063600EARMhfinal  =  (VhfinalVhmin)ρacum,h,t1063600\text{EARM}^{\,\text{init}}_h \;=\; (V^{init}_h - V^{min}_h) \cdot \rho_{acum,h,t} \cdot \frac{10^6}{3600} \qquad \text{EARM}^{\,\text{final}}_h \;=\; (V^{final}_h - V^{min}_h) \cdot \rho_{acum,h,t} \cdot \frac{10^6}{3600}

The conversion factor 106/360010^6 / 3600 converts hm³ to m³ and seconds to hours so that storage in hm³ multiplied by productivity in MW/(m³/s) yields MWh.

These quantities do not enter the LP — they are accounting outputs derived from the LP solution. Their methodology relevance is that they make the production model auditable in the same energy units used by the load forecast and the cost objective.

The full FPHA production function (section 2) is multi-dimensional and concave; constant productivity is a scalar but per-plant; linearized head is bilinear in (q,vavg)(q, v^{avg}). None of these can be summed across a cascade or scaled by inflow without a reference operating point. The energy-conversion scalars resolve this: each model is reduced to one number per (hydro, stage), at one operating point, and that number is what the cascade-summation, ENA, and EARM formulas above can consume uniformly. The LP continues to enforce the full production model — the scalar reduction is for accounting, not for dispatch.

Data SourceRequired FieldsUsed For
Hydro plant entitytailrace (polynomial, piecewise, or piecewise-quartic families)htail(qout)h_{tail}(q_{out}) computation
Hydro plant entityhydraulic_losses (factor or constant)hlossh_{loss} computation
Hydro plant entityefficiency (constant)Turbine efficiency η\eta
Hydro plant entityspecific_productivity_mw_per_m3s_per_m (FPHA)ρesp\rho_{esp} for ρeq\rho_{eq} derivation (§5.1)
Hydro plant entityCascade topology (downstream pointer)ρacum\rho_{acum} topological sum (§5.2)
Hydro production models inputRange-level productivity per stage_range / seasonal entry (non-FPHA, optional)ρh,t\rho_{h,t} for sections 1 and 3 (§5.1 authoring source 1)
Hydro production models inputreference_volume per stage_range / seasonal entry (absolute or percentile)VrefV^{ref} for the energy-conversion reduction (§5.1) and the tailrace backwater reference level (§2.3.1)
Hydro production models inputFPHA fitting_window per stage_range / seasonal entry (absolute or percentile bounds, optional)Computed-FPHA volume grid [vmin,vmax][v_{min}, v_{max}] (§2.6.1)
Hydro production models inputFPHA fitting configuration and optional fpha_plane_reduction blockGrid sizes, plane reduction method (§2.6–§2.8)
Hydro energy productivity inputPer-(hydro, stage) equivalent_productivity_mw_per_m3sρeq,h,t\rho_{eq,h,t} override (§5.1 authoring source 2 + FPHA override path)
Hydro geometryvolume_hm3, height_mhfore(v)h_{fore}(v) interpolation (§2.3)
Pre-fitted FPHA planesγ0,γv,γq,γs\gamma_0, \gamma_v, \gamma_q, \gamma_s (plus kappa column retained for back-compat, defaults to 1.0)Optional alternative to in-process fitting

For non-FPHA hydros, the per-(hydro, stage) productivity coefficient is resolved from exactly one of the two authoring sources listed in §5.1: range-level productivity in the production-models input, or per-stage productivity in the energy-productivity input. Supplying a value from both for the same (h,t)(h, t) is rejected at load time; supplying neither for any study stage of a non-FPHA hydro is also rejected at load time.

For FPHA hydros, the production-models input does not accept a productivity scalar — ρeq\rho_{eq} is derived from VHA geometry and ρesp\rho_{esp} unless the energy-productivity input supplies an override.

The methodology above defines the hydro production models; the tabs below cover how Cobre’s software surface configures, feeds, and reports on them.

Cobre’s system/hydros.json and system/hydro_production_models.json files author the entities and production-model selection the methodology above describes. This tab is the field-level configuration reference; the equations these fields feed are in the sections above.

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í",
"bus_id": 0,
"downstream_id": null,
"operational_start_date": "1984-11-22",
"entry_stage_id": null,
"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
},
"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
}
}
]
}

Only id, name, bus_id, downstream_id, operational_start_date, reservoir, outflow, and generation 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.
bus_idintegerYesIdentifier of the electrical bus to which this plant’s generation is injected. Must match an id in buses.json.
downstream_idinteger or nullYesIdentifier of the plant that receives this plant’s outflow. 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 Implementation notes and system elements §5) — 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_hm3numberMinimum operational storage (dead volume). Water below this level cannot reach the turbine intakes. For plants that can empty completely, use 0.0.
max_storage_hm3numberMaximum operational storage (flood control level). Must be strictly greater than min_storage_hm3.

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.

The generation block configures the turbine model for dispatch purposes and provides the default production function used when no hydro_production_models.json entry lists this plant. All variants share turbine bounds (min_turbined_m3s, max_turbined_m3s) and generation bounds (min_generation_mw, max_generation_mw); the model key selects the production function.

"generation": {
"model": "constant_productivity",
"min_turbined_m3s": 0.0,
"max_turbined_m3s": 50.0,
"min_generation_mw": 0.0,
"max_generation_mw": 50.0
}
FieldTypeDescription
modelstringProduction function variant. See the model table below.
min_turbined_m3snumberMinimum turbined flow [m³/s]. Non-zero values model a minimum stable turbine operation.
max_turbined_m3snumberMaximum turbined flow (installed turbine capacity) [m³/s].
min_generation_mwnumberMinimum electrical generation [MW].
max_generation_mwnumberMaximum electrical generation (installed capacity) [MW].

Available production function models:

Modelmodel valuePhase availabilityDescription
Constant productivity"constant_productivity"Training + simulationpower = productivity * turbined_flow. Independent of reservoir head. Productivity is supplied per stage range or season in system/hydro_production_models.json.
FPHA"fpha"Training + simulationPiecewise-linear envelope of the nonlinear production function. Head-dependent. Configured via hydro_production_models.json — see below.
Linearized head"linearized_head"Simulation-onlyHead-dependent productivity linearized around a reference volume. Rejected if selected during training — see methodology §3.1 for why the bilinear term breaks the fixed-LP-structure requirement SDDP depends on.

For most initial studies, constant_productivity is the natural choice. The productivity coefficient encodes the plant’s average efficiency and net head: for a plant with 80 m net head and 90% efficiency, the theoretical productivity is approximately 9.81 × 80 × 0.90 / 1000 ≈ 0.706 MW/(m³/s).

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 outflow is delivered to the downstream reservoir after this delay instead of in the same stage. Absent, null, or 0.0 means an instantaneous transfer.

When an arc is declared, Cobre carries the water in transit as additional Bellman state (see LP formulation §5d and system elements §5). 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 be operating over the arrival window — see Implementation notes.

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 , 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 (methodology §2.3.1) — overrides the entity-level model for any plant that has rows in that file.

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 net head) 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 Implementation notes for exactly what “no usable curve” means and what Cobre 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
}
FieldDescription
start_stage_idStage index at which filling begins (inclusive); filling runs through the stage before entry_stage_id.
filling_min_rate_m3sPer-stage minimum accumulation rate [m³/s]: 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, its soft-floor penalty, and the load-time filling-sufficiency check are covered in Penalty System §6.

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,
"water_withdrawal_violation_pos_cost": 1200.0,
"water_withdrawal_violation_neg_cost": 800.0,
"evaporation_violation_pos_cost": 5000.0,
"evaporation_violation_neg_cost": 5000.0,
"inflow_nonnegativity_cost": 1000.0
}

When present, the block must supply every field; the directional *_pos_cost / *_neg_cost fields override the symmetric default for their direction only. 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)

system/hydro_production_models.json — Production Model Selection

Section titled “system/hydro_production_models.json — Production Model Selection”

This optional file maps each hydro plant to a production-model selection strategy, overriding generation.model from hydros.json for the stages or seasons it lists. Two selection strategies are supported.

stage_ranges — assigns a model to each contiguous stage interval:

{
"production_models": [
{
"hydro_id": 1,
"selection_mode": "stage_ranges",
"stage_ranges": [
{
"start_stage_id": 0,
"end_stage_id": null,
"model": "fpha",
"fpha_config": { "source": "precomputed" }
}
]
}
]
}

seasonal — assigns a model by season index, with a fallback for seasons not listed:

{
"production_models": [
{
"hydro_id": 1,
"selection_mode": "seasonal",
"default_model": "constant_productivity",
"seasons": [
{
"season_id": 0,
"model": "fpha",
"fpha_config": {
"source": "computed",
"volume_discretization_points": 7,
"turbine_discretization_points": 7
}
}
]
}
]
}

Season indices are 0-based and match the season map in stages.json.

Each stage range or season entry may carry an optional reference_volume sibling of fpha_config (not nested inside it), declaring the reference operating volume the equivalent-productivity derivation and the computed-FPHA tailrace backwater interpolation consume. Set exactly one of:

  • volume_hm3 — an absolute value in hm³ (finite, > 0.0).
  • percentile — a fraction in [0.0, 1.0] of the plant’s operating range.
"reference_volume": { "percentile": 0.65 }

When a plant is configured with model: "fpha", fpha_config.source selects where the hyperplane coefficients come from.

source: "precomputed" — hyperplanes are loaded directly from system/fpha_hyperplanes.parquet:

"fpha_config": { "source": "precomputed" }

No additional fpha_config fields are needed; discretization and fitting options are ignored. The parquet schema:

ColumnTypeRequiredDescription
hydro_idINT32YesHydro plant identifier
stage_idINT32?NoStage the plane applies to (null = all stages)
plane_idINT32YesPlane index within this hydro
gamma_0DOUBLEYesIntercept coefficient (MW)
gamma_vDOUBLEYesVolume coefficient (MW/hm³). Must be non-negative (>= 0); run-of-river plants have gamma_v = 0.
gamma_qDOUBLEYesTurbined flow coefficient (MW per m³/s)
gamma_sDOUBLEYesSpillage coefficient (MW per m³/s). Must be <= 0.
kappaDOUBLE?NoIntercept-only correction factor, defaulting to 1.0; validated in (0, 1]. Applies only to precomputed planes — the computed path does not use kappa (see Implementation notes).
valid_v_min_hm3DOUBLE?NoMinimum volume where this plane is valid (hm³)
valid_v_max_hm3DOUBLE?NoMaximum volume where this plane is valid (hm³)
valid_q_max_m3sDOUBLE?NoMaximum turbined flow where this plane is valid (m³/s)

source: "computed" — hyperplanes are fitted at runtime from the plant’s physical geometry. Cobre evaluates the exact production function on a (volume, turbined-flow) grid at spillage = 0 — the flow axis starts at q = 0, whose zero-flow column anchors the lower closure, so there is no synthetic closing point and no spillage axis in the cloud — takes the 3-D convex hull of the resulting cloud with vendored qhull, applies a least-squares alpha_FPHA correction that scales the whole affine plane (intercept and slopes together, not just the intercept), and fits a per-plane lateral-flow secant. Fits are resolved independently per stage range or season. Run-of-river plants (a single fitting volume) are supported: the fitter synthesizes two nearby volume samples to keep the hull non-degenerate, then snaps the resulting gamma_v to exactly 0. See Implementation notes for the full fitting-pipeline summary and methodology §2.6–§2.7 for the derivation.

This source requires tailrace, hydraulic_losses, and efficiency models in hydros.json, plus at least 1 row in system/hydro_geometry.parquet for the plant (a single row is valid for run-of-river plants; linearized_head still needs at least 2 rows because it fits a head slope in volume — that constraint does not apply to FPHA).

"fpha_config": {
"source": "computed",
"volume_discretization_points": 5,
"turbine_discretization_points": 5,
"spillage_discretization_points": 5,
"max_planes_per_hydro": 10,
"fitting_window": null
}

All fields except source are optional:

FieldDefaultDescription
volume_discretization_points5Number of volume grid points for fitting. Must be >= 2.
turbine_discretization_points5Number of turbined-flow grid points. Must be >= 2.
spillage_discretization_points5Validated (must be >= 2) but does not add a spillage axis to the fitting grid — the cloud and the alpha_FPHA regression fix spillage at 0, and the lateral-flow secant sweeps its own sample range independently (§2.7). The field is retained for input round-tripping.
max_planes_per_hydro10Accepted and validated (>= 1) but does not currently trim the plane set — the hull fitter emits exactly the upper-envelope facets.
fitting_windownullOptional volume range for fitting; absent uses the full operating range [min_storage_hm3, max_storage_hm3].

fitting_window restricts which portion of the operating range builds the grid — useful when the plant rarely operates near one extreme:

"fitting_window": { "volume_min_hm3": 1000.0, "volume_max_hm3": 40000.0 }
"fitting_window": { "volume_min_percentile": 5.0, "volume_max_percentile": 95.0 }

Absolute (_hm3) and percentile (_percentile) bounds are mutually exclusive per bound (min and max may use different modes). For the 5% fit-quality warning emitted after fitting, and for the round-trip parquet export of computed planes, see Implementation notes.

The optional file-level fpha_plane_reduction block merges near-parallel or near-coincident FPHA planes after fitting. Off by default (absent = no reduction); applied uniformly to every plant in the file.

Angle method — merges planes whose normal vectors are within tolerance_deg degrees:

"fpha_plane_reduction": { "method": "angle", "tolerance_deg": 2.0 }
FieldRequiredDescription
methodYesMust be "angle".
tolerance_degYesMaximum angle between plane normals to merge them. Finite, in [0.0, 90.0].

Distance method — merges planes whose sampled mean-squared distance stays within tolerance_pct, using n_samples sample points:

"fpha_plane_reduction": { "method": "distance", "tolerance_pct": 0.01, "n_samples": 200 }
FieldRequiredDescription
methodYesMust be "distance".
tolerance_pctYesMaximum relative MSE distance (fraction) to treat two planes as coincident. Finite, >= 0.0.
n_samplesYesNumber of sample points used to estimate the distance. Must be >= 1.

Supplying a field belonging to the other method is a load-time error. The origin plane (zero generation at zero turbining) is never merged. The distance method’s sample draws are deterministically seeded — bit-identical across input ordering and rank count.

For constant_productivity and linearized_head hydros, the equivalent productivity for each (hydro, stage) pair is supplied by exactly one of two sources:

  • system/hydro_production_models.json — an inline productivity_mw_per_m3s field on the matching stage_range or seasonal entry. Natural for “this productivity is constant for the next five stages.”
  • system/hydro_energy_productivity.parquet — a row in the equivalent_productivity_mw_per_m3s column; a row with stage_id refines a single stage, a row with stage_id = null is a per-hydro default. Natural for per-stage numerical refinement.

Resolution order at load time:

  1. Parquet stage-specific row (exact stage_id match).
  2. Parquet per-hydro default row (stage_id = null).
  3. JSON productivity_mw_per_m3s on the matching stage range or season.

Supplying a value from both sources for the same (hydro, stage) is a load-time error; supplying neither is also a load-time error.

{
"start_stage_id": 12,
"end_stage_id": 24,
"model": "constant_productivity",
"productivity_mw_per_m3s": 0.72
}
FieldTypeRequiredDescription
productivity_mw_per_m3snumberOptional (non-FPHA)Finite and non-negative (>= 0.0) when present; 0.0 marks a planned-outage stage. Rejected on FPHA — FPHA derives productivity from VHA geometry, not a scalar coefficient.
RuleError ClassDescription
Bus reference integrityReference errorEvery bus_id must match an id in buses.json.
Downstream reference integrityReference errorEvery non-null downstream_id must match an id in hydros.json.
Cascade acyclicityTopology errorThe directed graph of downstream_id links must be acyclic.
Storage bounds orderingPhysical feasibilitymin_storage_hm3 must be less than max_storage_hm3.
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 completenessReference errorEvery hydro plant must have exactly one entry in initial_conditions.json (storage or filling_storage, not both).
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].
FPHA geometry coverageDimensional errorEvery fpha plant must have at least 1 row in system/hydro_geometry.parquet (a single row is valid for run-of-river plants); every linearized_head plant needs at least 2 rows.
FPHA plane coverageDimensional errorEvery (hydro_id, stage_id) group in system/fpha_hyperplanes.parquet must have at least 1 plane.
FPHA coefficient signsSemantic errorgamma_v must be non-negative (>= 0; run-of-river plants have gamma_v = 0); gamma_s must be non-positive.
Geometry monotonicitySemantic errorvolume_hm3 must be strictly increasing; height_m and area_km2 must be non-decreasing.
  • Notation conventions — variable and set definitions (ghg_h, qhq_h, vhv_h, shs_h, ρh\rho_h)
  • System elements — hydro plant element description, decision variables, Variable Units Convention
  • LP formulation — how production constraints integrate into the assembled LP
  • Penalty systemturbined_cost regularization, penalty priority ordering
  • Cut management — Benders cut generation affected by FPHA dual variables
  • Equipment formulations — thermal and hydro equipment constraint patterns