Skip to content

Equipment-Specific Formulations

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 elementsLP formulationthis spec (per-equipment deep dives).

For variable definitions and index sets, see notation conventions. For hydro production constraints specifically, see hydro production models.

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:

  • gj,k,sg_{j,k,s} = generation at thermal jj, block kk, cost segment ss

Constraints:

Total generation:

gj,k=sgj,k,sg_{j,k} = \sum_{s} g_{j,k,s}

Segment bounds:

0gj,k,sgˉj,ss0 \leq g_{j,k,s} \leq \bar{g}_{j,s} \quad \forall s

Total generation bounds:

Gjgj,kGˉj\underline{G}_j \leq g_{j,k} \leq \bar{G}_j

Both bounds are hard constraints with no slack variables — thermal dispatch is directly controllable (unlike hydro, which depends on exogenous inflows).

Objective Contribution:

kτkscj,sthgj,k,s\sum_{k} \tau_k \sum_{s} c^{th}_{j,s} \cdot g_{j,k,s}

Decision Variables:

  • fl,k+f^+_{l,k} = direct flow (source → target)
  • fl,kf^-_{l,k} = reverse flow (target → source)

Bounds:

0fl,k+Fˉl+,0fl,kFˉl0 \leq f^+_{l,k} \leq \bar{F}^+_l, \quad 0 \leq f^-_{l,k} \leq \bar{F}^-_l

Load Balance Contribution:

At source bus:

fl,k++ηlfl,k-f^+_{l,k} + \eta_l f^-_{l,k}

At target bus:

ηlfl,k+fl,k\eta_l f^+_{l,k} - f^-_{l,k}

where ηl=1losses_percent/100\eta_l = 1 - \text{losses\_percent}/100 accounts for transmission losses.

Objective Contribution:

kτkclexch(fl,k++fl,k)\sum_{k} \tau_k \cdot c^{exch}_l (f^+_{l,k} + f^-_{l,k})

Each contract is unidirectional — either an import or an export contract, identified by a type field.

Decision Variables:

  • χc,k\chi_{c,k} = dispatched power for contract cc, block kk

Bounds:

Ccχc,kCˉc\underline{C}_c \leq \chi_{c,k} \leq \bar{C}_c

Load Balance Contribution:

At connected bus:

  • Import contracts (cCimpc \in \mathcal{C}^{imp}): +χc,k+\chi_{c,k} (power entering the system)
  • Export contracts (cCexpc \in \mathcal{C}^{exp}): χc,k-\chi_{c,k} (power leaving the system)

Objective Contribution:

kτkcCccctrχc,k\sum_{k} \tau_k \sum_{c \in \mathcal{C}} c^{ctr}_c \cdot \chi_{c,k}

Because import prices (ccctrc^{ctr}_c) 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 +χc,k+\chi_{c,k} and carries a positive (cost) price; an export column withdraws χc,k-\chi_{c,k} and carries a negative (revenue) price.

Take-or-pay floor and lifecycle. A non-zero lower bound Cc\underline{C}_c is a hard take-or-pay obligation: the LP must dispatch at least Cc\underline{C}_c at the contract price, even when cheaper supply exists. Bounds [Cc,Cˉc][\underline{C}_c, \bar{C}_c] and the price ccctrc^{ctr}_c 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.

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:

  • pj,kp_{j,k} = pumped water flow at station jj, block kk (m³/s)

Bounds:

Pjpj,kPˉj\underline{P}_j \leq p_{j,k} \leq \bar{P}_j

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:

Pj,kpump=γjpj,kP^{pump}_{j,k} = \gamma_j \cdot p_{j,k}

where γj\gamma_j is the power consumption rate (MW per m³/s).

Water Balance Impact:

  • Source hydro: pj,k-p_{j,k} (water removed)
  • Destination hydro: +pj,k+p_{j,k} (water added)

Load Balance Impact:

At connected bus: Pj,kpump=γjpj,k-P^{pump}_{j,k} = -\gamma_j \cdot p_{j,k} (power consumed)

Objective Contribution: None

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.

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:

  • gr,kncg^{nc}_{r,k} = generation at non-controllable source rr, block kk

Bounds (hard):

0gr,kncAr0 \leq g^{nc}_{r,k} \leq A_r

