Equipment-Specific Formulations
Purpose
Section titled “Purpose”This spec details the LP constraints for each equipment type in Cobre. While system elements describes what each element is and its decision variables, this spec contains the detailed mathematical constraints governing each equipment type’s behavior within the LP. The reading order is: system elements → LP formulation → this spec (per-equipment deep dives).
For variable definitions and index sets, see notation conventions. For hydro production constraints specifically, see hydro production models.
1. Thermal Plants
Section titled “1. Thermal Plants”1.1 Standard Thermals
Section titled “1.1 Standard Thermals”Thermal generation cost is modeled as a piecewise-linear convex function of dispatched power. Each cost segment represents a generation tranche with its own marginal cost — for example, a 200 MW plant might have its first 50 MW at $100/MWh, the next 50 MW at $150/MWh, and the final 100 MW at $200/MWh. Because the segment costs are non-decreasing (convex), the LP solver naturally fills cheaper segments first.
Decision Variables:
- = generation at thermal , block , cost segment
Constraints:
Total generation:
Segment bounds:
Total generation bounds:
Both bounds are hard constraints with no slack variables — thermal dispatch is directly controllable (unlike hydro, which depends on exogenous inflows).
Objective Contribution:
2. Transmission Lines
Section titled “2. Transmission Lines”Decision Variables:
- = direct flow (source → target)
- = reverse flow (target → source)
Bounds:
Load Balance Contribution:
At source bus:
At target bus:
where accounts for transmission losses.
Objective Contribution:
3. Import/Export Contracts
Section titled “3. Import/Export Contracts”Each contract is unidirectional — either an import or an export contract, identified by a type field.
Decision Variables:
- = dispatched power for contract , block
Bounds:
Load Balance Contribution:
At connected bus:
- Import contracts (): (power entering the system)
- Export contracts (): (power leaving the system)
Objective Contribution:
Because import prices () are positive and export prices are negative, this single summation naturally adds import costs and subtracts export revenue. The price sign is independent of the load-balance sign: an import column injects and carries a positive (cost) price; an export column withdraws and carries a negative (revenue) price.
Take-or-pay floor and lifecycle. A non-zero lower bound is a hard take-or-pay obligation: the LP must dispatch at least at the contract price, even when cheaper supply exists. Bounds and the price may both vary by stage. Contracts honor the generic commissioning window (system elements §1) — outside [entry_stage_id, exit_stage_id) the column is pinned to zero. A contract is stateless: it carries no state variable and contributes nothing to the Benders cuts.
4. Pumping Stations
Section titled “4. Pumping Stations”Pumping stations transfer water from a source reservoir to a destination reservoir, consuming electrical power in the process. The source-to-destination direction is a modeling choice (typically uphill / against the cascade); the formulation does not require any particular elevation relationship.
Decision Variables:
- = pumped water flow at station , block (m³/s)
Bounds:
Both bounds are hard constraints. Pumping stations honor the generic commissioning window (system elements §1): outside [entry_stage_id, exit_stage_id) the flow column is pinned to zero, so the station moves no water and draws no power.
Power Consumption:
where is the power consumption rate (MW per m³/s).
Water Balance Impact:
- Source hydro: (water removed)
- Destination hydro: (water added)
Load Balance Impact:
At connected bus: (power consumed)
Objective Contribution: None
5. Hydro Plants
Section titled “5. Hydro Plants”Hydro constraints are the most complex in the system. Rather than duplicating them here, the hydro formulation is split across two specs:
- Water balance, outflow, storage bounds, and soft constraints: See LP formulation §4 (water balance), §6 (generation constraints), §7 (outflow constraints), §8 (variable bounds), §9 (constraint violation penalties).
- Production function models (constant productivity, FPHA, linearized head): See hydro production models.
For hydro decision variables and physical meaning, see system elements §5.
6. Non-Controllable Generation Sources
Section titled “6. Non-Controllable Generation Sources”Non-controllable sources (wind farms, solar plants, small run-of-river hydros) have stochastic availability determined by the scenario pipeline. The solver can only curtail generation below the available amount — it cannot dispatch upward beyond what nature provides.
Decision Variables:
- = generation at non-controllable source , block
Bounds (hard):
where is the stochastic available generation for the current (stage, scenario), bounded by (installed capacity).
Load Balance Contribution:
At connected bus: (generation injected)
Objective Contribution:
The curtailment cost is a regularization penalty (Category 3 in the Penalty System), analogous to spillage_cost for hydros — curtailment discards available “free” energy.
Implementation in Cobre
Section titled “Implementation in Cobre”The methodology above defines every equipment type’s LP constraints; the tabs below cover how Cobre’s software surface configures, feeds, and reports on the four element types with dedicated configuration files — thermals, contracts, pumping stations, and non-controllable sources.
Cobre’s system/thermals.json, system/energy_contracts.json,
system/pumping_stations.json, and system/non_controllable_sources.json
files author the four equipment types that carry dedicated element
configuration in this chapter. This tab is the field-level configuration
reference; the equations these fields feed are in the sections above.
Shared Operational Start Date
Section titled “Shared Operational Start Date”Thermals, non-controllable sources, pumping stations, and energy contracts all
carry a required operational_start_date field:
| Field | Type | Description |
|---|---|---|
operational_start_date | string (ISO-8601 date) | Calendar date (YYYY-MM-DD) the entity enters the registry’s operational history. Provenance and the canonical (operational_start_date, id) ordering key (see Notation Conventions) — independent of the commissioning window below: it does not gate commissioning, and the window does not derive from it. |
Shared Commissioning Window
Section titled “Shared Commissioning Window”Thermals, lines, contracts, pumping stations, and non-controllable sources all carry the same optional pair of fields:
| Field | Type | Description |
|---|---|---|
entry_stage_id | integer or null | Stage index at which the entity enters service (inclusive). null means the entity is available from stage 0. |
exit_stage_id | integer or null | Stage index at which the entity is decommissioned. The commissioning window is half-open [entry_stage_id, exit_stage_id): the entity is active through exit_stage_id - 1, and from exit_stage_id onward its columns are pinned to [0, 0]. null means the entity is never decommissioned. |
Outside its window, an entity’s decision columns remain present in the LP but
are pinned to zero: it injects no power, withdraws no power, moves no water,
and draws no bus power. entry_stage_id must fall within the stage range
declared in stages.json. See system elements §1
for the shared methodology statement — methodology §3–4 above cover how the
window interacts with contract take-or-pay bounds and pumping flow.
system/thermals.json — Thermal Plant Registry
Section titled “system/thermals.json — Thermal Plant Registry”Thermal units are defined in system/thermals.json. The top-level object has
a single key "thermals" containing an array of unit objects:
{ "thermals": [ { "id": 0, "name": "UTE1", "bus_id": 0, "operational_start_date": "1998-03-01", "cost_per_mwh": 5.0, "generation": { "min_mw": 0.0, "max_mw": 15.0 } }, { "id": 1, "name": "Angra 1", "bus_id": 0, "operational_start_date": "1985-01-01", "entry_stage_id": null, "exit_stage_id": null, "cost_per_mwh": 50.0, "generation": { "min_mw": 0.0, "max_mw": 657.0 }, "anticipated_config": { "lead_stages": 2 } } ]}Only id, name, bus_id, operational_start_date, cost_per_mwh, and
generation are required; entry_stage_id, exit_stage_id, and
anticipated_config are optional.
Core Fields
Section titled “Core Fields”| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | Unique non-negative identifier. |
name | string | Yes | Human-readable plant name. Used in output files, validation messages, and log output. |
bus_id | integer | Yes | Identifier of the electrical bus to which this unit’s generation is injected. Must match an id in buses.json. |
operational_start_date | string (ISO-8601 date) | Yes | Calendar date (YYYY-MM-DD) the unit enters the registry’s operational history — see Shared Operational Start Date above. |
cost_per_mwh | number | Yes | Marginal cost of generation, in dollars per MWh. Must be non-negative. |
Generation Bounds
Section titled “Generation Bounds”The generation block sets the output limits, stored internally as
min_generation_mw / max_generation_mw on the entity, and enforced as the
hard generation bounds from methodology §1.1 in every stage LP.
"generation": { "min_mw": 0.0, "max_mw": 657.0 }| Field | Type | Description |
|---|---|---|
min_mw | number | Minimum electrical generation, in MW. A non-zero value is a must-run commitment: the solver must dispatch at least this much whenever the unit is in service (subject to the continuous-relaxation caveat in methodology §1.1). |
max_mw | number | Maximum electrical generation (installed capacity), in MW. |
Anticipated Dispatch Configuration (anticipated_config)
Section titled “Anticipated Dispatch Configuration (anticipated_config)”The optional anticipated_config block flags a thermal as anticipated,
attaching the per-plant lead described in system elements §4. The lead is given in exactly one of two mutually exclusive forms — a stage count or a physical duration:
"anticipated_config": { "lead_stages": 2 }"anticipated_config": { "lead_time_hours": 720.0 }| Field | Type | Description |
|---|---|---|
lead_stages | integer | Number of stages of dispatch anticipation, calendar-free. A value of 2 means the commitment for stage t must be decided at stage t - 2. Must be >= 1. |
lead_time_hours | number | Physical anticipation lead in hours, resolved against the stage calendar to an integer stage lead by end-anchoring on the cumulative stage-hour boundaries. A lead shorter than the decision stage’s own duration resolves to zero stages, and the plant then dispatches as an ordinary thermal. |
Supplying both keys, or neither, is a load error. A lead that exceeds the study horizon — a commitment that could never deliver within it — is rejected at load, as is a physical lead coarse enough that a single decision stage would anchor more than one delivery stage (per-delivery-stage output for that case is not yet supported).
An anticipated plant also requires a matching entry in
initial_conditions.json’s past_anticipated_commitments, seeding the
pre-horizon ring-buffer slots described in system elements §4 (“Pre-horizon
seed”):
{ "past_anticipated_commitments": [ { "thermal_id": 2, "values_mw": [0.0, 0.0] } ]}values_mw must have exactly as many entries as the resolved integer lead —
lead_stages, or the stage lead that lead_time_hours resolves to — ordered
oldest-to-most-recent.
Constraining Commitments via Generic Constraints
Section titled “Constraining Commitments via Generic Constraints”The anticipated-commitment decision variable can be referenced in a generic
constraint using the anticipated_decision(N) expression, where N is the
thermal’s id — for example, to cap the MW level committed at each decision
stage:
{ "constraints": [ { "id": 1, "name": "cap_ant_t1", "expression": "anticipated_decision(2)", "sense": "<=", "slack": { "enabled": false } } ]}anticipated_decision(N) must reference a thermal carrying an
anticipated_config block — referencing a non-anticipated thermal is a hard
load-time error. thermal_generation(N) on an anticipated thermal is accepted
but flagged with a semantic-ambiguity warning, since that expression reads the
per-block delivered generation, not the forward commitment.
Load-Time Validation Rules
Section titled “Load-Time Validation Rules”| Rule | Error Class | Description |
|---|---|---|
| Bus reference integrity | Reference error | Every bus_id must match an id in buses.json. |
| Non-negative cost | Schema error | cost_per_mwh must be non-negative. |
| Generation bounds ordering | Physical feasibility | min_mw must be <= max_mw. |
| Anticipated lead validity | Physical feasibility | When anticipated_config is present, lead_stages must be >= 1 and must not exceed the study’s stage count. |
| Commitment history bijection | Reference error | Every anticipated thermal must have exactly one past_anticipated_commitments entry, with a values_mw array of exactly lead_stages entries. |
system/non_controllable_sources.json — NCS Registry
Section titled “system/non_controllable_sources.json — NCS Registry”Non-controllable sources (wind, solar, small run-of-river hydro) are defined
in system/non_controllable_sources.json:
{ "non_controllable_sources": [ { "id": 0, "name": "Wind Farm A", "bus_id": 1, "operational_start_date": "2015-06-01", "max_generation_mw": 100.0 } ]}| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | Unique identifier. |
name | string | Yes | Human-readable source name. |
bus_id | integer | Yes | Bus to which this source’s generation is injected. |
operational_start_date | string (ISO-8601 date) | Yes | Calendar date (YYYY-MM-DD) the source enters the registry’s operational history — see Shared Operational Start Date above. |
max_generation_mw | number | Yes | Installed capacity, in MW — the hard physical upper bound behind the stochastic availability described in methodology §6. |
allow_curtailment | boolean | No | Defaults to true (curtailable). Set false for must-run sources that pin generation to the realized availability every scenario — see system elements §6 for when this is required (e.g. already-netted aggregate NCS totals). |
curtailment_cost | number or null | No | Entity-level override of the global ncs_curtailment_cost regularization price. |
The stochastic available generation itself comes from the scenario pipeline
(constraints/ncs_bounds.parquet and the scenario factors/stats files), not
from this entity file — see the Inputs & Outputs tab.
system/pumping_stations.json — Pumping Station Registry
Section titled “system/pumping_stations.json — Pumping Station Registry”{ "pumping_stations": [ { "id": 0, "name": "Bombeamento Serra da Mesa", "bus_id": 10, "operational_start_date": "2010-09-15", "source_hydro_id": 3, "destination_hydro_id": 5, "consumption_mw_per_m3s": 0.5, "flow": { "min_m3s": 0.0, "max_m3s": 150.0 } } ]}| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | Unique identifier. |
name | string | Yes | Human-readable station name. |
bus_id | integer | Yes | Bus from which electrical power is consumed. |
operational_start_date | string (ISO-8601 date) | Yes | Calendar date (YYYY-MM-DD) the station enters the registry’s operational history — see Shared Operational Start Date above. |
source_hydro_id | integer | Yes | Hydro plant from whose reservoir water is extracted (see methodology §4). Must differ from destination_hydro_id — a station modeling a self-transfer (same id on both sides) is a load-time error, since it would silently cancel on one water-balance row while still drawing power. |
destination_hydro_id | integer | Yes | Hydro plant into whose reservoir water is injected (see methodology §4). |
consumption_mw_per_m3s | number | Yes | Power consumption rate, in MW per m³/s (methodology §4). |
flow.min_m3s / flow.max_m3s | number | Yes | Pumped-flow bounds, in m³/s (methodology §4). |
system/energy_contracts.json — Contract Registry
Section titled “system/energy_contracts.json — Contract Registry”{ "contracts": [ { "id": 0, "name": "Importação Argentina", "bus_id": 5, "operational_start_date": "2005-01-01", "type": "import", "price_per_mwh": 200.0, "limits": { "min_mw": 0.0, "max_mw": 1000.0 } }, { "id": 1, "name": "Exportação Uruguai", "bus_id": 6, "operational_start_date": "2020-04-01", "type": "export", "entry_stage_id": 1, "exit_stage_id": 60, "price_per_mwh": -150.0, "limits": { "min_mw": 0.0, "max_mw": 500.0 } } ]}| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | Unique identifier. |
name | string | Yes | Human-readable contract name. |
bus_id | integer | Yes | Bus at which the contracted power is injected or withdrawn. |
operational_start_date | string (ISO-8601 date) | Yes | Calendar date (YYYY-MM-DD) the contract enters the registry’s operational history — see Shared Operational Start Date above. |
type | string | Yes | "import" or "export" — sets the load-balance sign and the import/export set membership from methodology §3. |
price_per_mwh | number | Yes | Contract price, in dollars per MWh. Positive for imports (cost), negative for exports (revenue) — see methodology §3. |
limits.min_mw / limits.max_mw | number | Yes | Dispatch bounds, in MW. A non-zero min_mw is the hard take-or-pay floor. |
Stage-varying bounds and price are supplied via
constraints/contract_bounds.parquet, which accepts sparse
(contract_id, stage_id) rows carrying any combination of min_mw,
max_mw, and price_per_mwh; absent rows fall back to the base entity
values.
This is a topic-scoped index of the files thermals, contracts, pumping stations, and non-controllable sources touch — 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 Directory Format and Output Format pages).
Inputs
Section titled “Inputs”| File | Role |
|---|---|
system/thermals.json | Thermal plant registry — core fields, generation bounds, and optional anticipated_config (see the Configure tab). |
system/energy_contracts.json | Contract registry — direction, price, dispatch bounds, and commissioning window. |
system/pumping_stations.json | Pumping station registry — source/destination hydro, consumption rate, and flow bounds. |
system/non_controllable_sources.json | NCS registry — installed capacity, curtailment flag and cost override. |
constraints/exchange_factors.json | Optional per-stage capacity factors for transmission lines (methodology §2). |
constraints/ncs_bounds.parquet | Base available-generation values per NCS, combined with the scenario pipeline’s stochastic factor to produce the realized stochastic availability (methodology §6). |
scenarios/non_controllable_factors.json / scenarios/non_controllable_stats.parquet | Deterministic block factors or stochastic availability statistics for NCS. |
constraints/contract_bounds.parquet | Sparse per-(contract, stage) overrides of min_mw, max_mw, and price_per_mwh (see Configure tab). |
initial_conditions.json (past_anticipated_commitments) | Pre-horizon commitment seed for anticipated thermals (system elements §4). |
constraints/generic_constraint_bounds.parquet | Stage-varying bound rows for generic constraints, including any anticipated_decision(N) expression. |
For the complete field-by-field schema of each file above, see the Case Directory Format reference page in the Reference corpus.
Outputs
Section titled “Outputs”| File | Role |
|---|---|
simulation/thermals/ | Per-(stage, block, thermal) dispatch results — generation, cost, and (for anticipated plants) is_anticipated, anticipated_committed_mw, anticipated_decision_mw. |
simulation/contracts/ | Per-(stage, block, contract) dispatch results — power_mw, energy_mwh, price_per_mwh, total_cost, operative_state_code. Contract cost is booked inside the resource_cost breakdown, not a dedicated ledger. |
simulation/pumping_stations/ | Per-(stage, block, station) pumped-flow results and the associated pumping_cost (the power-consumption cost booked at the connected bus, not a station-level objective term — see the Implementation notes tab). |
simulation/non_controllables/ | Per-(stage, block, source) generation_mw, available_mw, curtailment_mw, curtailment_cost. |
simulation/costs/ | Per-(stage, block) cost breakdown; thermal fuel cost, contract cost, and curtailment cost each contribute their own column. |
For the complete output schema (columns, types, file layout), see the Output Format reference page in the Reference corpus.
Non-normative software behavior for thermal, contract, pumping, and NCS equipment — what Cobre does at runtime, beyond the equations above. This tab references the methodology body for the derivations rather than restating them.
Commissioning window: half-open, exit exclusive
Section titled “Commissioning window: half-open, exit exclusive”Every equipment type in this chapter — thermal plants, transmission lines,
import/export contracts, pumping stations, and non-controllable sources —
shares one commissioning-window predicate: an entity is active at stage t
when entry_stage_id <= t < exit_stage_id. The entry stage is inclusive;
the exit stage is exclusive — the entity is gone starting at
exit_stage_id itself, not decommissioned at the stage after it. Outside its
window, the entity’s LP columns are pinned to [0, 0]: a dormant contract
still emits an output row (power_mw = 0, a defined dormant
operative_state_code), it is simply zeroed rather than absent. This
resolves any “inclusive exit” wording found elsewhere describing the same
fields — the shipped commissioning predicate treats the exit stage as
exclusive.
Contracts are stateless
Section titled “Contracts are stateless”An energy contract contributes exactly one LP column per block per direction
and carries no state variable — it is not part of the Bellman recursion
and contributes no term to the Benders cuts (methodology §3). This is a
structural difference from hydro plants and anticipated thermals, whose
storage / ring-buffer state variables do carry a cut term. A non-zero
min_mw at a contract’s active stages is a hard take-or-pay floor: the LP
must dispatch at least that quantity at the contract price regardless of
cheaper alternatives elsewhere in the system. All contract cost — the import
cost or the (negative) export revenue — is booked inside the resource_cost
component of the per-stage cost breakdown, alongside thermal fuel cost.
Pumping stations carry no explicit objective cost
Section titled “Pumping stations carry no explicit objective cost”A pumping station’s flow variable has no direct term in the objective function (methodology §4). The cost of pumping is entirely implicit: pumping draws power at the connected bus in proportion to pumped flow, and that draw is priced by whatever the bus’s marginal cost happens to be at that stage and scenario — cheap when hydro/renewable surplus is on the margin, expensive when thermal dispatch is setting the price. This is why pumping economically self-selects toward low-price periods without any pump-specific cost coefficient to tune.
Anticipated thermals are the exception to “no cross-stage state” for equipment
Section titled “Anticipated thermals are the exception to “no cross-stage state” for equipment”Every other equipment type in this chapter is memoryless: its dispatch at
stage t depends only on that stage’s own bounds and price. An anticipated
thermal (anticipated_config.lead_stages = K) is the one exception — its
commitment decision at stage t is carried forward as ring-buffer state and
matures as delivered generation at stage t + K (system elements §4). The
anticipated_decision(N) generic-constraint expression lets a case author
cap or floor that forward commitment directly; referencing a non-anticipated
thermal with this expression is a hard load-time error, and referencing an
anticipated thermal’s per-block generation with thermal_generation(N) is
accepted but flagged, since it does not read the forward commitment.
Must-run NCS and the double-discount trap
Section titled “Must-run NCS and the double-discount trap”The allow_curtailment: false flag pins an NCS generation column to the
realized availability every scenario — a bound-equality, not merely an upper
bound. This exists specifically for aggregate, non-simulated generation
figures (small hydro, biomass, distributed generation) that an upstream
source model has already subtracted from load before the dispatch LP runs:
leaving such an aggregate curtailable lets the LP “curtail” energy that was
never separately available to curtail, silently cheapening the hydrothermal
dispatch relative to the reference model it is meant to reproduce.
Stand-alone wind and solar plants, where curtailment is a real physical and
economic choice, use the default curtailable behavior instead.
Cross-References
Section titled “Cross-References”- Notation conventions — variable and set definitions (, , , , )
- System elements — element descriptions, decision variables, and connections
- LP formulation — how equipment constraints integrate into the assembled LP; hydro water balance (§4), generation constraints (§6), variable bounds (§8)
- Hydro production models — hydro-specific production function constraints (constant, FPHA, linearized head)
- Penalty System — penalty taxonomy, regularization vs. violation costs
- Block formulations — block structure within which equipment constraints operate
- SDDP Algorithm — iterative algorithm that solves stage subproblems containing these equipment constraints