Skip to content

Penalty System

This chapter defines the unified penalty system that ensures LP feasibility across all scenarios while correctly pricing operational costs and constraint violations. Each penalty enters the stage objective as LP Formulation §2 and §9 state it.

The LP must always be feasible. Several physical and operational constraints may be impossible to satisfy in extreme scenarios (droughts, equipment failures, etc.). The penalty system provides slack variables with graduated costs to maintain feasibility while signaling the severity of violations.

Novomodelo’s penalty cascade has three levels of specificity: a stage override on a (entity, stage, penalty) triple is most specific, an entity override defined per entity is next, and a global default at the case level is the fallback. The most specific value present wins. The Configure tab lists the tiers each cost supports, with an example.

Penalties serve three distinct purposes in the LP formulation. Understanding these categories is important for setting appropriate cost magnitudes and interpreting results.

Category 1: Recourse Slacks (LP Feasibility)

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

These penalties ensure that the SDDP algorithm has relatively complete recourse — every subproblem must be feasible regardless of the scenario realization. Without these slacks, the LP would be infeasible when generation cannot meet demand or when excess uncontrollable generation cannot be absorbed, or when a negative inflow realization leaves a hydro’s water balance without a solution.

PenaltyUnitsApplied ToPurpose
Deficit (cb,sdefc^{def}_{b,s})$/MWhUnmet load per bus, by segmentPiecewise cost of load shedding
Excess (cbexcc^{exc}_b)$/MWhExcess generation per busAbsorb uncontrollable surplus
Inflow non-negativity (chinfc^{inf}_h)$/(m³/s·h)Water added to a hydro’s water balance (penalty-based inflow methods)Keep the water balance feasible under a negative inflow realization

Deficit and excess are conceptually slack variables on the load balance constraint, but they have special names because of their importance in the hydrothermal dispatch application. Deficit represents the value of lost load; excess is a regularization-level cost to eliminate spurious slack generation.

Category 2: Constraint Violation Penalties (Policy Shaping)

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

These penalties provide slack for physical or operational constraints that may be impossible to satisfy under extreme conditions (e.g., drought, environmental directives from the system operator). Their cost must be high enough to affect the value function in earlier stages, signaling that the system should avoid states that lead to these violations.

PenaltyUnitsApplied ToPurpose
Storage below minimum (chsv−c^{sv-}_h)$/hm³Storage < min (dead volume) of a filling hydro once it operatesReservoir below dead volume — near-physical limit
Filling-target shortfall (chfillc^{fill}_h)$/hm³Per-stage filling floor missedCommissioning fill schedule — below deficit in the hierarchy
Turbined flow below minimum (chtv−c^{tv-}_h)$/(m³/s·h)Turbined flow < minEquipment limits / ecological flow
Outflow below minimum (chov−c^{ov-}_h)$/(m³/s·h)Outflow < minEnvironmental minimum flow (operator/regulatory)
Outflow above maximum (chov+c^{ov+}_h)$/(m³/s·h)Outflow > maxDownstream flooding prevention
Generation below minimum (chgv−c^{gv-}_h)$/MWhGeneration < minContractual or environmental minimum generation
Evaporation above or below target (chev+c^{ev+}_h, chev−c^{ev-}_h)$/(m³/s·h)Evaporation constraintPhysical constraint (bidirectional, see Section 5)
Withdrawal over- or under-delivery (chwv+c^{wv+}_h, chwv−c^{wv-}_h)$/(m³/s·h)Unmet water withdrawalHuman consumption / irrigation commitments
Generic constraint violation (user-set cost)$/(constraint unit·h)Generic constraint violationsUser-defined physical or operational constraints

These penalties create an artificial cost in the objective function that propagates backward through the value function, telling earlier stages to store more water (or dispatch differently) to avoid reaching states where violations are necessary.

Category 3: Regularization Costs (Solution Guidance)

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

These are small costs inserted into the objective function to guide the solver toward physically preferred solutions when the LP would otherwise be indifferent. They do not represent real costs and should be orders of magnitude smaller than any economic cost to avoid distorting the optimal policy.

