Skip to content

Hydro Production Function Models

This chapter defines the hydro generation constraint models supported by Novomodelo, which relate turbined flow and reservoir storage to electrical output. Two production functions shape dispatch: constant productivity and FPHA. A third model name — linearized head — is a reserved alias for constant productivity (see §3). The choice between constant productivity and FPHA 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 Element Modeling Overview. For variable definitions see Notation Conventions; for LP integration see LP Formulation; for hydro element descriptions see System Element Modeling Overview.

The simplest model assumes a linear relationship:

gh,k=ρh,t⋅qh,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 in (0,1](0, 1])
  • 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 supplied.

LP treatment: constant productivity adds no generation column and no production row: the turbined-flow column of each (hydro, bus) cell (§2.9) enters its bus’s load balance with coefficient ρh,t\rho_{h,t} in every block, so gh,k=ρh,t⋅qh,kg_{h,k} = \rho_{h,t} \cdot q_{h,k} holds by construction. 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 (Approximate Hydroelectric Production Function)

Section titled “2. FPHA (Approximate Hydroelectric Production Function)”

For accurate modeling of hydroelectric generation, FPHA (Approximate Hydroelectric 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 Novomodelo 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 the notation of the LP Formulation; for equivalent terms in other planning tools, see the Glossary.

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

ϕ(v,q,s)=(9.81 ηh/1000)⋅q⋅hnet\phi(v, q, s) = (9.81\,\eta_h/1000) \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
  • ηh\eta_h = turbine efficiency (section 1), constant per plant; the factor 9.81 ηh/10009.81\,\eta_h/1000 (MW·s/m⁴) converts hydraulic power to electrical power

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, Novomodelo approximates ϕ\phi with a set of linear hyperplanes.

Novomodelo 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 plant’s volume-height curve. For storage vv where v(i)≤v<v(i+1)v^{(i)} \leq v < v^{(i+1)}:

hfore(v)=h(i)+h(i+1)−h(i)v(i+1)−v(i)×(v−v(i))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:

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: 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 hh‘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(n)(qjus)=c0(n)+c1(n)qjus+c2(n)qjus2+c3(n)qjus3+c4(n)qjus4h_{tail}^{(n)}(q_{jus}) = c_0^{(n)} + c_1^{(n)} q_{jus} + c_2^{(n)} q_{jus}^2 + c_3^{(n)} q_{jus}^3 + c_4^{(n)} q_{jus}^4

evaluated via Horner’s method for numerical stability. Consecutive segments are contiguous in outflow and agree in level at their shared boundary (C0 continuity) to calibration precision (Implementation notes, “Computed-FPHA fitting behavior”). 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×(hfore−htail)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 exact production function converts hydraulic power to electrical power with the factor 9.81 ηh/10009.81\,\eta_h/1000 (MW per (m³/s · m)), so ϕ=(9.81 ηh/1000)⋅q⋅hnet\phi = (9.81\,\eta_h/1000) \cdot q \cdot h_{net} in MW. Novomodelo uses constant efficiency ηh\eta_h per plant. The specific productivity ρesp\rho_{esp}, in the same unit, is a separate plant input that the exact production function and the FPHA fit do not use.

An FPHA plant has no single scalar productivity ρ\rho. The fitted planes follow from ηh\eta_h and the topology functions alone, while the equivalent productivity ρeq\rho_{eq} at the reference operating point is derived from a specific productivity ρesp\rho_{esp} supplied for the plant (overridable per stage) 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 approximation of the exact surface (an outer approximation of the capped cloud before the kFPHAk_{FPHA} correction of §2.6.3). Each hyperplane mm defines an upper bound on generation:

gh,k≤γ0m+γvm⋅vhavg+γqm⋅qh,k+γsm⋅sh,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 plane is fitted once per plant (§2.1–§2.8); a plant split across buses evaluates it per (hydro, bus) cell at LP-integration time, apportioning its flow-independent terms by each cell’s share of the plant’s turbine capacity — see §2.9 for the exact per-cell form.

Physical interpretation of coefficients:

CoefficientSignMeaning
γ0m\gamma_0^m—Intercept (MW at zero storage, flow, spillage); its sign is not validated
γvm\gamma_v^m≥ 0Higher storage → higher forebay → more generation
γqm\gamma_q^m≥ 0 (> 0 on at least one plane per stage for precomputed planes)More turbined flow → more generation
γsm\gamma_s^m≤ 0More spillage → higher tailrace → less net head

Source of hyperplanes: planes are either pre-computed (supplied with the case) 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 → kFPHAk_{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 ϕ=(9.81 ηh/1000)⋅q⋅hnet\phi = (9.81\,\eta_h/1000) \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: a configurable number of uniformly spaced values spanning the fitting window [vmin,vmax][v_{min}, v_{max}]. The window lies within the plant’s forebay storage range (the full range unless narrowed) and is distinct from the reference volume (§5.1), which fixes an operating point, not this window. The point counts and the window are set on the Configure tab.
  • Flow axis: a configurable number of uniformly spaced values spanning [0,qmax][0, q_{max}]. 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.

Plants without turbine capacity: when qmax=0q_{max} = 0 the flow axis collapses onto q=0q = 0, where generation is zero, so the cloud carries no production and no plane survives the hull. Such a plant is modelled with zero productivity at every stage instead of a plane set; the implementation notes give the capacity threshold and how the plant is reported.

Run-of-river plants: when the plant has a single fitting volume (vmin≈vmaxv_{min} \approx v_{max}, or a single volume sample requested), two nearby volume samples are synthesized to keep the 3-D hull non-degenerate (Implementation notes, “Run-of-river support”). The resulting γvm\gamma_v^m is snapped to exactly 0 when its magnitude is negligible, enforcing the run-of-river semantics (γvm=0\gamma_v^m = 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=γ0m+γvmV+γqmQ\text{generation} = \gamma_0^m + \gamma_v^m V + \gamma_q^m Q. Before the kFPHAk_{FPHA} correction of §2.6.3, the result is a concave outer approximation (the smallest concave function lying above min⁡(ϕ,Gˉh)\min(\phi, \bar{G}_h) at the capped cloud points); 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 kFPHAk_{FPHA} Correction

Section titled “2.6.3 Least-Squares kFPHAk_{FPHA}kFPHA​ Correction”

The raw hull envelope FPHA0FPHA_0 is optimistic where ϕ\phi is non-concave and pessimistic where it is concave. A single scalar kFPHAk_{FPHA} corrects the bias by minimising the mean-squared error between kFPHA⋅FPHA0k_{FPHA} \cdot FPHA_0 and the capped production min⁡(ϕ,Gˉh)\min(\phi, \bar{G}_h) over the spill=0=0 grid:

kFPHA=∑i,jFPHA0(Vi,Qj) min⁡(ϕ(Vi,Qj),Gˉh)∑i,jFPHA0(Vi,Qj)2,FPHA=kFPHA⋅FPHA0k_{FPHA} = \frac{\sum_{i,j} FPHA_0(V_i,Q_j)\,\min\big(\phi(V_i,Q_j), \bar{G}_h\big)}{\sum_{i,j} FPHA_0(V_i,Q_j)^2}, \qquad FPHA = k_{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 kFPHAk_{FPHA} toward the larger-deviation spill region and degrade the no-spill operating region.
  • kFPHAk_{FPHA} scales the whole affine function (γ0m,γvm,γqm\gamma_0^m, \gamma_v^m, \gamma_q^m alike), not just the intercept. It therefore differs from the factor κ\kappa of precomputed planes, which scales the intercept only.
  • kFPHAk_{FPHA} may be greater or less than 1 (an MSE balance, not a one-sided shrink). Validation requires kFPHA>0k_{FPHA} > 0. A degenerate denominator (all-zero production) yields the neutral kFPHA=1k_{FPHA} = 1.
  • Validation also requires γvm≥0\gamma_v^m \ge 0, γqm≥0\gamma_q^m \ge 0, γsm≤0\gamma_s^m \le 0.

Fit-quality diagnostic: after the full pipeline, the relative mean-absolute-deviation of the emitted min-envelope vs the capped production min⁡(ϕ,Gˉh)\min(\phi, \bar{G}_h) over the spill=0=0 grid is computed. A warning is emitted (in canonical plant/stage order) when it exceeds a threshold (Implementation notes, “Fit-quality warning”) — typically indicating a strongly non-concave surface that no single kFPHAk_{FPHA} can track well.

Precomputed input: precomputed planes may carry an intercept factor κ∈(0,1]\kappa \in (0, 1] (Configure tab) that scales each plane’s intercept only (γ0m←γ0m⋅κ\gamma_0^m \leftarrow \gamma_0^m \cdot \kappa), unlike the whole-affine kFPHAk_{FPHA}. The computed path does not use κ\kappa; its kFPHAk_{FPHA} correction plays that role.

The figure below shows a synthetic plant with normalized axes: storage, flow and generation are fractions of vmaxv_{max}, qmaxq_{max} and Gˉh\bar{G}_h. It slices the fit at one grid volume, which the example takes as the reference volume VrefV^{ref} of §5.1, and plots five series against turbined flow: the cloud points at that volume; the exact production ϕ\phi capped at Gˉh\bar{G}_h; the raw hull FPHA0FPHA_0, the minimum over the hull planes, which lies on or above every cloud point; the corrected envelope kFPHA⋅FPHA0k_{FPHA} \cdot FPHA_0, which rescales the hull by the single scalar of this section; and the dashed line ρeq q\rho_{eq}\,q of §5.1, which, as the example sets ρesp\rho_{esp} to 9.81 ηh/10009.81\,\eta_h/1000, meets the uncapped ϕ\phi at qmaxq_{max}, above the capped curve where the capacity binds.

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

Smax={2⋅MLTif MLT>02⋅qmaxif 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 kFPHAk_{FPHA} regression (§2.6), which both use the capacity-capped output.

Sign: γsm≤0\gamma_s^m \le 0 (more lateral flow raises the tailrace, reduces generation). A slope that is non-negative or negligibly negative is snapped to exactly 0 (Implementation notes, “Computed-FPHA fitting behavior”) to protect LP column scaling from near-zero structural coefficients.

Lateral axis: the plant’s own spillage (qlat=sq_{lat} = s); upstream releases and post-incremental inflows do not enter it.

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:

  • Angle: merge consecutive pairs whose normal-vector angle θ=arccos⁡(n1⋅n2/∥n1∥∥n2∥)\theta = \arccos(\mathbf{n}_1 \cdot \mathbf{n}_2 / \|\mathbf{n}_1\|\|\mathbf{n}_2\|) satisfies θ<ε\theta < \varepsilon (strict), with θ\theta and the tolerance ε\varepsilon in degrees. Fully deterministic from coefficients.
  • Distance: merge consecutive pairs whose normalised mean-squared generation difference δ=EQM/ϕmax⁡2<ε/100\delta = \text{EQM}/\phi_{\max}^2 < \varepsilon/100, where ϕmax⁡\phi_{\max} is the largest value of the exact production ϕ\phi (uncapped) over the fitting grid, with the tolerance ε\varepsilon in percent and EQM estimated over sampled operating points. 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 (γ0m≈0∧γvm≈0\gamma_0^m \approx 0 \wedge \gamma_v^m \approx 0, generating zero power at zero turbining) is never merged. This guarantees the zero-generation floor.

Reduction is optional and applies only when selected (Configure tab).

A hydro plant is partitioned into one or more (hydro, bus) cells — one per distinct bus among its declared unit groups (see System Element Modeling Overview §5) — and FPHA is evaluated per cell, not per plant: a plant’s fitted hyperplane set (§2.1–§2.8) is fitted once per plant and consumed by every one of its cells, apportioned as described below. For a single-cell plant the per-cell constraint is the plant-level constraint.

For each cell (h,b)(h, b) of a hydro hh using FPHA, block kk, and plane m∈Mhm \in \mathcal{M}_h:

gh,b,k−λh,b γvm⋅vhavg−γqm⋅qh,b,k−λh,b γsm⋅sh,k≤λh,b γ0mg_{h,b,k} - \lambda_{h,b} \, \gamma_v^m \cdot v_h^{avg} - \gamma_q^m \cdot q_{h,b,k} - \lambda_{h,b} \, \gamma_s^m \cdot s_{h,k} \leq \lambda_{h,b} \, \gamma_0^m

Every variable sits on the left with its plane coefficient negated, and the apportioned intercept is the row’s upper bound; the row is the cap gh,b,k≤λh,b(γ0m+γvm⋅vhavg+γsm⋅sh,k)+γqm⋅qh,b,kg_{h,b,k} \leq \lambda_{h,b} \big( \gamma_0^m + \gamma_v^m \cdot v_h^{avg} + \gamma_s^m \cdot s_{h,k} \big) + \gamma_q^m \cdot q_{h,b,k}.

The coefficients are already kFPHAk_{FPHA}-scaled — there is no separate pre-scaling step. These are hard constraints — no slack variables.

λh,b\lambda_{h,b} apportions the plane’s flow-independent part — the intercept γ0m\gamma_0^m, the storage term, and the spillage term — by cell (h,b)(h,b)‘s share of the plant’s declared turbine capacity:

λh,b=∑u ∈ (h,b)Qˉu∑u ∈ hQˉu\lambda_{h,b} = \frac{\sum_{u \,\in\, (h,b)} \bar{Q}_u}{\sum_{u \,\in\, h} \bar{Q}_u}

(00 when the plant’s declared total is 00), a study-time constant derived from each unit group’s declared maximum turbined flow — never recomputed per stage or block, and never re-derived from a resolved per-stage override, which would double-count an availability derate already enforced on the flow path by each cell’s own column bounds. It satisfies ∑b∈Bhλh,b=1\sum_{b \in \mathcal{B}_h} \lambda_{h,b} = 1, and a single-cell plant has λh,b=1\lambda_{h,b} = 1 exactly. The flow coefficient γqm\gamma_q^m stays on the cell’s own qh,b,kq_{h,b,k} unscaled — it is the only term homogeneous in the cell partition, which is what makes ∑bgh,b,k\sum_{b} g_{h,b,k} at fixed vhavgv^{avg}_h, sh,ks_{h,k}, and ∑bqh,b,k\sum_b q_{h,b,k} recover the plant-level bound this per-cell form replaces.

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 State Augmentation §2) and vhv_h is end-of-stage storage — a single plant-level quantity shared by every cell, since storage is not partitioned. Both are LP variables, so vhinv^{in}_h appears in every cell’s FPHA constraint with coefficient −λh,b γvm/2-\lambda_{h,b} \, \gamma_v^m / 2 on the left-hand side, as does vhv_h, on a parallel stage; on a chronological stage, in block 1’s rows, and vhv_h in block ∣K∣|\mathcal{K}|‘s. The LP solver automatically accounts for this in the reduced cost of the pinned vhinv^{in}_h column.

On a chronological stage the row of block kk uses that block’s own average storage (vh,k−1+vh,k)/2(v_{h,k-1} + v_{h,k})/2 in place of vhavgv^{avg}_h, with vh,0=vhinv_{h,0} = v^{in}_h (see Block Formulation Variants).

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

  1. Generation is a free LP variable per cell, bounded by [0,Gˉh,b][0, \bar{G}_{h,b}], where Gˉh,b\bar{G}_{h,b} sums the cell’s own unit groups’ declared maximum generation and closes against the plant’s own resolved maximum (see LP Formulation §6)
  2. FPHA constraints (one per plane mm, per cell) provide upper bounds relating generation to storage, flow, and spillage
  3. Where generation has value, the optimizer raises it until an FPHA plane or a tighter bound such as Gˉh,b\bar{G}_{h,b} binds
  4. At an optimum where generation has value, each cell’s generation lies on one of its FPHA hyperplane facets or on a tighter bound, such as Gˉh,b\bar{G}_{h,b}

Key insight: Because minimizing cost includes maximizing hydro generation (which has zero fuel cost), the optimizer naturally pushes generation to the FPHA surface boundary or to a tighter bound such as Gˉh,b\bar{G}_{h,b}. Where generation has no value, turbined flow beyond what the planes convert produces nothing, and the turbined cost of section 2.10 decides between turbining that water and spilling it; on a binding plane with γqm=0\gamma_q^m = 0, spillage still lowers the plane through γsm\gamma_s^m (section 2.7), which the LP weighs against that cost.

A small regularization cost chtcc^{tc}_h is charged on the turbined flow of every cell of every hydro, whatever its production model:

∑kτk∑b∈Bhchtc qh,b,k\sum_{k} \tau_k \sum_{b \in \mathcal{B}_h} c^{tc}_h \, q_{h,b,k}

Where generation has no value, the LP is indifferent between turbining water without generating from it and spilling it; a cost above the spillage cost, chtc>chspillc^{tc}_h > c^{spill}_h, tips it toward spilling. On a binding plane with γqm=0\gamma_q^m = 0, more turbined flow adds no generation, but each unit of spillage lowers the plane by λh,b∣γsm∣\lambda_{h,b} \lvert \gamma_s^m \rvert (sections 2.7 and 2.9), so the LP spills that water only where chtc−chspillc^{tc}_h - c^{spill}_h exceeds the value of the generation lost. A hydro using FPHA requires chtc≥0c^{tc}_h \geq 0 (Penalty System — Category 3).

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 every one of the plant’s cells’ FPHA constraints (via vhavg=(vhin+vh)/2v^{avg}_h = (v^{in}_h + v_h)/2, each apportioned by that cell’s λh,b\lambda_{h,b}, section 2.9) — on a parallel stage; on a chronological stage, in block 1’s rows — the FPHA hyperplane duals of every cell 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 State Augmentation §2) makes its reduced cost capture the total sensitivity βhv\beta^v_h automatically — the LP solver propagates every cell’s FPHA contribution through the single shared vhinv^{in}_h.

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

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

This reduced cost implicitly includes the water balance contribution (that of the row reading vhinv^{in}_h: πhwb\pi^{wb}_h on a parallel stage, πh,1wb\pi^{wb}_{h,1} on a chronological stage), the FPHA contribution summed over every cell of the plant (12∑b∈Bhλh,b∑mπh,b,mfpha⋅γvm\frac{1}{2} \sum_{b \in \mathcal{B}_h} \lambda_{h,b} \sum_m \pi_{h,b,m}^{fpha} \cdot \gamma_v^m, with πh,b,mfpha\pi_{h,b,m}^{fpha} summed over the blocks whose rows read vhinv^{in}_h: every block on a parallel stage, block 1 on a chronological stage), the dual of the evaporation row (LP Formulation — Evaporation Row) weighted by γv,hev/2\gamma^{ev}_{v,h}/2 for a hydro with an evaporation model (block 1’s row only on a chronological stage), and any generic constraint contributions — all resolved by the LP solver without explicit dual combination.

For the complete cut coefficient computation, see Cut Management.

A production model that varies by stage or season is part of the model the policy is trained on: its cuts lower-approximate that model’s cost-to-go whichever production model each stage uses (Cut Management — when bounds and certificates hold). A policy simulated under a different production model than the one it was trained on is a heuristic, with no bound guarantee: the trained lower bound need not bound the simulated model’s optimal value, and the gap between the two certifies nothing.

3. Linearized Head Model (Reserved — Resolves to Constant Productivity)

Section titled “3. Linearized Head Model (Reserved — Resolves to Constant Productivity)”

Linearized head is a reserved production-model name: it is equivalent to constant productivity (§1) in every phase — training (backward and forward passes) and simulation alike — and selecting it applies no head correction and imposes no phase restriction.

Generation is therefore gh,k=ρh,t⋅qh,kg_{h,k} = \rho_{h,t} \cdot q_{h,k} with the plant’s per-(hydro, stage) productivity (§5.1). It still requires the plant’s productivity input.

The substantive choice during training is between constant productivity and FPHA (linearized head is an alias for the former, §3). That choice trades accuracy against computational cost:

ScenarioRecommended ModelRationale
High-head storage reservoirsFPHASignificant head variation
Large storage variation plantsFPHAOperating across wide volume range
Run-of-river plantsFPHA or ConstantThe hull fit handles run-of-river plants (§2.6.1)
Initial algorithm testingConstant productivityFast iteration, debug focus
Near-term stagesFPHAAccuracy for operational decisions
Far-future stagesConstant productivityComputational efficiency

During simulation, constant productivity and FPHA behave as they do in training, and linearized head resolves to constant productivity (§3):

ScenarioRecommended ModelRationale
Plants trained with FPHAFPHAConsistency with training model
Plants trained with constant, low head variationConstant productivityNo benefit from head correction
Plants selecting linearized headConstant productivity (equivalent)Linearized head resolves to the same constant-productivity source (§3); no head correction is applied
Post-optimization validationCompare all modelsVerify approximation quality

Simulating a plant under another production model than the one it was trained with evaluates the policy heuristically (§2.11).

The production model may vary by stage or by season per hydro (Configure tab).

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 — Novomodelo reduces each plant’s production model to a small set of per-(hydro, stage) scalars produced by two evaluators: one evaluated at a representative reference operating point (§5.1–§5.2), and one averaged over the plant’s physical storage range (§5.3). These scalars are computed once at study setup and reused on every stage of every scenario.

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 plant’s declared generation model, not on the model a stage selects:

Generation modelρeq,h,t\rho_{eq,h,t} derivation
Constant productivityA per-(hydro, stage) numeric value authored by the case (see below).
Linearized headA per-(hydro, stage) numeric value authored by the case, supplied as for constant productivity.
FPHAρeq,h,t=ρesp,h,t⋅heq(Vh,tref, Qh,tref)\rho_{eq,h,t} = \rho_{esp,h,t} \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 and ρesp,h,t\rho_{esp,h,t} is resolved per stage: a per-stage override, else a per-plant default override, else the plant’s own value. FPHA hydros author no separate ρeq\rho_{eq} scalar — it is derived, and an override is accepted (see below).

Reference volume: the reference operating volume Vh,trefV^{ref}_{h,t} is declared once per production-model entry (a stage range or a season), either as an absolute volume or as a fraction of the useful volume between VhminV^{min}_h and VhmaxV^{max}_h; when absent a default fraction of the useful volume applies (Configure tab). This single declaration 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 on the Configure tab, defaulting to the plant’s full forebay storage range (§2.6.1).

The reference flow Qh,trefQ^{ref}_{h,t} at which both evaluators compute the net head is the plant’s installed turbined-flow capacity.

Authoring: a non-FPHA plant’s equivalent productivity ρeq,h,t\rho_{eq,h,t} is authored per hydro and stage. An FPHA plant’s equivalent productivity is derived as in the table above and may be overridden; the override replaces the derived value in both evaluators, the reference-point value here and the useful-range mean value of §5.3. A zero equivalent productivity at a stage where the plant does not use the FPHA model is a planned outage: the plant generates nothing at that stage. At a stage that uses the FPHA model, generation follows the fitted planes, not this scalar. The authoring sources, their resolution order and the load-time checks are on the Configure tab.

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  +  ∑h′ ∈ downstream(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 Element Modeling Overview 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.

The reference-point scalars of §5.1–§5.2 value water at a single operating volume. A quantity that values the whole stored volume — stored energy, or a floor on it — needs a productivity representative of every volume the reservoir can occupy. The second evaluator supplies it by averaging the forebay level over the plant’s physical storage range.

Physical storage range. Each plant carries a physical storage range [Vhmin,Vhmax][V^{min}_h, V^{max}_h]: a stage-invariant property of the plant (its dead-volume floor and its full-reservoir ceiling). It is distinct from the per-stage operative bounds [V‾h,Vˉh][\underline V_h, \bar V_h] on the storage variable, which may be tighter at a given stage (for example under a flood-control ceiling). Every quantity in this subsection and in §5.4 reads the physical range, never the operative bounds, so an active operative restriction never truncates a stored-energy value.

Mean forebay level. The forebay level is averaged over the physical range:

hˉfore,h  =  1Vhmax−Vhmin∫VhminVhmaxhfore(v) dv\bar h_{fore,h} \;=\; \frac{1}{V^{max}_h - V^{min}_h} \int_{V^{min}_h}^{V^{max}_h} h_{fore}(v)\, dv

Because hforeh_{fore} is the piecewise-linear volume–height curve of §2.3, this integral is evaluated exactly (a composite trapezoid over the curve’s breakpoints), not approximated by quadrature.

Mean equivalent productivity. With heq(hf,Q)=hf−htail(Q)−hlossh_{eq}(h_f, Q) = h_f - h_{tail}(Q) - h_{loss} the net-head function of §5.1, evaluated from a forebay level hfh_f and the reference flow QQ:

ρˉeq,h,t  =  ρesp,h,t⋅heq(hˉfore,h,  Qh,tref)\bar\rho_{eq,h,t} \;=\; \rho_{esp,h,t} \cdot h_{eq}\big(\bar h_{fore,h},\; Q^{ref}_{h,t}\big)

It is the forebay level that is averaged, not the net head: the tailrace level and the hydraulic losses are then evaluated at Qh,trefQ^{ref}_{h,t} exactly as the reference-point evaluator evaluates them. The two evaluators therefore differ only in the forebay level they feed to the same net-head function.

Resolution order. For each (hydro hh, stage tt):

  1. An explicit equivalent-productivity override replaces both evaluators with the same value, so ρˉeq,h,t=ρeq,h,t\bar\rho_{eq,h,t} = \rho_{eq,h,t} equals the override.
  2. Otherwise, a collapsed physical range (Vhmin=VhmaxV^{min}_h = V^{max}_h), or a plant without volume–height geometry or without a specific productivity ρesp,h,t\rho_{esp,h,t}, gives ρˉeq,h,t=ρeq,h,t\bar\rho_{eq,h,t} = \rho_{eq,h,t}: a zero-width range reduces the average to a point evaluation.
  3. Otherwise, ρˉeq,h,t\bar\rho_{eq,h,t} is the integrated value above.

The integrating evaluator is gated by the plant’s geometry, not by its generation model: it applies to any plant with volume–height geometry and a specific productivity, whether its LP production model is FPHA or constant productivity. A non-positive mean net head is rejected at study setup. The collapsed-range rule is how a run-of-river plant falls out of the data: with a collapsed range it behaves identically under either evaluator.

Mean accumulated productivity. ρˉacum,h,t\bar\rho_{acum,h,t} follows from the mean own terms by the same downstream recurrence as §5.2:

ρˉacum,h,t  =  ρˉeq,h,t  +  ∑h′ ∈ downstream(h)ρˉeq,h′,t\bar\rho_{acum,h,t} \;=\; \bar\rho_{eq,h,t} \;+\; \sum_{h' \,\in\, \text{downstream}(h)} \bar\rho_{eq,h',t}

Maximum stored energy. The energy content of the plant’s full useful volume is

Eh,tmax  =  ρˉacum,h,t (Vhmax−Vhmin)E^{max}_{h,t} \;=\; \bar\rho_{acum,h,t}\,\big(V^{max}_h - V^{min}_h\big)

Its unit is the raw product MW/(m³/s) × hm³ — not MWh (no hm³-to-hours factor is applied).

Security-curve identity. A security curve is a per-stage floor on stored energy expressed as a fraction pp of the maximum stored energy. Written on the outgoing storage vhv_h with both sides on the mean evaluator,

ρˉacum,h,t (vh−Vhmin)  ≥  p Eh,tmax    ⟺    vh−Vhmin  ≥  p (Vhmax−Vhmin)\bar\rho_{acum,h,t}\,\big(v_h - V^{min}_h\big) \;\ge\; p\,E^{max}_{h,t} \;\;\Longleftrightarrow\;\; v_h - V^{min}_h \;\ge\; p\,\big(V^{max}_h - V^{min}_h\big)

states “at least a fraction pp of the useful volume, in energy terms”. The per-plant coefficient cancels only because both sides use the same (mean) evaluator and the same raw unit; mixing the reference-point coefficient on one side with Eh,tmaxE^{max}_{h,t} on the other, or converting one side to MWh alone, would not reduce to the useful-volume fraction. How such a row is authored is described in LP Formulation §10.

ρacum\rho_{acum} and ρˉacum\bar\rho_{acum} convert hydraulic quantities to energy units that downstream reporting expects:

Incremental inflow energy (MW):

ENAh,k  =  ρacum,h,t⋅ah,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):

EARMh init  =  (Vhinit−Vhmin)⋅ρˉacum,h,t⋅1063600EARMh final  =  (Vhfinal−Vhmin)⋅ρˉacum,h,t⋅1063600\text{EARM}^{\,\text{init}}_h \;=\; (V^{init}_h - V^{min}_h) \cdot \bar\rho_{acum,h,t} \cdot \frac{10^6}{3600} \qquad \text{EARM}^{\,\text{final}}_h \;=\; (V^{final}_h - V^{min}_h) \cdot \bar\rho_{acum,h,t} \cdot \frac{10^6}{3600}

where VhminV^{min}_h is the physical minimum storage of §5.3 (not the stage’s operative lower bound) and ρˉacum,h,t\bar\rho_{acum,h,t} is the useful-range mean accumulated productivity. 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; no stage duration enters, so EARM is independent of stage length.

Stored reservoir energy, power form (MW):

EARMh MW  =  EARMh∑k∈Kτk\text{EARM}^{\,\text{MW}}_h \;=\; \frac{\text{EARM}_h}{\sum_{k \in \mathcal{K}} \tau_k}

for either the initial or the final value, where the divisor is the stage’s total block hours — the same divisor for every block of the stage.

EARM and ENA use different evaluators on purpose: ENA values an inflow at the plant’s representative operating point, while EARM values the stored volume over the whole range it can occupy.

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. Neither 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 two numbers per (hydro, stage) — one from the reference-point evaluator, one from the useful-range mean evaluator — and those numbers are what the cascade-summation, ENA, EARM, and maximum-stored-energy formulas above can consume uniformly. The LP’s production model does not use these scalars; it continues to enforce the full production model. They are accounting quantities that may also be used as coefficients of user-authored generic constraints, such as the security curve of §5.3 (see LP Formulation §10).

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

Novomodelo’s system/hydro_production_models.json file authors the production-model selection the methodology above describes, and the generation block of system/hydros.json supplies each plant’s default model and its turbine and generation bounds. The equations these fields feed are in the sections above (Hydro Production Function Models §1–§5). The rest of the plant registry is on System Element Modeling Overview — Configure.

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 valueDescription
Constant productivity"constant_productivity"power = productivity * turbined_flow. Independent of reservoir head. Productivity is supplied per stage range or season in system/hydro_production_models.json.
FPHA"fpha"Piecewise-linear envelope of the nonlinear production function. Head-dependent. Configured via hydro_production_models.json — see below.
Linearized head"linearized_head"Reserved model name; it resolves to the same constant-productivity source as constant_productivity (see methodology §3). Requires the plant’s productivity input.

Every model applies in training and in simulation.

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).

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.

When reference_volume is omitted entirely, the reference volume defaults to 0.65 of the operating band (equivalent to { "percentile": 0.65 }).

"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_idInt32NoStage the plane applies to (null = all stages; an absent column means the same)
plane_idInt32YesPlane index within this hydro
gamma_0Float64YesIntercept coefficient (MW)
gamma_vFloat64YesVolume coefficient (MW/hm³). Must be non-negative (>= 0); run-of-river plants have gamma_v = 0.
gamma_qFloat64YesTurbined flow coefficient (MW per m³/s). Must be non-negative (>= 0); each stage a plant runs on precomputed planes needs at least one plane with gamma_q > 0, checked when the study is set up.
gamma_sFloat64YesSpillage coefficient (MW per m³/s). Must be <= 0.
kappaFloat64NoIntercept-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_hm3Float64NoMinimum volume where this plane is valid (hm³)
valid_v_max_hm3Float64NoMaximum volume where this plane is valid (hm³)
valid_q_max_m3sFloat64NoMaximum turbined flow where this plane is valid (m³/s)

source: "computed" — hyperplanes are fitted at runtime from the plant’s physical geometry. Novomodelo 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 kFPHAk_{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 geometry rows in system/hydro_geometry.parquet for the plant. An FPHA-configured plant needs at least 1 row — a single row is valid for run-of-river plants (a constant forebay, so gamma_v = 0). A plant configured with linearized_head needs at least 2 rows when the file holds any row; that higher minimum is a load-time validation rule keyed on the configured model name, not a runtime head fit — linearized_head resolves to constant productivity (methodology §3).

"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. 1 selects the single-volume fit of a run-of-river plant; otherwise it must be >= 2. With a single fitting volume the fitter synthesizes two nearby volume samples and snaps gamma_v to exactly 0 (see Implementation notes).
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 kFPHAk_{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 trim the plane set — the hull fitter emits exactly the upper-envelope facets.
fitting_windownullOptional volume range for fitting; absent uses the full forebay range of the plant’s system/hydro_geometry.parquet volume-height curve.

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": 0.05, "volume_max_percentile": 0.95 }

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_pctYesTolerance, in percent, on the normalised mean-squared generation difference: two planes merge when that difference, as a fraction, is below tolerance_pct / 100. 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.

As with the generic-constraint inputs, the JSON file owns model selection and range-level values, and the parquet file owns 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. The conflict error names both files and the offending (hydro_id, stage_id) and asks for the value from exactly one source; the coverage error names the pair and both files.

A hydro whose generation.model is fpha is outside both checks: its equivalent productivity is derived from the VHA geometry and the specific productivity (methodology §5.1). An equivalent_productivity_mw_per_m3s row for such a hydro (stage-specific, else the per-hydro default) is an override that replaces the derived value for both evaluators — the reference-point value (§5.1) and the useful-range mean value (§5.3).

In system/hydro_energy_productivity.parquet, equivalent_productivity_mw_per_m3s and specific_productivity_mw_per_m3s_per_m must each be finite and non-negative when set: 0 is accepted and a negative value is rejected at load. A 0 equivalent productivity at a stage that does not use the "fpha" model marks a planned-outage stage, as 0.0 does in the JSON field below. A 0 specific productivity zeroes the head-derived equivalent productivity and does not stop generation.

{
"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
FPHA geometry coverageDimensional errorWhen system/hydro_geometry.parquet holds any row, every fpha plant must have at least 1 row of its own (a single row is valid for run-of-river plants) and every linearized_head plant at least 2 rows; with no row in the file this rule does not run.
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.