where ArA_r is the stochastic available generation for the current (stage, scenario), bounded by [0,Gˉr][0, \bar{G}_r] (installed capacity).

Load Balance Contribution:

At connected bus: +gr,knc+g^{nc}_{r,k} (generation injected)

Objective Contribution:

kτkrRcrcurt(Argr,knc)\sum_{k} \tau_k \sum_{r \in \mathcal{R}} c^{curt}_r \cdot (A_r - g^{nc}_{r,k})

The curtailment cost crcurtc^{curt}_r is a regularization penalty (Category 3 in the Penalty System), analogous to spillage_cost for hydros — curtailment discards available “free” energy.

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.

Thermals, non-controllable sources, pumping stations, and energy contracts all carry a required operational_start_date field:

FieldTypeDescription
operational_start_datestring (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.

Thermals, lines, contracts, pumping stations, and non-controllable sources all carry the same optional pair of fields:

FieldTypeDescription
entry_stage_idinteger or nullStage index at which the entity enters service (inclusive). null means the entity is available from stage 0.
exit_stage_idinteger or nullStage 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.

FieldTypeRequiredDescription
idintegerYesUnique non-negative identifier.
namestringYesHuman-readable plant name. Used in output files, validation messages, and log output.
bus_idintegerYesIdentifier of the electrical bus to which this unit’s generation is injected. Must match an id in buses.json.
operational_start_datestring (ISO-8601 date)YesCalendar date (YYYY-MM-DD) the unit enters the registry’s operational history — see Shared Operational Start Date above.
cost_per_mwhnumberYesMarginal cost of generation, in dollars per MWh. Must be non-negative.

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 }
FieldTypeDescription
min_mwnumberMinimum 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_mwnumberMaximum 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 }
FieldTypeDescription
lead_stagesintegerNumber 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_hoursnumberPhysical 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.

RuleError ClassDescription
Bus reference integrityReference errorEvery bus_id must match an id in buses.json.
Non-negative costSchema errorcost_per_mwh must be non-negative.
Generation bounds orderingPhysical feasibilitymin_mw must be <= max_mw.
Anticipated lead validityPhysical feasibilityWhen anticipated_config is present, lead_stages must be >= 1 and must not exceed the study’s stage count.
Commitment history bijectionReference errorEvery 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
}
]
}
FieldTypeRequiredDescription
idintegerYesUnique identifier.
namestringYesHuman-readable source name.
bus_idintegerYesBus to which this source’s generation is injected.
operational_start_datestring (ISO-8601 date)YesCalendar date (YYYY-MM-DD) the source enters the registry’s operational history — see Shared Operational Start Date above.
max_generation_mwnumberYesInstalled capacity, in MW — the hard physical upper bound behind the stochastic availability described in methodology §6.
allow_curtailmentbooleanNoDefaults 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_costnumber or nullNoEntity-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 }
}
]
}
FieldTypeRequiredDescription
idintegerYesUnique identifier.
namestringYesHuman-readable station name.
bus_idintegerYesBus from which electrical power is consumed.
operational_start_datestring (ISO-8601 date)YesCalendar date (YYYY-MM-DD) the station enters the registry’s operational history — see Shared Operational Start Date above.
source_hydro_idintegerYesHydro 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_idintegerYesHydro plant into whose reservoir water is injected (see methodology §4).
consumption_mw_per_m3snumberYesPower consumption rate, in MW per m³/s (methodology §4).
flow.min_m3s / flow.max_m3snumberYesPumped-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 }
}
]
}
FieldTypeRequiredDescription
idintegerYesUnique identifier.
namestringYesHuman-readable contract name.
bus_idintegerYesBus at which the contracted power is injected or withdrawn.
operational_start_datestring (ISO-8601 date)YesCalendar date (YYYY-MM-DD) the contract enters the registry’s operational history — see Shared Operational Start Date above.
typestringYes"import" or "export" — sets the load-balance sign and the import/export set membership from methodology §3.
price_per_mwhnumberYesContract price, in dollars per MWh. Positive for imports (cost), negative for exports (revenue) — see methodology §3.
limits.min_mw / limits.max_mwnumberYesDispatch 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.

  • Notation conventions — variable and set definitions (gjg_j, flf_l, χc\chi_c, pjp_j, τk\tau_k)
  • 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