PenaltyUnitsApplied ToPurpose
Spillage (chspillc^{spill}_h)$/(m³/s·h)Water spilledPrefer storing over spilling when the solver is indifferent
Turbined flow (chtcc^{tc}_h)$/(m³/s·h)Turbined flow (every hydro)Prefer spilling over turbining water that produces no value (see below)
Diversion (chdivc^{div}_h)$/(m³/s·h)Water divertedPrefer main channel flow; higher than spillage (water leaves cascade)
Curtailment (crcurtc^{curt}_r)$/MWhCurtailed non-controllable genPrioritize using available non-controllable generation over curtailing it
Exchange (cnexchc^{exch}_n)$/MWhPower flow on linesPrefer local supply; avoid unnecessary inter-bus power flows

Turbined cost: the cost chtcc^{tc}_h is charged on the turbined flow of every cell of every hydro, whatever its production model. Where energy has no marginal value, turbined water produces nothing, so the LP is indifferent between turbining it and spilling it; chtc>chspillc^{tc}_h > c^{spill}_h tips it toward spilling. On an FPHA plane with γqm=0\gamma_q^m = 0 (the installed-capacity plane, Hydro Production Function Models §2.6) more turbined flow adds no generation either, but spillage lowers the plane through its coefficient γsm≤0\gamma_s^m \leq 0 (§2.7), so where that plane binds the LP weighs the generation that spilling would lose against chtc−chspillc^{tc}_h - c^{spill}_h. A hydro using FPHA requires chtc≥0c^{tc}_h \geq 0, since a negative cost would reward turbining water that generates nothing; chtc>chspillc^{tc}_h > c^{spill}_h is advised for every hydro and not enforced (Configure tab).

The costs carry different units: $/hm³ for the storage-violation and filling-target slacks; $/(m³/s·h) for the turbined, outflow, evaporation, withdrawal and inflow non-negativity slacks and the spillage, turbined and diversion costs; and $/MWh for deficit, excess, the generation-minimum slack and every non-hydro cost. The ordering compares them as energy-equivalent costs in $/MWh.

Water at hydro hh in stage tt produces energy at its accumulated productivity ρˉacum,h,t\bar\rho_{acum,h,t}, the sum of the useful-range mean productivities of the plant and of every plant downstream (Hydro Production Function Models §5.3). One hm³ is κ=106/3600\kappa = 10^6/3600 (m³/s)·h, so, for a plant with ρˉacum,h,t>0\bar\rho_{acum,h,t} > 0, a storage cost cc is worth c/(κ ρˉacum,h,t)c/(\kappa\,\bar\rho_{acum,h,t}) $/MWh and a flow cost cc is worth c/ρˉacum,h,tc/\bar\rho_{acum,h,t} $/MWh. In these units the tiers are ordered from highest to lowest:

csv−κ ρˉacum  >  cdef>  ctv−ρˉacum,  cov±ρˉacum,  cgv−,  cev±ρˉacum,  cwv±ρˉacum,  cinfρˉacum>  cth,  cctr>  cspillρˉacum,  ctcρˉacum,  cdivρˉacum,  ccurt,  cexch\begin{aligned} \frac{c^{sv-}}{\kappa\,\bar\rho_{acum}} \;&>\; c^{def} \\ &>\; \frac{c^{tv-}}{\bar\rho_{acum}},\; \frac{c^{ov\pm}}{\bar\rho_{acum}},\; c^{gv-},\; \frac{c^{ev\pm}}{\bar\rho_{acum}},\; \frac{c^{wv\pm}}{\bar\rho_{acum}},\; \frac{c^{inf}}{\bar\rho_{acum}} \\ &>\; c^{th},\; c^{ctr} \\ &>\; \frac{c^{spill}}{\bar\rho_{acum}},\; \frac{c^{tc}}{\bar\rho_{acum}},\; \frac{c^{div}}{\bar\rho_{acum}},\; c^{curt},\; c^{exch} \end{aligned}

