Penalty System
Purpose
Section titled “Purpose”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.
1. Cascade Resolution
Section titled “1. Cascade Resolution”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.
2. Penalty Categories
Section titled “2. Penalty Categories”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.
| Penalty | Units | Applied To | Purpose |
|---|---|---|---|
| Deficit () | $/MWh | Unmet load per bus, by segment | Piecewise cost of load shedding |
| Excess () | $/MWh | Excess generation per bus | Absorb uncontrollable surplus |
| Inflow non-negativity () | $/(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.
| Penalty | Units | Applied To | Purpose |
|---|---|---|---|
| Storage below minimum () | $/hm³ | Storage < min (dead volume) of a filling hydro once it operates | Reservoir below dead volume — near-physical limit |
| Filling-target shortfall () | $/hm³ | Per-stage filling floor missed | Commissioning fill schedule — below deficit in the hierarchy |
| Turbined flow below minimum () | $/(m³/s·h) | Turbined flow < min | Equipment limits / ecological flow |
| Outflow below minimum () | $/(m³/s·h) | Outflow < min | Environmental minimum flow (operator/regulatory) |
| Outflow above maximum () | $/(m³/s·h) | Outflow > max | Downstream flooding prevention |
| Generation below minimum () | $/MWh | Generation < min | Contractual or environmental minimum generation |
| Evaporation above or below target (, ) | $/(m³/s·h) | Evaporation constraint | Physical constraint (bidirectional, see Section 5) |
| Withdrawal over- or under-delivery (, ) | $/(m³/s·h) | Unmet water withdrawal | Human consumption / irrigation commitments |
| Generic constraint violation (user-set cost) | $/(constraint unit·h) | Generic constraint violations | User-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.
| Penalty | Units | Applied To | Purpose |
|---|---|---|---|
| Spillage () | $/(m³/s·h) | Water spilled | Prefer storing over spilling when the solver is indifferent |
| Turbined flow () | $/(m³/s·h) | Turbined flow (every hydro) | Prefer spilling over turbining water that produces no value (see below) |
| Diversion () | $/(m³/s·h) | Water diverted | Prefer main channel flow; higher than spillage (water leaves cascade) |
| Curtailment () | $/MWh | Curtailed non-controllable gen | Prioritize using available non-controllable generation over curtailing it |
| Exchange () | $/MWh | Power flow on lines | Prefer local supply; avoid unnecessary inter-bus power flows |
Turbined cost: the cost 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; tips it toward spilling. On an FPHA plane with (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 (§2.7), so where that plane binds the LP weighs the generation that spilling would lose against . A hydro using FPHA requires , since a negative cost would reward turbining water that generates nothing; is advised for every hydro and not enforced (Configure tab).
Penalty Priority Ordering
Section titled “Penalty Priority Ordering”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 in stage produces energy at its accumulated productivity , 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 (m³/s)·h, so, for a plant with , a storage cost is worth $/MWh and a flow cost is worth $/MWh. In these units the tiers are ordered from highest to lowest:
Each inequality holds for every hydro, bus, deficit segment, thermal, contract, source and line it involves, with 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:
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.
Penalty Ordering Validation
Section titled “Penalty Ordering Validation”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.
3. Penalty Defaults and Overrides
Section titled “3. Penalty Defaults and Overrides”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.
Piecewise Deficit
Section titled “Piecewise Deficit”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.
4. Constraint Violation Coverage
Section titled “4. Constraint Violation Coverage”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.
System-Level (Bus)
Section titled “System-Level (Bus)”| Constraint Type | Slack Variable | Direction | Penalty | Category |
|---|---|---|---|---|
| Load balance | Lower (unmet demand) | Recourse | ||
| Load balance | Upper (surplus generation) | Recourse |
Hydro — Flow and Generation Constraints
Section titled “Hydro — Flow and Generation Constraints”| Constraint Type | Slack Variable | Direction | Penalty | Category |
|---|---|---|---|---|
| Minimum storage (filling hydro, from its entry stage) | Lower bound | Constraint violation | ||
| Filling target | Lower bound | Constraint violation | ||
| Minimum turbined | Lower bound | Constraint violation | ||
| Minimum outflow | Lower bound | Constraint violation | ||
| Maximum outflow | Upper bound | Constraint violation | ||
| Minimum generation | Lower bound | Constraint violation | ||
| Evaporation | Above target | Constraint violation | ||
| Evaporation | Below target | Constraint violation | ||
| Water withdrawal | Over-delivery | Constraint violation | ||
| Water withdrawal | Under-delivery | Constraint violation | ||
| Water balance (penalty-based inflow methods) | Added water | Recourse |
Hydro — Storage Bounds
Section titled “Hydro — Storage Bounds”The minimum storage (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 , priced at (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 ).
The maximum storage 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 that reaches the dead volume exactly at the last filling stage. Each floor is soft, , priced at . 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 is a regularization term on the direct and reverse flows themselves, not a violation penalty.
Thermals
Section titled “Thermals”Thermal bounds and 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.
Generic Constraints
Section titled “Generic Constraints”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 Sources
Section titled “Non-Controllable Sources”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 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).
5. Bidirectional Slacks
Section titled “5. Bidirectional Slacks”Signed Net Evaporation
Section titled “Signed Net Evaporation”The reservoir-surface evaporation flow ( 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, , where 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 enters the water-balance equation with the same coefficient as a positive value; the sign of itself controls whether the term subtracts (loss) or adds (rainfall input) from storage.
The evaporation row sets equal to the linearised evaporation target plus minus . The slacks are one-sided, and : they absorb evaporation above and below the target and are priced at and . The signs of 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, and .
Withdrawal Slacks
Section titled “Withdrawal Slacks”The signed withdrawal target , 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 : the under-delivery is priced at and the over-delivery at , each over the stage hours . The slack that would flip the sign of the realized withdrawal is capped at (LP Formulation §9).
6. Hydro Variable Bounds Summary
Section titled “6. Hydro Variable Bounds Summary”The per-variable bounds and the slack each bound carries are tabulated in the Configure tab.
Dead-Volume Filling Specifics
Section titled “Dead-Volume Filling Specifics”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 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 that reaches the dead volume exactly at the last filling stage , the stage before the entry stage.
LP Formulation §8 gives the floor in closed form: the dead volume at minus the accumulation the schedule still owes after stage , never above the dead volume in force at stage . 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 (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 does not apply as a bound during filling — storage may sit anywhere in . 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 must reach , with the slack priced at . 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 takes over. If the fill fell short (a positive filling slack at ), 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 is not active during filling — the per-stage filling slack is the one that fires, and storage is allowed below the dead volume by design.
Implementation in Novomodelo
Section titled “Implementation in Novomodelo”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:
- Bus-level override — the
deficit_segmentsarray on the bus object insystem/buses.json. - Global default — the
bus.deficit_segmentsarray inpenalties.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:
| Field | Entity-level override | Stage-level override |
|---|---|---|
line.exchange_cost | lines[].exchange_cost in system/lines.json | constraints/penalty_overrides_line.parquet |
non_controllable_source.curtailment_cost | non_controllable_sources[].curtailment_cost in system/non_controllable_sources.json | constraints/penalty_overrides_ncs.parquet |
The global exchange_cost applies to every line without an override, in both
flow directions.
Resolution Cascade at a Glance
Section titled “Resolution Cascade at a Glance”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 field | Stage-level override | Entity-level override | Global default |
|---|---|---|---|
bus.deficit_segments | not supported | buses[].deficit_segments | penalties.json → bus.deficit_segments |
bus.excess_cost | penalty_overrides_bus.parquet | not supported | penalties.json → bus.excess_cost |
line.exchange_cost | penalty_overrides_line.parquet | lines[].exchange_cost | penalties.json → line.exchange_cost |
hydro.* | penalty_overrides_hydro.parquet | hydros[].penalties — see the System Element Modeling Overview Configure tab | penalties.json → hydro.* |
non_controllable_source.curtailment_cost | penalty_overrides_ncs.parquet | non_controllable_sources[].curtailment_cost | penalties.json → non_controllable_source.curtailment_cost |
Resolution example
Section titled “Resolution example”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:
| Query | Stage Override? | Entity Override? | Result |
|---|---|---|---|
| Hydro 0, Stage 30, spillage | No | Yes (0.005) | 0.005 (entity) |
| Hydro 0, Stage 60, spillage | Yes (0.02) | Yes (0.005) | 0.02 (stage) |
| Hydro 1, Stage 30, spillage | No | No | 0.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
penaltiesblock takes everypenalties.jsonvalue, directional costs included. - A plant’s
penaltiesblock has no directional keys. With the block present, each directional cost takes the block’s matching symmetric cost when the block sets it, else thepenalties.jsondirectional cost. Apenalties.jsondirectional cost therefore reaches every plant whose block does not set the matching symmetric key. - A
penalty_overrides_hydro.parquetrow’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.
Hydro variable bounds
Section titled “Hydro variable bounds”| Variable | Lower Bound | Upper Bound | Lower Slack | Upper Slack |
|---|---|---|---|---|
storage | min_storage_hm3; 0 for a filling hydro in every phase and for a hydro outside its commissioning window | max_storage_hm3 | Hard (soft for a filling hydro once operating) | Spillage |
turbined_flow | 0; the min_turbined_m3s minimum, summed over a (hydro, bus) cell’s unit groups, is a soft row | max_turbined_m3s | With penalty, when that minimum is > 0 | Hard |
spillage | min_spillage_m3s (default 0) | max_spillage_m3s (default unbounded) | Hard | Hard |
outflow | min_outflow_m3s | max_outflow_m3s (optional) | With penalty, when that minimum is > 0 | With penalty, when that maximum is set |
generation | 0; the min_generation_mw minimum, summed over a (hydro, bus) cell’s unit groups, is a soft row | max_generation_mw | With penalty, when that minimum is > 0 | Hard |
evaporation | minus the per-stage bound of Signed Net Evaporation | plus that bound | With penalty | With penalty |
withdrawal (stage-level) | water_withdrawal_m3s | water_withdrawal_m3s | With penalty | With 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)
Load-time penalty checks
Section titled “Load-time penalty checks”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.
| Check | Warns when | Hydros reported |
|---|---|---|
| Deficit above generation violation | a hydro’s generation_violation_below_cost is at least the largest deficit cost | every hydro that meets the condition; the worst case has the largest generation-violation cost |
| Flow violations above resource costs | a hydro’s smallest flow-violation cost is at most the largest resource cost over all hydros | every hydro that meets the condition; the worst case has the smallest flow-violation cost |
| Resource costs positive | a hydro’s smaller resource cost is at most 0 | every 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.modelisfphaand whoseturbined_costis negative is rejected onpenalties.json; zero is accepted. The message readsHydro {…}: turbined_cost ({…}) must be non-negative (>= 0) for FPHA hydros; negative values distort LP dispatch. To fix it, setturbined_costto 0 or more inpenalties.jsonor in the plant’spenaltiesblock. - Filling schedule that cannot reach the dead volume (the filling-sufficiency
check). For each hydro with a
fillingblock and anentry_stage_id, the sum over the stages fromstart_stage_idup to, not including,entry_stage_idof the stage’s total hours times 3600 / 1,000,000 times the stage’s minimum accumulation rate must reachmin_storage_hm3less the seed storage. The rate is thefilling_min_rate_m3sof the stage’shydro_boundsrow when it has one, else the plant’sfilling_min_rate_m3s; the seed is the hydro’sfilling_storageentry ininitial_conditions.json, or 0 when it has none. The case is rejected onsystem/hydros.jsonwhen the sum falls short by more than a relative tolerance of1e-9, applied to the larger of |min_storage_hm3− seed| and 1 hm³. The message readsHydro {…}: 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’sfilling_min_rate_m3s(or a stage’shydro_boundsrate), start filling at an earlier stage, moveentry_stage_idlater, or declare a largerfilling_storageseed ininitial_conditions.json.
Setting turbined_cost above spillage_cost is advised for every hydro and is
not checked.
This is a topic-scoped index of the files the penalty system (Penalty System §1–§3) touches — it names each file and its role, it does not repeat their field-by-field schemas. The exhaustive, field-by-field case-directory and output reference is owned by the Reference corpus (Case Format and Output Format pages).
Inputs
Section titled “Inputs”| File | Role |
|---|---|
penalties.json | Case-wide penalty defaults — required in every case; the four sections (bus, line, hydro, non_controllable_source) are listed under penalties.json in Case Format; the Configure tab covers the example, the resolution cascade and the load-time checks. |
constraints/penalty_overrides_bus.parquet | Sparse per-(bus_id, stage_id) override of excess_cost. Deficit segments are not stage-varying, so they have no row here. |
constraints/penalty_overrides_line.parquet | Sparse per-(line_id, stage_id) override of exchange_cost. |
constraints/penalty_overrides_hydro.parquet | Sparse per-(hydro_id, stage_id) override of the hydro penalty fields — a superset of the entity-level penalties block: the same twelve symmetric costs (including inflow_nonnegativity_cost) plus the four directional variants (water-withdrawal above/below, evaporation above/below) that the entity penalties block does not accept. Every column is optional and sparse. |
constraints/penalty_overrides_ncs.parquet | Sparse per-(ncs_id, stage_id) override of curtailment_cost. |
system/buses.json (deficit_segments) | Bus entity registry; only its deficit_segments field is penalty configuration (covered in the Configure tab) — the rest of the bus schema belongs to the network topology chapter. |
system/lines.json (exchange_cost) | Transmission line registry; only its exchange_cost field is penalty configuration (covered in the Configure tab) — the rest of the line schema belongs to the network topology chapter. |
system/hydros.json (penalties block) | Hydro plant registry; the entity-level penalties block is documented in the System Element Modeling Overview Configure tab, not repeated here. |
All penalty-override parquet files use sparse storage: only rows for
(entity_id, stage_id) pairs where a cost differs from the entity-level or
global default are required, and every value present must be strictly
positive and finite.
Outputs
Section titled “Outputs”| File / column group | Role |
|---|---|
simulation/costs/ | Per-stage monetary cost breakdown, including one column per penalty category — deficit_cost, excess_cost, storage_violation_cost, filling_target_cost, hydro_violation_cost, the outflow/turbined/generation/evaporation/withdrawal violation costs, inflow_penalty_cost, and generic_violation_cost — alongside the thermal, anticipated-thermal and contract cost columns. storage_violation_cost and filling_target_cost are always 0: the storage-floor and filling-target slack costs are included in immediate_cost. |
simulation/violations/generic/ | One row per active generic-constraint row at each stage, whether or not its slack fired (0 where it did not). The reported slack_value is the signed net s⁺−s⁻ of the two slack columns; its monetary cost charges both (s⁺+s⁻). |
| Per-hydro violation columns | Slack magnitudes (e.g. storage_violation_below_hm3, filling_target_violation_hm3, the evaporation and water-withdrawal directional slacks) reported alongside every other hydro dispatch column — see the Hydro Production Function Models Inputs & Outputs tab for the hydro output file this belongs to. |
For the complete output schema (columns, types, file layout), see the Output Format reference page in the Reference corpus.
Cross-References
Section titled “Cross-References”- LP Formulation — Cost taxonomy (§1) and penalty terms (§9)
- Inflow Non-Negativity Solution Methods — Penalty method for inflow non-negativity