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 Type | Status | JSON File | Description |
|---|---|---|---|
| Bus | Full | system/buses.json | Electrical node. Power balance constraint per stage per block. See Network Topology. |
| Line | Full | system/lines.json | Transmission interconnection between two buses with flow limits and losses. See Network Topology. |
| Hydro | Full | system/hydros.json | Reservoir-turbine-spillway system with cascade linkage. See Hydro Plants. |
| Thermal | Full | system/thermals.json | Dispatchable generator with piecewise-linear cost curve. See Thermal Units. |
| Contract | Stub | system/contracts.json | Energy purchase or sale obligation. Entity exists in registry; no LP variables in this release. |
| Pumping Station | Stub | system/pumping_stations.json | Pumped-storage or water-transfer station. Entity exists in registry; no LP variables in this release. |
| Non-Controllable | Stub | system/non_controllable.json | Variable 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:
- Stage-level override — penalty files for individual stages, when present
- Entity-level override — a
penaltiesblock inside the entity’s JSON object - Global default — the top-level
penalties.jsonfile 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:
| Field | Type | Meaning |
|---|---|---|
entry_stage_id | integer or null | Stage index at which the entity enters service (inclusive). null = available from stage 0 |
exit_stage_id | integer or null | Stage 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.
Related Pages
- Hydro Plants — complete field reference for
system/hydros.json - Thermal Units — complete field reference for
system/thermals.json - Network Topology — buses, lines, deficit modeling, and transmission
- Anatomy of a Case — walkthrough of every file in the
1dtoyexample - Case Format Reference — complete JSON schema for all input files