Each inequality holds for every hydro, bus, deficit segment, thermal, contract, source and line it involves, with ρˉacum\bar\rho_{acum} the accumulated productivity of the hydro whose cost is converted and the negative price of an export contract taken by its magnitude.

The storage violation below cost prices the soft dead-volume floor of a filling hydro from its entry stage on (every other operating hydro’s dead volume is a hard bound). It is the highest penalty in the system: it must exceed deficit cost, because keeping a reservoir above its dead volume is more critical than serving load (operating below dead volume risks dam safety and equipment damage). Deficit, in turn, exceeds the operational-constraint, resource, and regularization tiers.

The filling-target penalty sits on a separate rung, below deficit:

cdef  >  cfillκ ρˉacumc^{def} \;>\; \frac{c^{fill}}{\kappa\,\bar\rho_{acum}}

A commissioning fill schedule must not be defended as hard as load shedding — when nature physically cannot both serve load and keep a filling reservoir on its accumulation schedule, the solver should serve load and let the filling floor slip. The filling-target penalty’s position relative to the operational-constraint tier (minimum turbined flow, outflow, generation, evaporation, withdrawal) is left to study calibration: Novomodelo expects it below deficit and sets nothing against those operational slacks.

Novomodelo checks the configured costs when the case loads. The checks compare the values as given, without the conversion above, so a case that passes them can still invert the energy-equivalent ordering. They warn without stopping the run, and two rules reject a case: a negative turbined cost on an FPHA hydro, and a filling schedule that cannot reach the dead volume. The Configure tab lists them.

Each cost has a case-level default and may be overridden per entity and per stage. The evaporation and withdrawal slacks are priced by directional costs whose defaults follow the symmetric costs; the Configure tab lists the fields, the tiers and the fallback order. Inflow Non-Negativity Solution Methods covers the penalty method of the inflow slack.

Deficit is priced piecewise linearly. Each segment covers a depth of unmet load at its own cost, the costs rise from segment to segment, and the last segment is unbounded, so the LP is always feasible. Segments may be overridden per bus but not per stage.

This section enumerates all constraints in the LP that use slack variables, organized by the system element they belong to. For the full LP constraint formulations, see LP Formulation.

Constraint TypeSlack VariableDirectionPenaltyCategory
Load balanceδb,k,s\delta_{b,k,s}Lower (unmet demand)cb,sdefc^{def}_{b,s}Recourse
Load balanceϵb,k\epsilon_{b,k}Upper (surplus generation)cbexcc^{exc}_bRecourse
Constraint TypeSlack VariableDirectionPenaltyCategory
Minimum storage (filling hydro, from its entry stage)σhv−\sigma^{v-}_hLower boundchsv−c^{sv-}_hConstraint violation
Filling targetσhfill\sigma^{fill}_hLower boundchfillc^{fill}_hConstraint violation
Minimum turbinedσh,b,kq−\sigma^{q-}_{h,b,k}Lower boundchtv−c^{tv-}_hConstraint violation
Minimum outflowσh,ko−\sigma^{o-}_{h,k}Lower boundchov−c^{ov-}_hConstraint violation
Maximum outflowσh,ko+\sigma^{o+}_{h,k}Upper boundchov+c^{ov+}_hConstraint violation
Minimum generationσh,b,kg−\sigma^{g-}_{h,b,k}Lower boundchgv−c^{gv-}_hConstraint violation
Evaporationσhe+\sigma^{e+}_hAbove targetchev+c^{ev+}_hConstraint violation
Evaporationσhe−\sigma^{e-}_hBelow targetchev−c^{ev-}_hConstraint violation
Water withdrawalσhw+\sigma^{w+}_hOver-deliverychwv+c^{wv+}_hConstraint violation
Water withdrawalσhw−\sigma^{w-}_hUnder-deliverychwv−c^{wv-}_hConstraint violation
Water balance (penalty-based inflow methods)σhinf\sigma^{inf}_hAdded waterchinfc^{inf}_hRecourse

