Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

System Modeling

A Cobre case describes a power system as a collection of entities. Each entity represents a physical component — a bus, a generator, a transmission line — or a contractual obligation. Together, they form the complete model that the solver turns into a sequence of LP sub-problems, one per stage per scenario trajectory.

The fundamental organizing principle is simple: every generator and every load connects to a bus. A bus is an electrical node at which the power balance constraint must hold. At each stage and each load block, the LP enforces that the total power injected into a bus equals the total power withdrawn from it. When the constraint cannot be satisfied by physical generation alone, deficit slack variables absorb the gap at a penalty cost, ensuring the LP always has a feasible solution.

Entities are grouped by type and stored in a System object. The System is built from the case directory by load_case, which runs a five-layer validation pipeline before handing the model to the solver. Within the System, all entity collections are kept in canonical ID-sorted order. This ordering is an invariant: it guarantees that simulation results are bit-for-bit identical regardless of the order entities appear in the input files.


Entity Types

Cobre models seven entity types. Four are fully implemented and contribute LP variables and constraints. Three are registered stubs that appear in the entity model but do not yet contribute LP variables in the current release.

Entity TypeStatusJSON FileDescription
BusFullsystem/buses.jsonElectrical node. Power balance constraint per stage per block. See Network Topology.
LineFullsystem/lines.jsonTransmission interconnection between two buses with flow limits and losses. See Network Topology.
HydroFullsystem/hydros.jsonReservoir-turbine-spillway system with cascade linkage. See Hydro Plants.
ThermalFullsystem/thermals.jsonDispatchable generator with piecewise-linear cost curve. See Thermal Units.
ContractStubsystem/contracts.jsonEnergy purchase or sale obligation. Entity exists in registry; no LP variables in this release.
Pumping StationStubsystem/pumping_stations.jsonPumped-storage or water-transfer station. Entity exists in registry; no LP variables in this release.
Non-ControllableStubsystem/non_controllable.jsonVariable renewable source (wind, solar, run-of-river). Entity exists in registry; no LP variables in this release.

The three stub types are registered in the entity model from Phase 1 so that LP construction code can iterate over all seven types consistently. Adding LP contributions for stub entities is planned for future releases.


How Entities Connect

The network is bus-centric. Every entity that produces or consumes power is attached to a bus via a bus_id field:

   Hydro ──┐
           │ inject
  Thermal ─┤
           ├──> Bus <──── Line ────> Bus
  NCS ─────┘
                │
               load
                │
           Contract
         Pumping Station

At each stage and load block, the LP enforces the bus balance constraint:

  sum(generation at bus) + sum(imports from lines) + deficit
    = load_demand + sum(exports to lines) + excess

Deficit and excess slack variables absorb imbalance at a penalty cost, ensuring the LP is always feasible. When the deficit penalty is high enough relative to the cost of available generation, the solver will prefer to generate rather than incur deficit.

Cascade topology governs hydro plant interactions. A hydro plant with a non-null downstream_id sends all of its outflow — turbined flow plus spillage — into the downstream plant’s reservoir at the same stage. The cascade forms a directed forest: multiple upstream plants may flow into a single downstream plant, but no cycles are allowed. Water balance is computed in topological order — upstream plants first, downstream plants last — in a single pass per stage.


Declaration-Order Invariance

The order in which entities appear in the JSON input files does not affect results. Cobre reads all entities from their files, then sorts each collection by entity ID before building the System. Every function that processes entity collections operates on this canonical sorted order.

This invariant has a practical consequence: you can rearrange entries in buses.json, hydros.json, or any other entity file without changing the simulation output. You can also add new entities with lower IDs than existing ones without disturbing results for the existing entities.


Penalties and Soft Constraints

LP solvers require feasible problems. Physical constraints — minimum outflow, minimum turbined flow, reservoir bounds — can become infeasible under extreme stochastic scenarios (very low inflow, very high load). Cobre handles this by making nearly every physical constraint soft: instead of a hard infeasibility, the solver pays a penalty cost to violate the constraint by a small amount.

Penalties are set at three levels, resolved from most specific to most general:

  1. Stage-level override — penalty files for individual stages, when present
  2. Entity-level override — a penalties block inside the entity’s JSON object
  3. Global default — the top-level penalties.json file in the case directory

This three-tier cascade gives you precise control: you can set a strict global spillage penalty and then relax it for a specific plant that is known to spill frequently in wet years. For details on the penalty fields for each entity type, see the Configuration guide and the Case Format Reference.

The bus deficit segments are the most important penalty to configure correctly. A deficit cost that is too low makes the solver prefer deficit over building generation capacity; a cost that is too high (or an unbounded segment that is absent) can cause numerical instability. The final deficit segment must always have depth_mw: null (unbounded) to guarantee LP feasibility.


Entity Lifecycle

Entities can enter service or be decommissioned at specified stages using entry_stage_id and exit_stage_id fields:

FieldTypeMeaning
entry_stage_idinteger or nullStage index at which the entity enters service (inclusive). null = available from stage 0
exit_stage_idinteger or nullStage index at which the entity is decommissioned (inclusive). null = never decommissioned

These fields are available on Hydro, Thermal, and Line entities. When a plant has entry_stage_id: 12, the LP does not include any variables for that plant in stages 0 through 11. From stage 12 onward, the plant appears in every sub-problem as normal.

Lifecycle fields are useful for planning studies that span commissioning or retirement events: new thermal plants coming online mid-horizon, or aging hydro units being decommissioned. Each lifecycle event is validated to ensure that entry_stage_id falls within the stage range defined in stages.json.