The minimum storage V‾h\underline{V}_h (the dead volume) is a hard bound for every hydro except a filling hydro, whose dead-volume floor from its entry stage on is soft through σhv−\sigma^{v-}_h, priced at chsv−c^{sv-}_h (very high cost, above deficit), and a PreFilling hydro, whose storage its frozen identity holds (LP Formulation §8). Operating below dead volume risks dam safety and equipment damage. The slack ensures LP feasibility in extreme scenarios (severe drought, or the transition from filling to operating when the reservoir did not fully reach V‾h\underline{V}_h).

The maximum storage Vˉh\bar{V}_h is a hard physical limit (the reservoir capacity); excess water leaves through spillage. No slack variable is used.

Filling floors: While a plant is filling, from its filling start stage up to, not including, its entry stage, each stage carries a per-stage minimum end-of-stage storage VttargetV^{\text{target}}_t that reaches the dead volume exactly at the last filling stage. Each floor is soft, vh+σhfill≥Vttargetv_h + \sigma^{fill}_h \geq V^{\text{target}}_t, priced at chfillc^{fill}_h. This penalty sits below deficit (a fill schedule is not defended as hard as load serving); the slack absorbs the gap only when no cheaper feasible dispatch reaches the floor. See §6 Dead-Volume Filling Specifics for the floor trajectory.

Exchange bounds are hard variable bounds on the direct and reverse flow variables. No slack variables. The exchange cost cnexchc^{exch}_n is a regularization term on the direct and reverse flows themselves, not a violation penalty.

Thermal bounds G‾j\underline{G}_j and Gˉj\bar{G}_j are hard constraints. No slack variables. Thermal dispatch is directly controllable (unlike hydro, which depends on exogenous inflows), so if bounds cannot be met, this indicates a data error.

User-defined generic constraints can optionally have slack variables with user-specified penalty costs. These are typically used for physical or operational directives from the system operator, and fall into the constraint violation category. A two-sided generic constraint (both a floor and a cap) carries two slack columns, one per endpoint; the reported violation is their signed net, and both are penalised.

Non-controllable generation bounds: a source’s generation is bounded above by its available generation in the block, and below by it as well for a must-run source (System Element Modeling Overview §6). Both bounds are hard, with no slack variables. The curtailment cost crcurtc^{curt}_r is a regularization term (Category 3), analogous to the hydro spillage cost: it makes dispatching available non-controllable generation cheaper than curtailing it (Equipment-Specific Formulations §6 gives the objective term).

The reservoir-surface evaporation flow ehe_h (eh,ke_{h,k} per block on a chronological stage) is a signed net flux: positive values represent net evaporative loss, negative values represent net rainfall input on the lake surface (precipitation on the reservoir exceeds open-water evaporation). On tropical and subtropical basins, the net coefficient is negative through much of the wet season, so the negative case is common rather than exceptional. Other situations that can yield a negative value include condensation in humid climates and linearisation artefacts at certain volume/coefficient combinations.

The evaporation column is bounded symmetrically, eh∈[−qev,hmax⁡, +qev,hmax⁡]e_h \in [-q^{\max}_{ev,h},\, +q^{\max}_{ev,h}], where qev,hmax⁡q^{\max}_{ev,h} is recomputed at each stage as the magnitude of that stage’s linearised target at maximum storage, multiplied by a fixed safety margin. The symmetric bound lets the same column absorb both directions without forcing the over-evaporation slack to fire on every wet-month stage. A negative ehe_h enters the water-balance equation with the same coefficient as a positive value; the sign of ehe_h itself controls whether the term subtracts (loss) or adds (rainfall input) from storage.

The evaporation row sets ehe_h equal to the linearised evaporation target plus σhe+\sigma^{e+}_h minus σhe−\sigma^{e-}_h. The slacks are one-sided, σhe+≥0\sigma^{e+}_h \geq 0 and σhe−≥0\sigma^{e-}_h \geq 0: they absorb evaporation above and below the target and are priced at chev+c^{ev+}_h and chev−c^{ev-}_h. The signs of ehe_h and of the target, not the slacks, carry the direction of the net flux. A parallel stage has one stage-level pair, and a chronological stage one pair per block, σh,ke+\sigma^{e+}_{h,k} and σh,ke−\sigma^{e-}_{h,k}.

The signed withdrawal target rhr_h, in m³/s, is positive for a removal and negative for an inter-basin return. It is a stage-level quantity on the right-hand side of the hydro’s water balance (LP Formulation §4). Two one-sided stage-level slacks relax it, so the realized withdrawal is rh−σhw−+σhw+r_h - \sigma^{w-}_h + \sigma^{w+}_h: the under-delivery σhw−\sigma^{w-}_h is priced at chwv−c^{wv-}_h and the over-delivery σhw+\sigma^{w+}_h at chwv+c^{wv+}_h, each over the stage hours HtH_t. The slack that would flip the sign of the realized withdrawal is capped at ∣rh∣\lvert r_h \rvert (LP Formulation §9).

The per-variable bounds and the slack each bound carries are tabulated in the Configure tab.

During the filling period, from the filling start stage up to, not including, the entry stage, the hydro is in commissioning state: the reservoir accumulates water toward the dead volume V‾h\underline{V}_h before it can begin generating. This is Novomodelo’s own dead-volume filling model, whose per-stage accumulation schedule is specified below.

Filling is governed by a minimum accumulation rate rather than a single end-of-period target. The rate is not an applied inflow and not a cap on natural inflow — it is the floor below which the reservoir is not allowed to lag the fill schedule. From it, Novomodelo derives a per-stage minimum end-of-stage storage VttargetV^{\text{target}}_t that reaches the dead volume exactly at the last filling stage LL, the stage before the entry stage.

LP Formulation §8 gives the floor in closed form: the dead volume at LL minus the accumulation the schedule still owes after stage tt, never above the dead volume in force at stage tt. The schedule must be able to reach the plant’s declared dead volume, not a stage override of it, from the storage the plant starts filling with; Novomodelo rejects a case whose schedule cannot (Configure tab).

Operational constraints during filling:

  • No generation: turbined flow and generation are 00 (hard constraint — turbines not installed/operational, so the plant is excluded from the production-function constraints); a positive turbined or generation floor of the plant then falls wholly on its slack (Lifecycle Phases)
  • Outflow via spillage only: the turbines and the diversion are pinned to zero while the plant fills, so it releases water only by spilling; spillage is a free decision within its bounds, whatever the storage level.
  • Min outflow requirement: a minimum outflow is met through spillage; when the reservoir cannot release enough water, the outflow-below slack absorbs the shortfall at its penalty.
  • Natural inflow flows freely: incremental inflow and upstream cascade releases enter the reservoir through the ordinary water balance — there is no retention or impound cap that diverts inflow to storage. Whatever nature provides is what fills the reservoir.
  • Storage floor is the fill schedule: the dead volume V‾h\underline{V}_h does not apply as a bound during filling — storage may sit anywhere in [0,Vˉh][0, \bar{V}_h]. The active floor is the per-stage soft target below.

Per-stage filling floors: at every filling stage the end-of-stage storage plus the slack σhfill\sigma^{fill}_h must reach VttargetV^{\text{target}}_t, with the slack priced at chfillc^{fill}_h. This penalty sits below deficit in the hierarchy — when nature cannot both serve load and keep the reservoir on schedule, the solver lets the filling floor slip rather than shed load. The slack is priced so that the LP uses it only when no cheaper feasible dispatch exists: when the plant’s storage and inflow cannot reach that stage’s floor, or when meeting the floor in full would cost at least as much, future cost included, as using the slack at its penalty.

Transition to operating: At its entry stage, the hydro becomes operational. The storage at the end of the last filling stage becomes the initial storage for the first operating stage, and its soft dead-volume floor vh+σhv−≥V‾hv_h + \sigma^{v-}_h \geq \underline{V}_h takes over. If the fill fell short (a positive filling slack σhfill\sigma^{fill}_h at LL), that operating-stage floor slack absorbs the shortfall — both LPs remain feasible.

Penalties during filling: The same outflow violation cost applies, and spillage during filling still incurs its spillage cost. The storage-below-minimum slack σhv−\sigma^{v-}_h is not active during filling — the per-stage filling slack σhfill\sigma^{fill}_h is the one that fires, and storage is allowed below the dead volume by design.

The methodology above defines the penalty taxonomy and priority ordering; the tabs below cover how Novomodelo’s software surface configures and reports the penalty costs that implement it.

Novomodelo’s penalties.json file supplies the case-wide penalty defaults that the methodology above prices into the objective. Entity-level and stage-level overrides layer on top of these defaults through the resolution cascade described in §1 Cascade Resolution and §3 Penalty Defaults and Overrides. This tab covers how the defaults are supplied, how overrides resolve and the checks Novomodelo runs on the configured costs; the penalty-priority ordering itself is in Penalty Priority Ordering above.

penalties.json — Global Penalty Defaults

Section titled “penalties.json — Global Penalty Defaults”

Penalty Files owns the field table for penalties.json (every field, its type and the load rules). The example below sets the required costs only; the optional ones (the four directional costs and inflow_nonnegativity_cost) are omitted.

{
"$schema": "https://docs.novomodelo.invalid/schemas/penalties.schema.json",
"bus": {
"deficit_segments": [
{ "depth_mw": 500.0, "cost": 7000.0 },
{ "depth_mw": null, "cost": 7500.0 }
],
"excess_cost": 100.0
},
"line": { "exchange_cost": 2.0 },
"hydro": {
"spillage_cost": 0.01,
"turbined_cost": 0.05,
"diversion_cost": 0.1,
"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
},
"non_controllable_source": { "curtailment_cost": 0.005 }
}

Bus (deficit_segments[].cost, excess_cost), line (exchange_cost) and non-controllable-source (curtailment_cost) costs are in $ per MWh. Hydro costs are in $ per hm³ for storage_violation_below_cost and filling_target_violation_cost, in $ per MWh for generation_violation_below_cost, and in $ per (m³/s)·h for every other hydro cost.

A parallel stage prices its one evaporation slack pair over the stage hours, and a chronological stage each block’s pair over the block hours (Signed Net Evaporation).

The entity-level hydros[].penalties block that overrides these defaults per plant is documented in the System Element Modeling Overview Configure tab; the directional-cost fallback is described under Directional evaporation and withdrawal costs.

Bus deficit_segments — Two-Tier Resolution

Section titled “Bus deficit_segments — Two-Tier Resolution”

Deficit is priced through a piecewise-linear cost curve rather than a single flat value. Each segment is a { depth_mw, cost } pair; segments are cumulative — the first depth_mw MW of unmet demand cost the first segment’s cost, the next depth_mw MW cost the second segment’s cost, and so on:

"deficit_segments": [
{ "depth_mw": 500.0, "cost": 1000.0 },
{ "depth_mw": null, "cost": 5000.0 }
]

The last segment is unbounded, so the LP is always feasible whatever the shortfall. The field rules (a non-empty list, positive and strictly increasing costs, and a last depth_mw of null) are under penalties.json and system/buses.json.

deficit_segments resolves from exactly two sources and does not support stage-level overrides:

  1. Bus-level override — the deficit_segments array on the bus object in system/buses.json.
  2. Global default — the bus.deficit_segments array in penalties.json.

excess_cost resolves through a different pair of tiers: there is no bus-level excess_cost override, but the global default may be refined per stage via constraints/penalty_overrides_bus.parquet (see the Inputs & Outputs tab).

Line and Non-Controllable-Source Overrides

Section titled “Line and Non-Controllable-Source Overrides”

exchange_cost (line) and curtailment_cost (non-controllable source) each fall back to their penalties.json global default when no override applies at a more specific tier:

FieldEntity-level overrideStage-level override
line.exchange_costlines[].exchange_cost in system/lines.jsonconstraints/penalty_overrides_line.parquet
non_controllable_source.curtailment_costnon_controllable_sources[].curtailment_cost in system/non_controllable_sources.jsonconstraints/penalty_overrides_ncs.parquet

The global exchange_cost applies to every line without an override, in both flow directions.

The table below consolidates the tiers each penalty field supports — the tiers differ per field, so this is a lookup table, not a single uniform rule:

Penalty fieldStage-level overrideEntity-level overrideGlobal default
bus.deficit_segmentsnot supportedbuses[].deficit_segmentspenalties.json → bus.deficit_segments
bus.excess_costpenalty_overrides_bus.parquetnot supportedpenalties.json → bus.excess_cost
line.exchange_costpenalty_overrides_line.parquetlines[].exchange_costpenalties.json → line.exchange_cost
hydro.*penalty_overrides_hydro.parquethydros[].penalties — see the System Element Modeling Overview Configure tabpenalties.json → hydro.*
non_controllable_source.curtailment_costpenalty_overrides_ncs.parquetnon_controllable_sources[].curtailment_costpenalties.json → non_controllable_source.curtailment_cost

Take spillage_cost with a global default of 0.01, an entity override of 0.005 on Hydro 0 and a stage override of 0.02 on Hydro 0 at stage 60. The most specific value present wins:

QueryStage Override?Entity Override?Result
Hydro 0, Stage 30, spillageNoYes (0.005)0.005 (entity)
Hydro 0, Stage 60, spillageYes (0.02)Yes (0.005)0.02 (stage)
Hydro 1, Stage 30, spillageNoNo0.01 (global default)

Directional evaporation and withdrawal costs

Section titled “Directional evaporation and withdrawal costs”

The LP prices the evaporation and withdrawal slacks with four directional costs: evaporation_violation_pos_cost above the target, evaporation_violation_neg_cost below it, water_withdrawal_violation_pos_cost for over-delivery and water_withdrawal_violation_neg_cost for under-delivery. The symmetric costs evaporation_violation_cost and water_withdrawal_violation_cost supply their defaults only; no slack is priced at a symmetric cost.

  • In penalties.json, an absent directional cost takes that file’s symmetric cost.
  • A plant with no penalties block takes every penalties.json value, directional costs included.
  • A plant’s penalties block has no directional keys. With the block present, each directional cost takes the block’s matching symmetric cost when the block sets it, else the penalties.json directional cost. A penalties.json directional cost therefore reaches every plant whose block does not set the matching symmetric key.
  • A penalty_overrides_hydro.parquet row’s directional column sets that direction at its stage; its symmetric column sets the stage’s symmetric value and each matching direction the row leaves unset.
VariableLower BoundUpper BoundLower SlackUpper Slack
storagemin_storage_hm3; 0 for a filling hydro in every phase and for a hydro outside its commissioning windowmax_storage_hm3Hard (soft for a filling hydro once operating)Spillage
turbined_flow0; the min_turbined_m3s minimum, summed over a (hydro, bus) cell’s unit groups, is a soft rowmax_turbined_m3sWith penalty, when that minimum is > 0Hard
spillagemin_spillage_m3s (default 0)max_spillage_m3s (default unbounded)HardHard
outflowmin_outflow_m3smax_outflow_m3s (optional)With penalty, when that minimum is > 0With penalty, when that maximum is set
generation0; the min_generation_mw minimum, summed over a (hydro, bus) cell’s unit groups, is a soft rowmax_generation_mwWith penalty, when that minimum is > 0Hard
evaporationminus the per-stage bound of Signed Net Evaporationplus that boundWith penaltyWith penalty
withdrawal (stage-level)water_withdrawal_m3swater_withdrawal_m3sWith penaltyWith penalty

The spillage bounds come from constraints/hydro_bounds.parquet; a PreFilling hydro’s spillage is fixed at 0.

Relationship: outflow = turbined_flow + spillage, generation = f(turbined_flow, storage) (depends on production model)

Novomodelo checks the configured costs when the case loads. The checks read each hydro’s entity-level costs: the penalties.json defaults with the plant’s penalties block applied. Stage overrides are not checked. The deficit side of the first comparison is the largest deficit-segment cost of any bus, and 0 when the case has no bus. The comparisons use the configured values as given, without converting them to energy, so a case that passes them can still invert the energy-equivalent ordering of the methodology above.

Each check issues at most one warning, a ModelQuality warning on penalties.json that does not stop the run. The warning names a hydro count and a worst-case hydro.

CheckWarns whenHydros reported
Deficit above generation violationa hydro’s generation_violation_below_cost is at least the largest deficit costevery hydro that meets the condition; the worst case has the largest generation-violation cost
Flow violations above resource costsa hydro’s smallest flow-violation cost is at most the largest resource cost over all hydrosevery hydro that meets the condition; the worst case has the smallest flow-violation cost
Resource costs positivea hydro’s smaller resource cost is at most 0every hydro that meets the condition; the worst case has the smallest of those costs

The flow-violation costs of a hydro are turbined_violation_below_cost, outflow_violation_below_cost, outflow_violation_above_cost and the four directional evaporation and withdrawal costs. “Resource costs” means the hydro spillage_cost and diversion_cost. Each check compares only costs of one unit. These three checks read no thermal, contract, excess, exchange or curtailment cost, and neither turbined_cost nor inflow_nonnegativity_cost.

Each warning text is fixed apart from its numbers and hydro ids, written {…} here, and begins Penalty ordering violation::

  • Deficit above generation violation: Penalty ordering violation: max(deficit_segment_costs) ({…}) should be > generation_violation_below_cost ({…}) (both $/MWh) -- {…} hydro(s) affected, worst case: Hydro {…}
  • Flow violations above resource costs: Penalty ordering violation: min(flow_violation_costs) ({…}) should be > max(resource_costs) ({…}) (both $/(m³/s·h)) -- {…} hydro(s) affected, worst case: Hydro {…}
  • Resource costs positive: Penalty ordering violation: min(resource_costs) ({…}) should be > 0 (regularization costs must be positive to prevent LP degeneracy) -- {…} hydro(s) affected, worst case: Hydro {…}

To clear a warning, change the costs in penalties.json or in the plant’s penalties block until the comparison in the message holds: for should be >, raise the cost on the left or lower the one on the right. A warning names penalties.json even when a plant’s penalties block supplies the cost, and the Hydro {…} in it is the plant’s numeric id.

Two rules reject the case as a BusinessRuleViolation error (see Error Codes — BusinessRuleViolation):

  • Negative turbined cost on an FPHA hydro. A hydro whose generation.model is fpha and whose turbined_cost is negative is rejected on penalties.json; zero is accepted. The message reads Hydro {…}: turbined_cost ({…}) must be non-negative (>= 0) for FPHA hydros; negative values distort LP dispatch. To fix it, set turbined_cost to 0 or more in penalties.json or in the plant’s penalties block.
  • Filling schedule that cannot reach the dead volume (the filling-sufficiency check). For each hydro with a filling block and an entry_stage_id, the sum over the stages from start_stage_id up to, not including, entry_stage_id of the stage’s total hours times 3600 / 1,000,000 times the stage’s minimum accumulation rate must reach min_storage_hm3 less the seed storage. The rate is the filling_min_rate_m3s of the stage’s hydro_bounds row when it has one, else the plant’s filling_min_rate_m3s; the seed is the hydro’s filling_storage entry in initial_conditions.json, or 0 when it has none. The case is rejected on system/hydros.json when the sum falls short by more than a relative tolerance of 1e-9, applied to the larger of |min_storage_hm3 − seed| and 1 hm³. The message reads Hydro {…}: filling schedule is insufficient to reach the dead volume before stage {…}; cumulative minimum-rate capacity over stages [{…}, {…}) is {…} hm3 but {…} hm3 (min_storage {…} - seed {…}) is required. To fix it, raise the plant’s filling_min_rate_m3s (or a stage’s hydro_bounds rate), start filling at an earlier stage, move entry_stage_id later, or declare a larger filling_storage seed in initial_conditions.json.

Setting turbined_cost above spillage_cost is advised for every hydro and is not checked.