Case Directory Format
A Cobre case directory is a self-contained folder that holds all input data
for a single power system study. load_case reads this directory and produces
a fully-validated System ready for the solver.
For a description of how these files are parsed and validated, see the
cobre-io crate documentation in the cobre repository.
Directory layout
Section titled “Directory layout”my_case/├── config.json # Solver configuration (required)├── penalties.json # Global penalty defaults (required)├── stages.json # Stage sequence and policy graph (required)├── initial_conditions.json # Reservoir storage at study start (required)├── post_study_stages.json # Post-study boundary calendar + thermal bounds (optional)├── system/│ ├── buses.json # Electrical buses (required)│ ├── lines.json # Transmission lines (required)│ ├── hydros.json # Hydro plants (required)│ ├── thermals.json # Thermal plants (required)│ ├── non_controllable_sources.json # Intermittent sources (optional)│ ├── pumping_stations.json # Pumping stations (optional)│ ├── energy_contracts.json # Bilateral contracts (optional)│ ├── hydro_geometry.parquet # Reservoir geometry tables (optional)│ ├── hydro_production_models.json # FPHA production function configs (optional)│ ├── hydro_energy_productivity.parquet # Per-plant, per-stage energy-conversion overrides (optional)│ ├── fpha_hyperplanes.parquet # FPHA hyperplane coefficients (optional)│ └── tailrace_curves.parquet # Piecewise-quartic tailrace curves (optional)├── scenarios/│ ├── inflow_history.parquet # Historical inflow series (optional)│ ├── inflow_seasonal_stats.parquet # PAR model seasonal statistics (optional)│ ├── inflow_ar_coefficients.parquet # PAR autoregressive coefficients (optional)│ ├── external_inflow_scenarios.parquet # External inflow scenarios (optional)│ ├── external_load_scenarios.parquet # External load scenarios (optional)│ ├── external_ncs_scenarios.parquet # External NCS scenarios (optional)│ ├── load_seasonal_stats.parquet # Load model seasonal statistics (optional)│ ├── load_factors.json # Load scaling factors (optional)│ ├── non_controllable_factors.json # NCS block scaling factors (optional)│ ├── non_controllable_stats.parquet # NCS stochastic availability (optional)│ ├── correlation.json # Cross-series correlation model (optional)│ └── noise_openings.parquet # User-supplied backward-pass opening tree (optional)└── constraints/ ├── thermal_bounds.parquet # Stage-varying thermal bounds (optional) ├── hydro_bounds.parquet # Stage-varying hydro bounds (optional) ├── hydro_unit_group_bounds.parquet # Stage-varying hydro unit group bounds (optional) ├── line_bounds.parquet # Stage-varying line bounds (optional) ├── pumping_bounds.parquet # Stage-varying pumping bounds (optional) ├── contract_bounds.parquet # Stage-varying contract bounds (optional) ├── ncs_bounds.parquet # Stage-varying NCS available generation bounds (optional) ├── generic_constraints.json # User-defined LP constraints (optional) ├── generic_constraint_bounds.parquet # Bounds for generic constraints (optional) ├── generic_parameters.json # Named parameters for constraint expressions (optional) ├── penalty_overrides_bus.parquet # Stage-varying bus penalty overrides (optional) ├── penalty_overrides_line.parquet # Stage-varying line penalty overrides (optional) ├── penalty_overrides_hydro.parquet # Stage-varying hydro penalty overrides (optional) └── penalty_overrides_ncs.parquet # Stage-varying NCS penalty overrides (optional)File summary
Section titled “File summary”| File | Format | Required | Description |
|---|---|---|---|
config.json | JSON | Yes | Solver configuration |
penalties.json | JSON | Yes | Global penalty defaults |
stages.json | JSON | Yes | Stage sequence and policy graph |
initial_conditions.json | JSON | Yes | Initial reservoir storage |
post_study_stages.json | JSON | No | Post-study boundary calendar and per-thermal delivery bounds |
system/buses.json | JSON | Yes | Electrical bus registry |
system/lines.json | JSON | Yes | Transmission line registry |
system/hydros.json | JSON | Yes | Hydro plant registry |
system/thermals.json | JSON | Yes | Thermal plant registry |
system/non_controllable_sources.json | JSON | No | Intermittent source registry |
system/pumping_stations.json | JSON | No | Pumping station registry |
system/energy_contracts.json | JSON | No | Bilateral energy contract registry |
system/hydro_geometry.parquet | Parquet | No | Reservoir geometry elevation tables |
system/hydro_production_models.json | JSON | No | FPHA production function configs |
system/fpha_hyperplanes.parquet | Parquet | No | FPHA hyperplane coefficients |
system/hydro_energy_productivity.parquet | Parquet | No | Per-plant, per-stage energy-conversion overrides |
system/tailrace_curves.parquet | Parquet | No | Piecewise-quartic tailrace curves with backwater families |
scenarios/inflow_history.parquet | Parquet | No | Historical inflow time series |
scenarios/inflow_seasonal_stats.parquet | Parquet | No | PAR model seasonal statistics |
scenarios/inflow_ar_coefficients.parquet | Parquet | No | PAR autoregressive coefficients |
scenarios/external_inflow_scenarios.parquet | Parquet | No | External inflow scenario realizations (hydro_id, stage_id, scenario_id, value_m3s) |
scenarios/external_load_scenarios.parquet | Parquet | No | External load scenario realizations (bus_id, stage_id, scenario_id, value_mw) |
scenarios/external_ncs_scenarios.parquet | Parquet | No | External NCS scenario realizations (ncs_id, stage_id, scenario_id, availability_factor) |
scenarios/load_seasonal_stats.parquet | Parquet | No | Load model seasonal statistics |
scenarios/load_factors.json | JSON | No | Load scaling factors per bus/stage |
scenarios/non_controllable_factors.json | JSON | No | NCS block scaling factors per source/stage |
scenarios/non_controllable_stats.parquet | Parquet | No | NCS stochastic availability factors |
scenarios/correlation.json | JSON | No | Cross-series correlation model |
scenarios/noise_openings.parquet | Parquet | No | User-supplied backward-pass opening tree |
constraints/thermal_bounds.parquet | Parquet | No | Stage-varying thermal generation bounds |
constraints/hydro_bounds.parquet | Parquet | No | Stage-varying hydro operational bounds |
constraints/hydro_unit_group_bounds.parquet | Parquet | No | Stage-varying, optionally per-block hydro unit group bounds |
constraints/line_bounds.parquet | Parquet | No | Stage-varying line flow capacity |
constraints/pumping_bounds.parquet | Parquet | No | Stage-varying pumping flow bounds |
constraints/contract_bounds.parquet | Parquet | No | Stage-varying contract power bounds |
constraints/ncs_bounds.parquet | Parquet | No | Stage-varying NCS available generation bounds |
constraints/generic_constraints.json | JSON | No | User-defined LP constraints |
constraints/generic_constraint_bounds.parquet | Parquet | No | Generic constraint RHS bounds |
constraints/generic_parameters.json | JSON | No | Named parameters for constraint expressions |
constraints/penalty_overrides_bus.parquet | Parquet | No | Stage-varying bus excess cost |
constraints/penalty_overrides_line.parquet | Parquet | No | Stage-varying line exchange cost |
constraints/penalty_overrides_hydro.parquet | Parquet | No | Stage-varying hydro penalty costs |
constraints/penalty_overrides_ncs.parquet | Parquet | No | Stage-varying NCS curtailment cost |
Root-level files
Section titled “Root-level files”config.json
Section titled “config.json”Controls all solver parameters. The training section is required; all other
sections are optional and fall back to documented defaults when absent.
Top-level sections:
| Section | Type | Default | Purpose |
|---|---|---|---|
$schema | string | null | JSON Schema URI for editor validation (ignored during processing) |
modeling | object | {} | Inflow non-negativity treatment; objective cost-scale conditioning |
training | object | required | Iteration count, stopping rules, cut selection, parallelism, solver |
estimation | object | {} | PAR(p) model estimation settings (max order, selection criterion) |
upper_bound_evaluation | object | {} | Inner approximation upper-bound settings |
policy | object | fresh mode | Policy directory path and warm-start mode |
simulation | object | disabled | Post-training simulation settings |
exports | object | all enabled | Output file selection flags |
modeling section:
| Field | Type | Default | Description |
|---|---|---|---|
modeling.inflow_non_negativity.method | string | "penalty" | How to handle negative modelled inflows. One of "none", "penalty", "truncation", "truncation_with_penalty" |
modeling.cost_scale_factor | number | 1_000_000.0 | Divisor on non-θ objective coefficients; objective conditioning only (see Configuration) |
The per-hydro penalty coefficient applied to the inflow slack column is
authored in penalties.json::hydro.inflow_nonnegativity_cost.
training section (mandatory fields):
| Field | Type | Default | Description |
|---|---|---|---|
training.selection | object | required | Scenario-selection method — sampled{forward_passes} or enumerated{} (see below); a missing training.selection is a hard load error |
training.stopping_rules | array | required | At least one stopping rule entry; must include an iteration_limit rule |
training.stopping_mode | string | "any" | How multiple rules combine: "any" (stop when any triggers) or "all" (stop when all trigger) |
training.enabled | boolean | true | When false, skip training and proceed directly to simulation |
training.tree_seed | integer or null | null | Random seed for reproducible noise generation (see Seed resolution) |
training.scenario_source | object or null | null | Per-class sampling scheme for the training forward pass (see below) |
training.parallelism | object or null | {backward_scheduler: trial_point} | Backward-pass scheduler selection; see Configuration |
training.solver | object or null | null | LP solver retry policy and optional per-phase solver profiles; see Configuration |
training.selection sub-section:
Chooses how many trajectories the training forward pass runs per iteration, or
whether it exhaustively enumerates the scenario openings instead of sampling. The
object is internally tagged on method; each variant accepts only its own fields
(pairing forward_passes with "enumerated" is a load-time error). There is no
default forward-pass count — an absent training.selection is a hard load error,
and the root-level training.forward_passes field from earlier releases is
rejected as an unknown field.
| Field | Type | Required | Description |
|---|---|---|---|
training.selection.method | string | Yes | "sampled" or "enumerated" |
training.selection.forward_passes | integer | sampled only | Number of forward-pass trajectories per iteration (>= 1). Rejected under "enumerated" |
training.scenario_source sub-section:
Configures which scenario sampling scheme is used for each entity class during training.
When absent, all classes default to InSample (PAR-based noise generation).
| Field | Type | Default | Description |
|---|---|---|---|
training.scenario_source.inflow.scheme | string | "in_sample" | Inflow sampling scheme: "in_sample", "historical", "external", or "out_of_sample" |
training.scenario_source.load.scheme | string | "in_sample" | Load sampling scheme: "in_sample", "historical", "external", or "out_of_sample" |
training.scenario_source.ncs.scheme | string | "in_sample" | NCS sampling scheme: "in_sample", "historical", "external", or "out_of_sample" |
training.scenario_source.historical_years | array or object | null | Years eligible as inflow replay windows. List ([2010, 2015]) or range ({"from": 2010, "to": 2023}) |
Seed resolution
Section titled “Seed resolution”training.tree_seed in config.json is the only seed that controls noise generation
at runtime. It governs both the training forward pass and the post-training simulation.
-
When
training.tree_seedis a non-null integer, the CLI uses|seed|(unsigned absolute value) as the base seed for deterministic SipHash-1-3 noise generation. Results are bit-for-bit reproducible across runs with the same seed. -
When
training.tree_seedis absent ornull, the CLI applies a default seed of 42 and prints a warning to stderr:warning: no random seed specified in config.json (training.tree_seed); using default seed 42. Set training.tree_seed for reproducible results.Runs will be reproducible (same output every time) but the seed value is arbitrary. Set
training.tree_seedexplicitly to make the choice intentional and visible to other users of the case directory.
training.stopping_rules entries:
Each entry has a "type" discriminator. Valid types:
| Type | Required fields | Stops when |
|---|---|---|
iteration_limit | limit: integer | Iteration count reaches limit |
time_limit | seconds: number | Wall-clock time exceeds seconds |
bound_stalling | iterations: integer, tolerance: number | Lower bound improvement falls below tolerance over iterations window |
simulation | replications, period, bound_window, distance_tol, bound_tol | Both policy cost and bound have stabilized |
training.cut_selection sub-section:
Two always-on knobs plus a tagged selection object that chooses the method
and carries only that method’s parameters. Omitting selection disables row
selection. See the
Configuration guide for the full
per-method field tables.
| Field | Type | Default | Description |
|---|---|---|---|
row_activity_tolerance | number | 0.0 | Minimum dual multiplier for a row to count as binding |
max_active_per_stage | integer | null | Hard cap on active rows per stage; null = no cap |
selection | object | null | Active method and its parameters; method is one of "level1", "lml1", "domination", "dynamic" |
upper_bound_evaluation section:
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | null | Enable vertex-based inner approximation |
initial_iteration | integer | null | First iteration to compute the upper bound |
interval_iterations | integer | null | Iterations between upper-bound evaluations |
lipschitz.mode | string | null | Lipschitz constant computation mode: "auto" |
lipschitz.fallback_value | number | null | Fallback when automatic computation fails |
lipschitz.scale_factor | number | null | Multiplicative safety margin |
policy section:
| Field | Type | Default | Description |
|---|---|---|---|
path | string | "./policy" | Directory for policy data (cuts, states, vertices, basis) |
mode | string | "fresh" | Initialization mode: "fresh", "warm_start", or "resume" |
boundary | object or null | null | Terminal boundary cut config: path (string) + source_stage (int) |
checkpointing.enabled | boolean | null | Enable periodic checkpointing |
checkpointing.initial_iteration | integer | null | First iteration to write a checkpoint |
checkpointing.interval_iterations | integer | null | Iterations between checkpoints |
checkpointing.store_basis | boolean | null | Include LP basis in checkpoints |
checkpointing.compress | boolean | null | Compress checkpoint files |
simulation section:
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable post-training simulation |
simulation.selection | object or null | null | Scenario-selection method — sampled{num_scenarios} or enumerated{} (see below); absent resolves to the default sampled count (2000) |
io_channel_capacity | integer | 64 | Channel capacity between simulation and I/O writer threads |
simulation.scenario_source | object or null | null | Per-class sampling scheme for the simulation pass (see below) |
simulation.scenario_source.inflow.scheme | string | "in_sample" | Inflow sampling scheme: "in_sample", "historical", "external", or "out_of_sample" |
simulation.scenario_source.load.scheme | string | "in_sample" | Load sampling scheme: "in_sample", "historical", "external", or "out_of_sample" |
simulation.scenario_source.ncs.scheme | string | "in_sample" | NCS sampling scheme: "in_sample", "historical", "external", or "out_of_sample" |
simulation.scenario_source.historical_years | array or object | null | Years eligible as inflow replay windows. List ([2010, 2015]) or range ({"from": 2010, "to": 2023}) |
simulation.selection sub-section:
Chooses how many trajectories the post-training simulation runs, or whether it
exhaustively enumerates the scenario openings instead of sampling. Internally
tagged on method, mirroring training.selection above. Unlike
training.selection, this field is optional: an absent simulation.selection
resolves to a sampled count of 2000 — the same default the flat num_scenarios
field carried before this field existed.
| Field | Type | Required | Description |
|---|---|---|---|
simulation.selection.method | string | Yes (if present) | "sampled" or "enumerated" |
simulation.selection.num_scenarios | integer | sampled only | Number of simulation trajectories to draw (>= 0). Rejected under "enumerated" |
exports section:
| Field | Type | Default | Description |
|---|---|---|---|
states | boolean | false | Export visited forward-pass trial points to the policy checkpoint |
stochastic | boolean | false | Export stochastic preprocessing artifacts to output/stochastic/ |
Minimal valid example:
{ "$schema": "https://docs.cobre-rs.dev/schemas/config.schema.json", "training": { "selection": { "method": "sampled", "forward_passes": 192 }, "stopping_rules": [{ "type": "iteration_limit", "limit": 200 }] }}penalties.json
Section titled “penalties.json”Global penalty cost defaults used when no entity-level override is present.
All four sections are required. Every scalar cost must be strictly positive (> 0.0).
Deficit segment costs must be monotonically increasing and the last segment must
have depth_mw: null (unbounded).
| Section | Field | Type | Description |
|---|---|---|---|
bus | deficit_segments | array | Piecewise-linear deficit cost tiers |
bus | deficit_segments[].depth_mw | number or null | Segment depth (MW); null for the final unbounded segment |
bus | deficit_segments[].cost | number | Cost per MWh of deficit in this tier (USD/MWh) |
bus | excess_cost | number | Cost per MWh of excess injection (USD/MWh) |
line | exchange_cost | number | Cost per MWh of inter-bus exchange flow (USD/MWh) |
hydro | spillage_cost | number | Spillage penalty |
hydro | turbined_cost | number | Turbined flow regularization cost (applied to every hydro) |
hydro | diversion_cost | number | Diversion flow penalty |
hydro | storage_violation_below_cost | number | Storage below-minimum violation penalty |
hydro | filling_target_violation_cost | number | Filling target violation penalty |
hydro | turbined_violation_below_cost | number | Turbined flow below-minimum violation penalty |
hydro | outflow_violation_below_cost | number | Total outflow below-minimum violation penalty |
hydro | outflow_violation_above_cost | number | Total outflow above-maximum violation penalty |
hydro | generation_violation_below_cost | number | Generation below-minimum violation penalty |
hydro | evaporation_violation_cost | number | Symmetric evaporation violation penalty |
hydro | evaporation_violation_pos_cost | number or null | Optional over-evaporation override; supersedes evaporation_violation_cost for the positive direction. Omitted = symmetric value |
hydro | evaporation_violation_neg_cost | number or null | Optional under-evaporation override; supersedes evaporation_violation_cost for the negative direction. Omitted = symmetric value |
hydro | water_withdrawal_violation_cost | number | Symmetric water withdrawal violation penalty |
hydro | water_withdrawal_violation_pos_cost | number or null | Optional over-withdrawal override; supersedes water_withdrawal_violation_cost for the positive direction. Omitted = symmetric value |
hydro | water_withdrawal_violation_neg_cost | number or null | Optional under-withdrawal override; supersedes water_withdrawal_violation_cost for the negative direction. Omitted = symmetric value |
hydro | inflow_nonnegativity_cost | number or null | Optional inflow non-negativity penalty. Omitted = default 1000.0 |
non_controllable_source | curtailment_cost | number | Curtailment penalty (USD/MWh) |
Example:
{ "$schema": "https://docs.cobre-rs.dev/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 }}stages.json
Section titled “stages.json”Defines the temporal structure of the study: stage sequence, block decomposition, and policy graph horizon type.
Top-level fields:
| Field | Required | Description |
|---|---|---|
policy_graph | Yes | Horizon type, annual discount rate, and stage transitions/nodes (see below) |
stages | Yes | Array of study stage definitions |
season_definitions | No | Season labeling for seasonal model alignment |
pre_study_stages | No | Pre-study stages for AR model warm-up (negative IDs) |
policy_graph sub-object:
| Field | Required | Description |
|---|---|---|
type | Yes | Horizon type. Only "finite_horizon" is supported; "cyclic" is reserved and rejected at load (no engine consumer today) |
annual_discount_rate | Yes | Global annual discount rate (>= 0.0) |
nodes[] | No | Policy-graph nodes (see below). Absent ⇒ the graph is a stage chain and transitions[] endpoints are read directly as stage ids |
transitions[] | No | Stage transitions (see below). Defaults to an empty array |
A deck with no nodes[] is a stage chain byte-for-byte as before — existing
stages.json files with only policy_graph.transitions need no migration.
policy_graph.nodes[] entry fields:
| Field | Required | Description |
|---|---|---|
id | Yes | Unique node id, referenced by transitions[] endpoints when nodes[] is declared |
stage_id | Yes | Declared study-stage id this node sits at; resolved against the study stages, never an array index |
scenario_id | No | Per-stage external-library realization column this node carries. Required at a stage carrying a slot-occupying external class, omitted where none. scenario_id: k is exactly the degenerate one-element weighted opening set [{ "scenario_id": k, "probability": 1.0 }] |
label | No | Optional human-readable label |
policy_graph.transitions[] entry fields:
| Field | Required | Description |
|---|---|---|
source_id | Yes | Source endpoint: a node id when policy_graph.nodes[] is non-empty, a stage id otherwise |
target_id | Yes | Target endpoint: a node id when policy_graph.nodes[] is non-empty, a stage id otherwise |
probability | Yes | Transition probability |
annual_discount_rate_override | No | Optional per-transition discount rate override; absent uses policy_graph.annual_discount_rate |
stages[] entry fields:
| Field | Required | Description |
|---|---|---|
id | Yes | Stage identifier (non-negative integer, unique) |
start_date | Yes | ISO 8601 date (e.g., "2024-01-01") |
end_date | Yes | ISO 8601 date; must be after start_date |
blocks | Yes | Array of load blocks (id, name, hours) |
num_openings | Yes | Number of within-node openings for this stage (>= 1); required on the plain stage-chain dialect (no policy_graph.nodes[]) and at a generated-openings stage under nodes[], rejected at a stage that carries only external openings |
season_id | No | Reference to a season in season_definitions |
block_mode | No | Block execution mode: "parallel" (default) or "chronological" |
state_variables | No | Cut dimension for this stage: { storage, inflow_lags } booleans, defaulting to storage-only (inflow_lags off). Governs which state dimensions the stage’s cuts project onto — distinct from what is reported; a disabled dimension is projected out of the cut, not zero-padded. Travel-time buckets and anticipated-thermal slots are always included. See Cut Management. |
risk_measure | No | Per-stage risk measure: "expectation" or CVaR config |
sampling_method | No | Noise method: "saa" or other variants |
season_definitions sub-object:
The optional season_definitions object maps season IDs to calendar periods for the PAR model.
When absent, Cobre infers 12 monthly seasons from stage dates. When present, it controls
how season_id values on stages translate to stochastic parameters.
| Field | Required | Description |
|---|---|---|
cycle_type | Yes | "monthly", "weekly", or "custom" |
seasons | Yes | Array of season entries (see below) |
season_definitions.seasons[] entry fields:
| Field | Required | Description |
|---|---|---|
id | Yes | Season identifier (0-based integer, unique within the season map) |
label | Yes | Human-readable label (e.g., "January", "Q1", "Wet Season") |
month_start | Yes | Calendar month where the season starts (1–12) |
day_start | Custom only | Calendar day where the season starts (1–31). Required for custom cycle type. |
month_end | Custom only | Calendar month where the season ends (1–12). Required for custom cycle type. |
day_end | Custom only | Calendar day where the season ends (1–31). Required for custom cycle type. |
Cycle types:
"monthly"— seasons map to calendar months (12 seasons, 0 = January, …, 11 = December). Onlyid,label, andmonth_startare needed per entry."weekly"— seasons map to ISO calendar weeks (52 seasons). Onlyid,label, andmonth_startare needed per entry."custom"— user-defined date ranges with explicitmonth_start/day_start/month_end/day_end. All four boundary fields are required. Use this cycle type for mixed-resolution studies where some stages are monthly (IDs 0–11) and others are quarterly (IDs 12–15).
Example — Custom cycle type with monthly and quarterly seasons:
{ "season_definitions": { "cycle_type": "custom", "seasons": [ { "id": 0, "label": "January", "month_start": 1, "day_start": 1, "month_end": 2, "day_end": 1 }, { "id": 1, "label": "February", "month_start": 2, "day_start": 1, "month_end": 3, "day_end": 1 }, { "id": 11, "label": "December", "month_start": 12, "day_start": 1, "month_end": 1, "day_end": 1 }, { "id": 12, "label": "Q1", "month_start": 1, "day_start": 1, "month_end": 4, "day_end": 1 }, { "id": 13, "label": "Q2", "month_start": 4, "day_start": 1, "month_end": 7, "day_end": 1 }, { "id": 14, "label": "Q3", "month_start": 7, "day_start": 1, "month_end": 10, "day_end": 1 }, { "id": 15, "label": "Q4", "month_start": 10, "day_start": 1, "month_end": 1, "day_end": 1 } ] }}In this example, seasons 0–11 cover monthly PAR models for the near-term phase and seasons 12–15
cover quarterly PAR models for the long-term phase. Each monthly stage assigns a season_id of 0–11;
each quarterly stage assigns a season_id of 12–15. Rule 29 enforces that stages sharing the same
season_id must have similar durations (within 7 days), so monthly and quarterly stages must use
distinct season IDs.
initial_conditions.json
Section titled “initial_conditions.json”Initial reservoir storage, pre-study releases already in transit on declared travel-time arcs, and recent observations at the start of the study.
| Field | Required | Description |
|---|---|---|
storage | Yes | Array of { "hydro_id": integer, "value_hm3": number } entries for operating hydros |
filling_storage | Yes | Array of { "hydro_id": integer, "value_hm3": number } entries for filling hydros |
past_defluences | No | Array of windowed pre-study release records seeding the in-transit buckets of a declared travel-time arc (see below) |
recent_observations | No | Array of observed inflow entries for mid-season study starts (see below) |
past_anticipated_commitments | No | Array of windowed committed-MW records for anticipated thermals — the plant’s pre-study decisions, whether they deliver in-study or past the horizon (see below) |
Each hydro_id must be unique within its array and must not appear in both
storage and filling_storage. All value_hm3 values must be non-negative.
Seeding the PAR lag chain and the mid-period accumulator does not read a
positional lag array from this file — this file carries no past_inflows
field, and one is rejected at load. The seed instead derives from
scenarios/inflow_history.parquet’s windowed record, shadowed day-wise by
recent_observations wherever the two overlap.
past_defluences supplies the pre-study releases already in transit on a
declared travel-time arc (hydros[].travel_time_hours), so the first stages’
delayed-arrival buckets are seeded rather than empty. Each entry gives a release
window and its average rate for an upstream plant whose outflow feeds a
downstream arc:
| Field | Type | Description |
|---|---|---|
hydro_id | integer | Upstream plant whose release feeds the arc |
start_date | string | Start of the release window (inclusive), ISO 8601 YYYY-MM-DD |
end_date | string | End of the release window (exclusive), ISO 8601 YYYY-MM-DD; must be after start_date |
value_m3s | number | Average release rate over the window in m³/s; finite and non-negative |
The windows must cover the arc’s in-transit span at study start —
[start_0 − travel_time, start_0) — with no gap and none future-dated past the
study start; otherwise the study is rejected. There is no fallback for
incomplete coverage. Optional; defaults to an empty array when no arc needs
seeding.
recent_observations provides observed inflow data for partial periods
before the study start. Used to seed the lag accumulator when a study begins
mid-season (e.g., a coupled study starting on January 5 needs observed inflow
for January 1—4). Each entry has:
| Field | Type | Description |
|---|---|---|
hydro_id | integer | Hydro plant identifier |
start_date | string | Start of the observation period (inclusive), ISO 8601 YYYY-MM-DD |
end_date | string | End of the observation period (exclusive), ISO 8601 YYYY-MM-DD |
value_m3s | number | Average inflow observed during the period, in m³/s |
Date ranges for the same hydro must not overlap; adjacent ranges
(start_date == previous end_date) are accepted. Values must be finite; a
negative value is accepted — the quantity is incremental inflow (a plant’s
natural flow minus its upstream plants’), so a negative window is real
hydrology, and the LP prices it through the inflow non-negativity slack.
Semantic validation reports one warning per file naming the negative count
and the most-negative value’s hydro, so a genuinely sign-flipped series still
stands out. Optional; defaults to an empty array when absent. Existing cases
without this field are unaffected.
past_anticipated_commitments supplies the externally-decided committed MW
rate for an anticipated thermal plant — every delivery the plant decided
before the study begins, whether that delivery matures inside the horizon
(the classic pre-horizon ring seed) or past it (the DECOMP já-comandada case,
a commitment already commanded before the study whose delivery lands beyond the
last stage). Each entry is a dated window carrying a constant rate:
| Field | Type | Description |
|---|---|---|
thermal_id | integer | Anticipated thermal plant identifier; must reference a thermal declaring anticipated_config |
start_date | string | Start of the commitment window (inclusive), ISO 8601 YYYY-MM-DD |
end_date | string | End of the commitment window (exclusive), ISO 8601 YYYY-MM-DD; must be after start_date |
value_mw | number | Committed MW rate held constant over the window; finite |
A plant’s windows must tile every delivery stage the plant decided before
the study, exactly — coverage 1.0, no gap, no overlap. Those stages may
fall on either side of the horizon: the leading calendar-derived delivery
stages inside the study, and any post-horizon stages of a já-comandada
delivery. A window may never cover a stage the study itself decides, and a
single window may not straddle the horizon end — split it into an in-study
window ending at the horizon and a post-study window starting there. A stretch
with no scheduled commitment still needs an explicit 0.0-rate window rather
than a gap left to be inferred. Each value_mw must lie within the plant’s
[min_generation_mw, max_generation_mw] bounds and, if the plant declares a
commissioning window, mature inside it. The values are sunk cost: they
never enter the study objective — a post-horizon window is priced against the
terminal boundary by a constant fold into the future-cost cut intercepts, and
echoed at its real delivery date in anticipated/fixed_deliveries.parquet.
Windows for the same thermal_id must not overlap (adjacent ranges where
start_date == previous end_date are accepted). Optional; defaults to an empty
array when no anticipated thermals are present.
The old positional per-lead-slot MW-array shape is no longer accepted —
the file’s deny_unknown_fields contract rejects a deck still carrying that
legacy array field; each entry must be an explicit dated window instead. See
system elements §4 for
the delivery-stage mechanics this tiles against.
{ "past_anticipated_commitments": [ { "thermal_id": 2, "start_date": "2026-01-01", "end_date": "2026-02-01", "value_mw": 0.0 } ]}Example:
{ "storage": [{ "hydro_id": 0, "value_hm3": 15000.0 }], "filling_storage": [], "recent_observations": [ { "hydro_id": 0, "start_date": "2026-04-01", "end_date": "2026-04-04", "value_m3s": 500.0 }, { "hydro_id": 0, "start_date": "2026-04-04", "end_date": "2026-04-11", "value_m3s": 480.0 } ]}post_study_stages.json
Section titled “post_study_stages.json”The post-study boundary calendar: a sequence of post-horizon stages plus a
per-(thermal, post-study stage) cost and delivery-bound table. Its
thermal_bounds[] table is the sole surface for declaring the bound and
cost of a commitment an anticipated thermal decides in-study but delivers
after the horizon (the boundary prices it on the extended calendar). Optional
and inert when absent; required once any anticipated thermal’s lead reaches
a post-study stage — a missing cell for a reached stage is rejected at load,
naming the plant and the post-study stage index. See
Post-Study Boundary for the boundary formulation
this file feeds.
stages[] — the post-horizon calendar:
| Field | Type | Description |
|---|---|---|
start_date | string | Stage start date (inclusive), ISO 8601 YYYY-MM-DD |
duration_hours | number | Stage duration in hours; finite and > 0.0. end_date is derived from start_date + duration_hours, never declared |
Stages are date-contiguous (each stage’s implicit end_date equals the
next stage’s start_date) and the first start_date must equal the study
horizon end — the date immediately after the last in-study stage. Post-study
stages are boundary-only: they are never dispatched, never a study stage,
and never carry Benders cuts — they exist solely to give a post-horizon
delivery a calendar position to be priced against. No two stages may share a
start_date.
thermal_bounds[] — per-(thermal, post-study stage) cost and delivery
capability:
| Field | Type | Description |
|---|---|---|
thermal_id | integer | Thermal plant identifier; must reference a thermal declaring anticipated_config |
post_study_stage_index | integer | Zero-based index into stages[]; must be < stages.len() |
cost_per_mwh | number | Fuel cost ($/MWh) at this cell; finite |
min_mw | number | Lower bound of the delivered MW rate at this cell; finite |
max_mw | number | Upper bound of the delivered MW rate at this cell; finite, >= min_mw |
No two rows may share a (thermal_id, post_study_stage_index) pair. Every
post-study stage an anticipated thermal’s lead reaches must carry a matching
thermal_bounds row: the delivered commitment is bounded by that cell’s
[min_mw, max_mw] and priced at its cost_per_mwh against the extended
horizon.
{ "$schema": "https://docs.cobre-rs.dev/schemas/post_study_stages.schema.json", "stages": [ { "start_date": "2026-11-01", "duration_hours": 720.0 }, { "start_date": "2026-12-01", "duration_hours": 744.0 } ], "thermal_bounds": [ { "thermal_id": 86, "post_study_stage_index": 0, "cost_per_mwh": 210.0, "min_mw": 0.0, "max_mw": 350.0 }, { "thermal_id": 86, "post_study_stage_index": 1, "cost_per_mwh": 220.0, "min_mw": 0.0, "max_mw": 300.0 } ]}The vendored JSON Schema is at
/schemas/post_study_stages.schema.json —
see JSON Schemas for the full schema-to-file table
(this file’s row lives there).
system/ files
Section titled “system/ files”system/buses.json
Section titled “system/buses.json”Electrical bus registry. Buses are the nodes of the transmission network.
| Field | Required | Description |
|---|---|---|
buses[].id | Yes | Bus identifier (integer, unique) |
buses[].name | Yes | Human-readable bus name (string) |
buses[].operational_start_date | Yes | Calendar date the bus enters the registry’s operational history, ISO-8601 (YYYY-MM-DD). Provenance and the canonical (operational_start_date, id) ordering key (see Notation Conventions) |
buses[].deficit_segments | No | Entity-level deficit cost tiers; when absent, global defaults from penalties.json apply |
buses[].deficit_segments[].depth_mw | No | Segment MW depth; null for the final unbounded segment |
buses[].deficit_segments[].cost | No | Cost per MWh of deficit in this tier (USD/MWh) |
system/lines.json
Section titled “system/lines.json”Transmission line registry. Lines connect buses and carry power flows.
| Field | Required | Description |
|---|---|---|
lines[].id | Yes | Line identifier (integer, unique) |
lines[].name | Yes | Human-readable line name (string) |
lines[].source_bus_id | Yes | Sending-end bus ID |
lines[].target_bus_id | Yes | Receiving-end bus ID |
lines[].operational_start_date | Yes | Calendar date the line enters the registry’s operational history, ISO-8601 (YYYY-MM-DD). Provenance and the canonical (operational_start_date, id) ordering key — independent of entry_stage_id/exit_stage_id below, which alone gate commissioning |
lines[].entry_stage_id | No | Stage when line enters service; null = always exists |
lines[].exit_stage_id | No | Stage when line is decommissioned; null = never |
lines[].capacity.direct_mw | Yes | Maximum power flow in the direct direction (MW) |
lines[].capacity.reverse_mw | Yes | Maximum power flow in the reverse direction (MW) |
lines[].exchange_cost | No | Entity-level exchange cost override ($/MWh); absent = global default |
lines[].losses_percent | No | Transmission losses as percentage (default: 0.0) |
system/hydros.json
Section titled “system/hydros.json”Hydro plant registry. Each entry defines a complete hydro plant with reservoir, turbine, and optional cascade linkage.
Key fields:
| Field | Required | Description |
|---|---|---|
hydros[].id | Yes | Plant identifier (integer, unique) |
hydros[].name | Yes | Human-readable plant name |
hydros[].unit_groups | Yes | Turbine groups partitioning the plant’s generation envelope, each on its own bus (see below); an absent, null, or empty array is rejected |
hydros[].downstream_id | No | Downstream plant ID in the cascade; null = tailwater |
hydros[].travel_time_hours | No | Water travel time [hours] on the main cascade arc to downstream_id; null or 0.0 = instantaneous. When present and strictly positive, the release is delivered downstream after this delay and carried as in-transit Bellman state (see System Elements §5 and LP Formulation §5d). Requires initial_conditions.past_defluences seeding. |
hydros[].operational_start_date | Yes | Calendar date the plant enters the registry’s operational history, ISO-8601 (YYYY-MM-DD). Provenance and the canonical (operational_start_date, id) ordering key (see Notation Conventions) — independent of entry_stage_id/exit_stage_id below, which alone gate commissioning |
hydros[].entry_stage_id | No | Stage when plant enters service; null = always exists |
hydros[].exit_stage_id | No | Stage when plant is decommissioned; null = never |
hydros[].reservoir | Yes | min_storage_hm3 and max_storage_hm3 (both >= 0) |
hydros[].outflow | Yes | min_outflow_m3s and max_outflow_m3s total outflow bounds |
hydros[].generation | Yes | Generation model: model, turbine flow bounds, generation MW bounds |
hydros[].generation.model | Yes | "constant_productivity", "linearized_head", or "fpha" |
hydros[].specific_productivity_mw_per_m3s_per_m | No | Specific productivity ρ_esp [MW/(m³/s)/m]. Required for FPHA hydros that rely on VHA geometry to derive ρ_eq. |
hydros[].tailrace | No | Tailrace model: "polynomial" or "piecewise" |
hydros[].hydraulic_losses | No | Head loss model: "factor" or "constant" |
hydros[].efficiency | No | Turbine efficiency model: "constant" |
hydros[].evaporation | No | Evaporation config: coefficients_mm (12 values) and optional reference_volumes_hm3 |
hydros[].diversion | No | Diversion channel: downstream_id and max_flow_m3s |
hydros[].filling | No | Filling config: start_stage_id and filling_min_rate_m3s |
hydros[].penalties | No | Entity-level hydro penalty overrides (all fields optional, fall back to global) |
hydros[].unit_groups[] replaces the plant-level bus_id field, which is
removed — a deck still carrying it is rejected by this file’s
deny-unknown-fields contract. Every group carries its own bus, so one plant
can inject generation onto several electrical buses:
| Field | Required | Description |
|---|---|---|
id | Yes | Group identifier: dense, 0-based, unique within the plant (not globally) |
name | Yes | Human-readable group name |
bus_id | Yes | Bus where this group’s generation is injected |
min_turbined_m3s | Yes | Minimum turbined flow for this group [m³/s] |
max_turbined_m3s | Yes | Maximum turbined flow for this group [m³/s] |
min_generation_mw | Yes | Minimum generation for this group [MW] |
max_generation_mw | Yes | Maximum generation for this group [MW] |
The plant is partitioned into (hydro, bus) cells — one cell per distinct
bus_id among its unit groups. The LP holds one turbine (turbined-flow)
column and one FPHA generation column per cell, and each cell’s generation
injects at that cell’s bus; two groups sharing a bus share that cell’s
columns, so their individual split is undetermined rather than unimplemented
— every equally optimal split has no dual to distinguish it. A plant whose
groups all declare the same bus_id collapses to a single cell, matching the
pre-partition LP exactly (byte-identical).
Stage-varying, optionally per-block overrides on a group’s four declared
bounds are supplied via
constraints/hydro_unit_group_bounds.parquet.
Per-cell simulation output is
simulation/hydro_bus_generation/;
simulation/hydros/ continues to report each plant’s total, unchanged.
All fields within hydros[].penalties are optional. When a field is absent the
global default from penalties.json is used. The following fields are supported:
Field within penalties | Optional | Description |
|---|---|---|
spillage_cost | Yes | Spillage penalty ($/m³/s). |
turbined_cost | Yes | Turbined flow regularization cost; applied to every hydro’s turbine column in the LP objective. |
diversion_cost | Yes | Diversion flow penalty. |
storage_violation_below_cost | Yes | Storage below-minimum violation penalty. |
filling_target_violation_cost | Yes | Filling target violation penalty. |
turbined_violation_below_cost | Yes | Turbined flow below-minimum violation penalty. |
outflow_violation_below_cost | Yes | Total outflow below-minimum violation penalty. |
outflow_violation_above_cost | Yes | Total outflow above-maximum violation penalty. |
generation_violation_below_cost | Yes | Generation below-minimum violation penalty. |
evaporation_violation_cost | Yes | Symmetric evaporation violation penalty (applies to both directions when directional fields are absent). |
water_withdrawal_violation_cost | Yes | Symmetric water withdrawal violation penalty (applies to both directions when directional fields are absent). |
water_withdrawal_violation_pos_cost | Yes | Override cost for over-withdrawal violations (actual > target). Supersedes water_withdrawal_violation_cost for the positive direction. |
water_withdrawal_violation_neg_cost | Yes | Override cost for under-withdrawal violations (actual < target). Supersedes water_withdrawal_violation_cost for the negative direction. |
evaporation_violation_pos_cost | Yes | Override cost for over-evaporation violations (actual > modelled). Supersedes evaporation_violation_cost for the positive direction. |
evaporation_violation_neg_cost | Yes | Override cost for under-evaporation violations (actual < modelled). Supersedes evaporation_violation_cost for the negative direction. |
inflow_nonnegativity_cost | Yes | Override global inflow non-negativity penalty cost for this plant ($/m³/s). |
system/thermals.json
Section titled “system/thermals.json”Thermal plant registry. Each entry defines a dispatchable generation unit.
| Field | Required | Description |
|---|---|---|
thermals[].id | Yes | Plant identifier (integer, unique) |
thermals[].name | Yes | Human-readable plant name |
thermals[].bus_id | Yes | Bus where generation is injected |
thermals[].generation | Yes | Dispatch-bounds object with min_mw and max_mw |
thermals[].generation.min_mw | Yes | Minimum dispatch level (MW) |
thermals[].generation.max_mw | Yes | Maximum dispatch level (MW) |
thermals[].cost_per_mwh | Yes | Linear generation cost (USD/MWh) |
thermals[].operational_start_date | Yes | Calendar date the unit enters the registry’s operational history, ISO-8601 (YYYY-MM-DD). Provenance and the canonical (operational_start_date, id) ordering key — independent of entry_stage_id/exit_stage_id below, which alone gate commissioning |
thermals[].entry_stage_id | No | Stage when the unit enters service (null = present from stage 0) |
thermals[].exit_stage_id | No | Stage when the unit is decommissioned (null = never) |
thermals[].anticipated_config | No | Anticipated-dispatch config (object with lead_stages ≥ 1) |
system/non_controllable_sources.json
Section titled “system/non_controllable_sources.json”Non-controllable source (NCS) registry. Each entry defines an intermittent or
aggregate generator — wind, solar, small hydro, biomass, distributed generation —
whose available output is a scenario input rather than a free LP decision. The
file is optional; when absent, no non-controllable sources are modeled.
Per-scenario availability is supplied by the
scenarios/non_controllable_factors.json and
scenarios/non_controllable_stats.parquet inputs.
| Field | Required | Description |
|---|---|---|
non_controllable_sources[].id | Yes | Source identifier (integer, unique within the file) |
non_controllable_sources[].name | Yes | Human-readable source name (string) |
non_controllable_sources[].bus_id | Yes | Bus into which the source’s generation is injected |
non_controllable_sources[].operational_start_date | Yes | Calendar date the source enters the registry’s operational history, ISO-8601 (YYYY-MM-DD). Provenance and the canonical (operational_start_date, id) ordering key — independent of entry_stage_id/exit_stage_id below, which alone gate commissioning |
non_controllable_sources[].max_generation_mw | Yes | Maximum generation (installed capacity) [MW] |
non_controllable_sources[].allow_curtailment | No | Whether the LP may curtail this source. true (default) — dispatch within [0, max_generation_mw × availability] at the curtailment cost; false — must-run, dispatch pinned to the realized availability every scenario |
non_controllable_sources[].curtailment_cost | No | Entity-level curtailment cost override [$/MWh]; falls back to the global ncs_curtailment_cost when absent |
non_controllable_sources[].entry_stage_id | No | Stage when the source enters service; null or absent = present from stage 0 |
non_controllable_sources[].exit_stage_id | No | Stage when the source is decommissioned; null or absent = never |
A curtailable source (allow_curtailment: true) contributes a dispatch variable
bounded by [0, max_generation_mw × availability], with curtailed energy priced
at curtailment_cost (or the global default). A must-run source
(allow_curtailment: false) is pinned to its realized availability every
scenario, injecting a fixed amount with no curtailment freedom.
Minimal valid example:
{ "$schema": "https://docs.cobre-rs.dev/schemas/non_controllable_sources.schema.json", "non_controllable_sources": [ { "id": 0, "name": "Complexo Eólico Nordeste", "bus_id": 4, "operational_start_date": "2015-06-01", "max_generation_mw": 1200.0 } ]}system/pumping_stations.json
Section titled “system/pumping_stations.json”Pumping station registry. Each entry defines a pumped-storage or water-transfer installation that withdraws water from a source hydro reservoir, injects it into a destination hydro reservoir, and consumes electrical power from a bus. The file is optional; when absent, no pumping stations are modeled.
| Field | Required | Description |
|---|---|---|
pumping_stations[].id | Yes | Station identifier (integer, unique) |
pumping_stations[].name | Yes | Human-readable station name (string) |
pumping_stations[].bus_id | Yes | Bus from which electrical power is consumed |
pumping_stations[].source_hydro_id | Yes | Hydro plant from whose reservoir water is extracted |
pumping_stations[].destination_hydro_id | Yes | Hydro plant into whose reservoir water is injected |
pumping_stations[].consumption_mw_per_m3s | Yes | Power drawn per unit of pumped flow [MW/(m³/s)]; must be >= 0 |
pumping_stations[].operational_start_date | Yes | Calendar date the station enters the registry’s operational history, ISO-8601 (YYYY-MM-DD). Provenance and the canonical (operational_start_date, id) ordering key — independent of entry_stage_id/exit_stage_id below, which alone gate commissioning |
pumping_stations[].entry_stage_id | No | Stage when the station enters service; null or absent = present from stage 0 |
pumping_stations[].exit_stage_id | No | Stage when the station is decommissioned; null or absent = never |
pumping_stations[].flow | Yes | Nested object with min_m3s and max_m3s (see below) |
pumping_stations[].flow.min_m3s | Yes | Minimum pumped flow [m³/s]; must be >= 0 |
pumping_stations[].flow.max_m3s | Yes | Maximum pumped flow (installed pump capacity) [m³/s]; must be >= flow.min_m3s |
The pumped flow variable is bounded by [flow.min_m3s, flow.max_m3s] in the LP.
At each stage within [entry_stage_id, exit_stage_id), the flow appears with
a negative sign in the source reservoir water-balance row and a positive sign in
the destination reservoir water-balance row. Power consumed equals
consumption_mw_per_m3s × flow_m3s and is charged as load on the station’s bus.
Stage-varying flow bounds can be overridden via constraints/pumping_bounds.parquet.
Minimal valid example:
{ "$schema": "https://docs.cobre-rs.dev/schemas/pumping_stations.schema.json", "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 } } ]}system/energy_contracts.json
Section titled “system/energy_contracts.json”Energy contract registry. Each entry defines a bilateral energy purchase or sale obligation with a counterparty outside the modeled system. The file is optional; when absent, no contracts are modeled.
| Field | Required | Description |
|---|---|---|
contracts[].id | Yes | Contract identifier (integer, unique) |
contracts[].name | Yes | Human-readable contract name (string) |
contracts[].bus_id | Yes | Bus where power is injected (import) or withdrawn (export) |
contracts[].type | Yes | Energy flow direction: "import" or "export" |
contracts[].price_per_mwh | Yes | Contract price [monetary units/MWh]. Positive = cost (import); negative = revenue (export) |
contracts[].limits.min_mw | Yes | Minimum dispatch level [MW]; use 0.0 unless a take-or-pay floor applies |
contracts[].limits.max_mw | Yes | Maximum dispatch level [MW]; must be >= limits.min_mw |
contracts[].operational_start_date | Yes | Calendar date the contract enters the registry’s operational history, ISO-8601 (YYYY-MM-DD). Provenance and the canonical (operational_start_date, id) ordering key — independent of entry_stage_id/exit_stage_id below, which alone gate commissioning |
contracts[].entry_stage_id | No | Stage when the contract enters service; null or absent = present from stage 0 |
contracts[].exit_stage_id | No | Stage when the contract is decommissioned; null or absent = never |
At each active stage within [entry_stage_id, exit_stage_id), the LP adds one
column per block per direction bounded by [limits.min_mw, limits.max_mw]. An
import column injects +1.0 MW into the bus power-balance row; an export column
withdraws −1.0 MW. At dormant stages the column bounds are pinned to [0, 0]
and the output row is emitted with power_mw = 0. Stage-varying bounds and prices
can be overridden via constraints/contract_bounds.parquet.
Minimal valid example:
{ "$schema": "https://docs.cobre-rs.dev/schemas/energy_contracts.schema.json", "contracts": [ { "id": 0, "name": "Import base load", "bus_id": 0, "operational_start_date": "2018-01-01", "type": "import", "price_per_mwh": 200.0, "limits": { "min_mw": 0.0, "max_mw": 50.0 } }, { "id": 1, "name": "Export revenue (stage 1 only)", "bus_id": 0, "operational_start_date": "2019-06-01", "type": "export", "entry_stage_id": 1, "exit_stage_id": 2, "price_per_mwh": -150.0, "limits": { "min_mw": 0.0, "max_mw": 30.0 } } ]}system/hydro_geometry.parquet
Section titled “system/hydro_geometry.parquet”Volume-Height-Area (VHA) curves for hydro reservoirs. Required when any hydro is
configured with a computed FPHA production model (source: "computed") or with
evaporation linearization. When absent, FPHA computation and evaporation
linearization are unavailable for all plants.
4 columns, all non-nullable. Rows are sorted by (hydro_id, volume_hm3) ascending.
Multiple rows per hydro_id together constitute the VHA curve for that plant.
| Column | Type | Required | Description |
|---|---|---|---|
hydro_id | INT32 | Yes | Hydro plant ID |
volume_hm3 | DOUBLE | Yes | Total reservoir volume at this point (hm³). Non-negative and finite. |
height_m | DOUBLE | Yes | Reservoir surface elevation at this volume (m). Non-negative and finite. |
area_km2 | DOUBLE | Yes | Water surface area at this volume (km²). Non-negative and finite. |
Validation: all four columns must be present with the correct types. volume_hm3,
height_m, and area_km2 must be non-negative and finite. Monotonicity of
volume_hm3 within each hydro is enforced during Layer 5 semantic validation.
system/hydro_production_models.json
Section titled “system/hydro_production_models.json”Per-hydro production function assignment. The file is required whenever
the case contains at least one non-FPHA hydro: each non-FPHA plant must
have a matching entry that supplies either an inline
productivity_mw_per_m3s per stage range / season, or defers to
system/hydro_energy_productivity.parquet for that (hydro, stage)
coefficient.
The file contains a "production_models" array. Each entry configures one hydro
plant and is identified by a unique hydro_id. Results are loaded in
hydro_id-ascending order regardless of declaration order.
Top-level structure:
{ "$schema": "https://docs.cobre-rs.dev/schemas/production_models.schema.json", "production_models": [ ... ]}Per-hydro entry fields:
| Field | Required | Description |
|---|---|---|
hydro_id | Yes | Hydro plant ID. Must be unique within the file. |
selection_mode | Yes | How the model variant is chosen per stage: "stage_ranges" or "seasonal" |
stage_ranges mode. The model for each stage is determined by the first
matching [start_stage_id, end_stage_id] range. end_stage_id may be null
to mean “until end of horizon”.
| Field within each range | Required | Description |
|---|---|---|
start_stage_id | Yes | First stage (inclusive) to which this entry applies |
end_stage_id | Yes | Last stage (inclusive); null means open-ended |
model | Yes | Model name: "constant_productivity", "linearized_head", or "fpha" |
fpha_config | No | Required when model is "fpha". See FPHA config fields below. |
reference_volume | No | Reference operating volume V_ref, a sibling of fpha_config (not nested). Set exactly one of volume_hm3 (absolute, hm³, > 0.0) or percentile (a fraction of the operating range, [0.0, 1.0]); both or neither is rejected. Absent ⇒ the case-wide default fraction. Applies to any plant in either selection mode. See reference-volume fields below. |
productivity_mw_per_m3s | No | Positive when present; rejected on "fpha". Optional for constant_productivity and linearized_head — when omitted, supply the value via system/hydro_energy_productivity.parquet. Exactly one source per (hydro, stage) is required; both is rejected at load time. |
seasonal mode. The model for a stage is determined by its season_id.
Stages whose season is not listed use default_model.
| Field | Required | Description |
|---|---|---|
default_model | Yes | Fallback model name for unlisted seasons |
seasons | Yes | Array of season overrides: season_id, model, optional fpha_config, reference_volume, productivity_mw_per_m3s |
reference_volume fields (optional sibling of fpha_config):
| Field | Required | Description |
|---|---|---|
volume_hm3 | No | Absolute reference volume [hm³]; finite and > 0.0. Mutually exclusive with percentile. |
percentile | No | Reference volume as a fraction of the [V_min, V_max] band; finite and in [0.0, 1.0]. Mutually exclusive with volume_hm3. |
The reference operating volume V_ref feeds the FPHA backwater (downstream forebay) level and the energy-equivalent productivity ρ_eq. It is the single source of truth for V_ref: when absent, the case-wide default fraction is used.
fpha_config fields (required when model is "fpha"):
| Field | Required | Default | Description |
|---|---|---|---|
source | Yes | — | "precomputed" or "computed" |
volume_discretization_points | No | solver default | Number of volume grid points for hyperplane computation |
turbine_discretization_points | No | solver default | Number of turbine-flow grid points for hyperplane computation |
spillage_discretization_points | No | solver default | Number of spillage grid points for hyperplane computation |
max_planes_per_hydro | No | solver default | Maximum hyperplanes per plant after selection heuristic |
fitting_window | No | full range | Volume range restriction for hyperplane computation |
source: "precomputed" means the hyperplanes are loaded from
system/fpha_hyperplanes.parquet. source: "computed" means Cobre derives
them from system/hydro_geometry.parquet; in this case hydro_geometry.parquet
must be present and the computed planes are automatically written to
output/hydro_models/fpha_hyperplanes.parquet.
fitting_window fields. Absolute bounds (volume_min_hm3, volume_max_hm3)
and percentile bounds (volume_min_percentile, volume_max_percentile) are
mutually exclusive — set one pair or the other, not both.
| Field | Type | Description |
|---|---|---|
volume_min_hm3 | number | Explicit minimum volume for fitting (hm³) |
volume_max_hm3 | number | Explicit maximum volume for fitting (hm³) |
volume_min_percentile | number | Minimum as a percentile of the operating range (0–1) |
volume_max_percentile | number | Maximum as a percentile of the operating range (0–1) |
Example — hydro 0 uses computed FPHA for stages 0–24, then constant productivity:
{ "$schema": "https://docs.cobre-rs.dev/schemas/production_models.schema.json", "production_models": [ { "hydro_id": 0, "selection_mode": "stage_ranges", "stage_ranges": [ { "start_stage_id": 0, "end_stage_id": 24, "model": "fpha", "fpha_config": { "source": "computed", "volume_discretization_points": 7, "turbine_discretization_points": 15 } }, { "start_stage_id": 25, "end_stage_id": null, "model": "constant_productivity", "productivity_mw_per_m3s": 0.72 } ] } ]}Example — hydro 5 uses FPHA in season 0, linearized_head in all other seasons:
{ "production_models": [ { "hydro_id": 5, "selection_mode": "seasonal", "default_model": "linearized_head", "seasons": [ { "season_id": 0, "model": "fpha", "fpha_config": { "source": "precomputed" } } ] } ]}system/fpha_hyperplanes.parquet
Section titled “system/fpha_hyperplanes.parquet”Pre-computed FPHA hyperplane coefficients for hydros configured with
fpha_config.source: "precomputed". When absent, only "computed" source is
available.
11 columns. Rows are sorted by (hydro_id, stage_id, plane_id) ascending.
Null stage_id sorts before any non-null stage and means the plane is valid for
all stages of that hydro. One row per hyperplane; at least 3 planes are required
per (hydro_id, stage_id) group.
| Column | Type | Nullable | Description |
|---|---|---|---|
hydro_id | INT32 | No | Hydro plant ID |
stage_id | INT32 | Yes | Stage the plane applies to. null = valid for all stages |
plane_id | INT32 | No | Plane index within this hydro (and stage) |
gamma_0 | DOUBLE | No | Intercept coefficient (MW) |
gamma_v | DOUBLE | No | Volume coefficient (MW/hm³). Positive. |
gamma_q | DOUBLE | No | Turbined flow coefficient (MW per m³/s) |
gamma_s | DOUBLE | No | Spillage coefficient (MW per m³/s). Typically non-positive. |
kappa | DOUBLE | Yes | Correction factor. Defaults to 1.0 when absent or null. |
valid_v_min_hm3 | DOUBLE | Yes | Volume range minimum where this plane is valid (hm³) |
valid_v_max_hm3 | DOUBLE | Yes | Volume range maximum where this plane is valid (hm³) |
valid_q_max_m3s | DOUBLE | Yes | Maximum turbined flow where this plane is valid (m³/s) |
Validation: required columns (hydro_id, plane_id, gamma_0, gamma_v,
gamma_q, gamma_s) must be present with the correct types. Optional columns
that are present must also have the correct types. Minimum planes per
(hydro_id, stage_id) group and sign constraints on gamma_v and gamma_s
are enforced during Layer 5 semantic validation.
The file produced by output/hydro_models/fpha_hyperplanes.parquet (written when
source: "computed" is used) has this exact same 11-column schema and is
suitable for use as a future precomputed input.
system/hydro_energy_productivity.parquet
Section titled “system/hydro_energy_productivity.parquet”Optional per-plant, per-stage overrides for the energy-conversion preprocessing
layer. When present, any non-null column in a matching row replaces the value
that would otherwise be derived from VHA geometry or plant defaults. Rows with
stage_id = NULL act as per-hydro defaults and apply to all stages not covered
by a stage-specific row.
| Column | Parquet type | Nullable | Description |
|---|---|---|---|
hydro_id | INT32 | no | Hydro plant identifier |
stage_id | INT32 | yes | Stage; NULL means “applies to all stages” |
equivalent_productivity_mw_per_m3s | DOUBLE | yes | Direct ρ_eq override [MW/(m³/s)]; finite and >= 0.0 (0.0 marks a planned-outage stage) |
reference_outflow_m3s | DOUBLE | yes | Q_ref override [m³/s]; finite and >= 0.0 |
specific_productivity_mw_per_m3s_per_m | DOUBLE | yes | ρ_esp override [MW/(m³/s)/m]; finite and > 0.0 |
Validation:
hydro_idmust not be null.equivalent_productivity_mw_per_m3s, when set, must be finite and >= 0.0;0.0is accepted as a planned-outage marker.reference_outflow_m3s, when set, must be finite and >= 0.0.specific_productivity_mw_per_m3s_per_m, when set, must be finite and >= 0.0;0.0mirrors theequivalent_productivity_mw_per_m3splanned-outage marker.- A row where all three override columns are NULL is accepted.
- Duplicate
(hydro_id, stage_id)pairs are rejected during case build. - The reference operating volume V_ref is no longer an override column here; it
is declared per
(plant, stage)viareference_volumeinsystem/hydro_production_models.json. A legacyreference_volume_hm3column, if still present, is ignored (a one-time warning is emitted).
system/tailrace_curves.parquet
Section titled “system/tailrace_curves.parquet”Optional piecewise-quartic tailrace-level curves that replace the entity-level
tailrace model for any plant that has rows in this file. When a plant has rows
here, the computed-FPHA pipeline evaluates its tailrace level from these
piecewise-quartic curves — selecting the segment by downstream flow and
interpolating between backwater families at the downstream plant’s stage
reference level — instead of the tailrace model declared in hydros.json.
Plants without a row in this file keep their existing tailrace model; the file
is inert (silently skipped) when absent from the case directory.
Rows are sorted by (hydro_id, family_id, segment_id) ascending. A complete
curve for one backwater family consists of multiple rows sharing
(hydro_id, family_id).
| Column | Type | Nullable | Description |
|---|---|---|---|
hydro_id | INT32 | No | Plant whose tailrace this describes |
family_id | INT32 | No | Family index within the plant (sequential grouping key) |
downstream_reference_level_m | DOUBLE | Yes | Downstream reservoir reference level keying this family (m). null when the plant has a single family and no backwater dependency. |
segment_id | INT32 | No | Piece index within the family |
outflow_min_m3s | DOUBLE | No | Segment lower validity bound (m³/s). Non-negative. |
outflow_max_m3s | DOUBLE | No | Segment upper validity bound (m³/s). Non-negative, >= outflow_min_m3s. |
coefficient_0 | DOUBLE | No | Degree-0 polynomial coefficient. Any sign. |
coefficient_1 | DOUBLE | No | Degree-1 polynomial coefficient. Any sign. |
coefficient_2 | DOUBLE | No | Degree-2 polynomial coefficient. Any sign. |
coefficient_3 | DOUBLE | No | Degree-3 polynomial coefficient. Any sign. |
coefficient_4 | DOUBLE | No | Degree-4 polynomial coefficient. Any sign. |
The quartic is evaluated as coefficient_0 + coefficient_1*x + coefficient_2*x² + coefficient_3*x³ + coefficient_4*x⁴ where x is the downstream outflow in m³/s. Higher-degree coefficients are routinely negative in source data; all signs are accepted.
Validation rules:
- All eleven columns must be present with the correct Arrow types.
outflow_min_m3sandoutflow_max_m3smust be non-negative and finite.outflow_max_m3s >= outflow_min_m3s(segments are non-inverted).coefficient_0throughcoefficient_4must be finite.downstream_reference_level_m, when non-null, must be non-negative and finite.
scenarios/ files (Parquet)
Section titled “scenarios/ files (Parquet)”scenarios/inflow_history.parquet
Section titled “scenarios/inflow_history.parquet”Historical inflow observation windows per hydro. Every row is a window, not a
point-in-time reading: [start_date, end_date) bounds the period and
value_m3s is the mean inflow observed over it. This is the windowed layout
the loader accepts; a file still carrying the legacy single-date-per-row
layout is rejected outright, with no inference or conversion path back to the
windowed form.
| Column | Type | Required | Description |
|---|---|---|---|
hydro_id | INT32 | Yes | Hydro plant ID |
start_date | DATE | Yes | Window start, inclusive |
end_date | DATE | Yes | Window end, exclusive; after start_date |
value_m3s | DOUBLE | Yes | Mean inflow over the window (m³/s) |
Rows are sorted by (hydro_id, start_date) ascending. For a given hydro_id,
windows must not overlap; adjacent windows (start_date == previous end_date)
are accepted. value_m3s must be finite; a negative value is accepted — the
quantity is incremental inflow (a plant’s natural flow minus its upstream
plants’), so a negative window is real hydrology, and the LP prices it through
the inflow non-negativity slack. Semantic validation reports one warning per
file naming the negative count and the most-negative value’s hydro.
These windows are the default-seeding record the PAR lag chain and the
mid-period accumulator derive from, layered under initial_conditions.json’s
recent_observations conditioning wherever the two cover the same date (see
initial_conditions.json above) — as well as the
raw observations Cobre estimates inflow_seasonal_stats/inflow_ar_coefficients
from when those files are absent.
scenarios/inflow_seasonal_stats.parquet
Section titled “scenarios/inflow_seasonal_stats.parquet”PAR(p) model seasonal statistics for each (hydro plant, stage) pair. Not
consulted for a class sampled under the external scheme — there the mean and
standard deviation are derived from the external scenario file itself, so this
file (and its load/NCS counterparts) is optional when every class is external.
| Column | Type | Required | Description |
|---|---|---|---|
hydro_id | INT32 | Yes | Hydro plant ID |
stage_id | INT32 | Yes | Stage ID |
mean_m3s | DOUBLE | Yes | Seasonal mean inflow (m³/s); must be finite |
std_m3s | DOUBLE | Yes | Seasonal standard deviation (m³/s); must be >= 0 and finite |
scenarios/inflow_ar_coefficients.parquet
Section titled “scenarios/inflow_ar_coefficients.parquet”Autoregressive coefficients for the PAR(p) inflow model. The model’s innovation scale is not an input: Cobre derives it at load from the coefficients below via the periodic-ACF closure — see PAR(p) Inflow Model.
| Column | Type | Required | Description |
|---|---|---|---|
hydro_id | INT32 | Yes | Hydro plant ID |
stage_id | INT32 | Yes | Stage ID |
lag | INT32 | Yes | Lag index (1-based) |
coefficient | DOUBLE | Yes | AR coefficient for this (hydro, stage, lag) |
Columns beyond this schema are ignored — a file carrying an extra column (for example a stored innovation-scale ratio produced by another tool) loads with that column ignored outright; the derived value always wins.
scenarios/noise_openings.parquet
Section titled “scenarios/noise_openings.parquet”User-supplied backward-pass opening tree. When present, Cobre loads the opening
tree directly from this file instead of generating it internally via
generate_opening_tree(). This enables cross-tool comparison, sensitivity
analysis, and round-trip replay of a previously exported opening tree.
| Column | Type | Required | Description |
|---|---|---|---|
stage_id | INT32 | Yes | Zero-based stage index (0 to n_stages − 1) |
opening_index | UINT32 | Yes | Zero-based opening index within the stage (0 to openings_per_stage − 1) |
entity_index | UINT32 | Yes | Zero-based entity index in system dimension order (see entity ordering below) |
value | DOUBLE | Yes | Noise realization for this (stage, opening, entity) triple |
Entity ordering. The entity_index column follows the system dimension
convention: hydro entities first (sorted by canonical ID), then load buses
(sorted by canonical ID), matching the ordering used by the internal opening
tree generator. Violating this convention causes silent value misassignment
because the file stores indices only, not entity identifiers.
Validation rules. The loader checks three conditions and raises a hard error on failure:
- Dimension mismatch — the number of distinct
entity_indexvalues must equaln_hydros + n_load_buses. - Stage count mismatch — the number of distinct
stage_idvalues must equal the configured number of study stages. - Missing opening indices — for each stage, every opening index from 0 to
openings_per_stage − 1must be present for every entity. Gaps are not permitted; partial-stage override is not supported.
The total row count must equal n_stages × openings_per_stage × (n_hydros + n_load_buses).
See the noise_openings.rs module for the full schema and validation
rules, and the User-Supplied Opening Trees section of the Stochastic
Modeling guide for usage instructions.
scenarios/ files (JSON)
Section titled “scenarios/ files (JSON)”scenarios/load_factors.json
Section titled “scenarios/load_factors.json”Per-bus, per-stage, per-block load scaling factors. When present, each factor multiplies the stochastic load demand realization at the specified bus for the specified block. This allows you to model time-of-day or seasonal patterns in load shape without changing the underlying statistical model.
When this file is absent, all load factors default to 1.0. When a
(bus_id, stage_id) pair is absent from the file, its factors also default
to 1.0 for every block.
JSON structure:
{ "load_factors": [ { "bus_id": 0, "stage_id": 0, "block_factors": [ { "block_id": 0, "factor": 0.8 }, { "block_id": 1, "factor": 1.2 } ] } ]}Fields per entry:
| Field | Type | Description |
|---|---|---|
bus_id | integer | Bus entity ID. Must refer to a bus defined in system/buses.json. |
stage_id | integer | Study stage index. Must be a valid stage ID from stages.json. |
block_factors | array | Array of { block_id, factor } pairs for each load block. |
block_factors entry fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
block_id | integer | Must be a valid block for stage | Zero-based block index within the stage. |
factor | number | > 0, finite | Multiplier applied to the stochastic load realization (MW) at this bus and block. |
Effect: load_rhs = mean_mw * stochastic_noise_factor * block_factor.
A factor of 1.0 leaves the load unchanged. Values less than 1.0 reduce load;
values greater than 1.0 increase it.
scenarios/non_controllable_factors.json
Section titled “scenarios/non_controllable_factors.json”Per-NCS, per-stage, per-block scaling factors for non-controllable source (NCS)
available generation. When present, each factor multiplies the available
generation bound from constraints/ncs_bounds.parquet for the specified block.
This allows modeling of intra-stage availability patterns such as diurnal solar
irradiance profiles or wind speed variations across load blocks.
When this file is absent, all NCS block factors default to 1.0. When a
(ncs_id, stage_id) pair is absent from the file, its factors default to 1.0
for every block.
JSON structure:
{ "non_controllable_factors": [ { "ncs_id": 0, "stage_id": 0, "block_factors": [ { "block_id": 0, "factor": 0.3 }, { "block_id": 1, "factor": 0.8 } ] } ]}Fields per entry:
| Field | Type | Description |
|---|---|---|
ncs_id | integer | NCS entity ID. Must refer to a source in system/non_controllable_sources.json. |
stage_id | integer | Study stage index. Must be a valid stage ID from stages.json. |
block_factors | array | Array of { block_id, factor } pairs for each load block. |
block_factors entry fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
block_id | integer | Must be a valid block for stage | Zero-based block index within the stage. |
factor | number | >= 0, finite | Multiplier applied to the stage available generation bound for this block. |
Effect: available_mw_block = available_generation_mw * block_factor.
A factor of 1.0 leaves the bound unchanged. A factor of 0.0 sets availability
to zero for that block (complete generation unavailability).
scenarios/non_controllable_stats.parquet
Section titled “scenarios/non_controllable_stats.parquet”Per-NCS, per-stage stochastic availability model. Each row provides the mean
and standard deviation of the availability factor for one NCS entity at one
stage. The noise transform produces: A_r = max_gen × clamp(mean + std × η, 0, 1).
| Column | Type | Required | Description |
|---|---|---|---|
ncs_id | INT32 | Yes | Non-controllable source ID |
stage_id | INT32 | Yes | Stage ID (0-based) |
mean | DOUBLE | Yes | Mean availability factor in [0, 1] |
std | DOUBLE | Yes | Standard deviation of availability factor (>= 0) |
When absent, NCS availability is deterministic from constraints/ncs_bounds.parquet
or the entity’s max_generation_mw.
constraints/ files (Parquet)
Section titled “constraints/ files (Parquet)”All bounds Parquet files use sparse storage: only (entity_id, stage_id) pairs
that differ from the base entity-level value need rows. Absent rows use the
entity-level value unchanged.
constraints/thermal_bounds.parquet
Section titled “constraints/thermal_bounds.parquet”Stage-varying generation and cost overrides for thermal plants.
| Column | Type | Required | Description |
|---|---|---|---|
thermal_id | INT32 | Yes | Thermal plant ID |
stage_id | INT32 | Yes | Stage ID |
min_generation_mw | DOUBLE | No | Minimum generation override (MW) |
max_generation_mw | DOUBLE | No | Maximum generation override (MW) |
cost_per_mwh | DOUBLE | No | Dispatch cost override (USD/MWh) |
block_id | INT32 (nullable) | No | Zero-based block index within the stage, selecting one block’s min_generation_mw/max_generation_mw override. null applies at the stage level. A non-null cost_per_mwh on the same row, or any block_id on a thermal declaring anticipated_config, is rejected at validation. |
cost_per_mwh is deliberately not block-eligible, unlike
contract_bounds.price_per_mwh below — commitment is a stage-level decision,
so a per-block dispatch cost has nothing to attach to.
constraints/hydro_bounds.parquet
Section titled “constraints/hydro_bounds.parquet”Stage-varying operational bound overrides for hydro plants.
| Column | Type | Required | Description |
|---|---|---|---|
hydro_id | INT32 | Yes | Hydro plant ID |
stage_id | INT32 | Yes | Stage ID |
min_turbined_m3s | DOUBLE | No | Minimum turbined flow (m³/s) |
max_turbined_m3s | DOUBLE | No | Maximum turbined flow (m³/s) |
min_storage_hm3 | DOUBLE | No | Minimum reservoir storage (hm³) |
max_storage_hm3 | DOUBLE | No | Maximum reservoir storage (hm³) |
min_outflow_m3s | DOUBLE | No | Minimum total outflow (m³/s) |
max_outflow_m3s | DOUBLE | No | Maximum total outflow (m³/s) |
min_generation_mw | DOUBLE | No | Minimum generation (MW) |
max_generation_mw | DOUBLE | No | Maximum generation (MW) |
max_diversion_m3s | DOUBLE | No | Maximum diversion flow (m³/s) |
min_diversion_m3s | DOUBLE | No | Minimum diversion flow (m³/s); requires a declared diversion channel |
min_spillage_m3s | DOUBLE | No | Minimum spillage flow (m³/s) |
max_spillage_m3s | DOUBLE | No | Maximum spillage flow (m³/s); >= min_spillage_m3s |
filling_min_rate_m3s | DOUBLE | No | Filling minimum-rate override (m³/s) |
water_withdrawal_m3s | DOUBLE | No | Water withdrawal (m³/s) |
block_id | INT32 (nullable) | No | Zero-based block index within the stage, selecting one block’s turbined/outflow/generation/diversion/spillage override. null applies at the stage level. A non-null min_storage_hm3/max_storage_hm3/filling_min_rate_m3s/water_withdrawal_m3s value on the same row is rejected at validation — those four stay stage-level only, with no per-block variant. |
A min_diversion_m3s override on a hydro that declares no diversion
channel is rejected at validation — with no channel, the diversion column is
pinned [0, 0], making a positive floor infeasible. min_diversion_m3s,
min_spillage_m3s, and max_spillage_m3s must each be non-negative, and a
row combining min_spillage_m3s and max_spillage_m3s with `min_spillage_m3s
max_spillage_m3s` is rejected. See Error Codes for the full failure-mode catalog.
constraints/hydro_unit_group_bounds.parquet
Section titled “constraints/hydro_unit_group_bounds.parquet”Stage-varying, optionally per-block bound overrides for a hydro plant’s
declared unit groups (hydros[].unit_groups[]; see
system/hydros.json above). A row overrides one of a
group’s four declared bounds for one (hydro_id, hydro_unit_group_id, stage_id), optionally narrowed to a single block; a group with no override
row reads its declared value. Group id is dense and 0-based within its
plant, so hydro_unit_group_id addresses the group by that declared id.
| Column | Type | Required | Description |
|---|---|---|---|
hydro_id | INT32 | Yes | Hydro plant ID |
hydro_unit_group_id | INT32 | Yes | Unit group ID (unit_groups[].id), unique within the plant |
stage_id | INT32 | Yes | Stage ID |
min_turbined_m3s | DOUBLE | No | Minimum turbined flow override for this group (m³/s) |
max_turbined_m3s | DOUBLE | No | Maximum turbined flow override for this group (m³/s) |
min_generation_mw | DOUBLE | No | Minimum generation override for this group (MW) |
max_generation_mw | DOUBLE | No | Maximum generation override for this group (MW) |
block_id | INT32 (nullable) | No | Zero-based block index within the stage, selecting one block’s override. null applies at the stage level; all four bound columns are block-eligible. |
constraints/line_bounds.parquet
Section titled “constraints/line_bounds.parquet”Stage-varying, absolute-MW flow capacity overrides for transmission lines.
| Column | Type | Required | Description |
|---|---|---|---|
line_id | INT32 | Yes | Transmission line ID |
stage_id | INT32 | Yes | Stage ID |
direct_mw | DOUBLE | No | Direct-flow capacity override (MW) |
reverse_mw | DOUBLE | No | Reverse-flow capacity override (MW) |
block_id | INT32 (nullable) | No | Zero-based block index within the stage, selecting one block’s direct_mw/reverse_mw override. null applies at the stage level. |
Per-block line capacity is declared here directly, as absolute MW.
constraints/exchange_factors.json, which scaled a stage-level capacity by a
per-block factor, is removed; a study still carrying that file is rejected by
name. The old factor form’s effect on a given (line, stage, block) is
reproduced by direct_mw = base_capacity × direct_factor (and the reverse
equivalent). Absolute MW also expresses direct_mw = 0.0 — a line fully
closed in one block — which the previous factor, constrained strictly
positive, could never represent.
constraints/pumping_bounds.parquet
Section titled “constraints/pumping_bounds.parquet”Stage-varying flow bounds for pumping stations.
| Column | Type | Required | Description |
|---|---|---|---|
pumping_station_id | INT32 | Yes | Pumping station ID |
stage_id | INT32 | Yes | Stage ID |
min_m3s | DOUBLE | No | Minimum pumping flow (m³/s) |
max_m3s | DOUBLE | No | Maximum pumping flow (m³/s) |
block_id | INT32 (nullable) | No | Zero-based block index within the stage, selecting one block’s min_m3s/max_m3s override. null applies at the stage level. |
constraints/contract_bounds.parquet
Section titled “constraints/contract_bounds.parquet”Stage-varying power and price overrides for energy contracts.
| Column | Type | Required | Description |
|---|---|---|---|
contract_id | INT32 | Yes | Energy contract ID |
stage_id | INT32 | Yes | Stage ID |
min_mw | DOUBLE | No | Minimum power (MW) |
max_mw | DOUBLE | No | Maximum power (MW) |
price_per_mwh | DOUBLE | No | Price override (USD/MWh) |
block_id | INT32 (nullable) | No | Zero-based block index within the stage, selecting one block’s min_mw/max_mw/price_per_mwh override. null applies at the stage level. |
price_per_mwh is block-eligible — a study’s simulation cost path honors a
per-block contract price — deliberately asymmetric with
thermal_bounds.cost_per_mwh above, which is not.
constraints/ncs_bounds.parquet
Section titled “constraints/ncs_bounds.parquet”Stage-varying available generation bounds for non-controllable sources. Uses
sparse storage: only (ncs_id, stage_id) pairs that differ from the base
entity-level value need rows. Absent rows keep the entity’s declared
available_generation_mw unchanged.
| Column | Type | Required | Description |
|---|---|---|---|
ncs_id | INT32 | Yes | Non-controllable source ID |
stage_id | INT32 | Yes | Stage ID |
available_generation_mw | DOUBLE | Yes | Maximum available generation for this stage (MW). Must be >= 0. |
The per-block available generation bound in the LP is:
available_mw_block = available_generation_mw * block_factor, where
block_factor comes from scenarios/non_controllable_factors.json
(default 1.0 when absent).
constraints/generic_constraints.json
Section titled “constraints/generic_constraints.json”User-defined linear constraints, added to every stage’s LP alongside the built-in constraint set. The file is optional; when absent, no generic constraints are added.
Top-level structure:
{ "constraints": [ { "id": 1, "name": "cap_ant_t1", "expression": "anticipated_decision(2)", "slack": { "enabled": false } } ]}Per-entry fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | Constraint identifier. Must be unique within the file. |
name | string | Yes | Short name used in reports and log output. |
expression | string | Yes | Expression string over variable references and constants (see below). |
description | string or null | No | Optional human-readable description. |
slack | object | Yes | Slack variable configuration: enabled (boolean) and penalty (number; required when enabled is true). |
There is no sense field. A constraint’s shape (floor/cap/band/equality)
is derived from which of bound_lower/bound_upper are present on its
activation-grid rows in constraints/generic_constraint_bounds.parquet below,
not authored per-entry — see
Generic Constraints: Interval and shape derivation
for the full derivation and the optional inline relational-RHS shorthand.
Variable references. An expression is a linear combination of
coefficient-scaled variable references, e.g.
"2.5 * thermal_generation(5) - hydro_generation(3)". A reference takes the
form var_name(entity_id[, block_id][, bus=bus_id]) — an optional positional
block argument, then an optional named bus= selector, in that order.
hydro_turbined and hydro_generation are the only two variables accepting
the bus= selector, letting a constraint address one (hydro, bus) cell of a
plant split across several unit groups (see
hydros[].unit_groups[] above): hydro_turbined(5, bus=2) and hydro_generation(5, bus=2) resolve to the LP column for hydro
5’s cell on bus 2 — 2 must be a bus actually carried by one of hydro 5’s
unit groups, or the reference is rejected at load. Omitting bus= sums over
the plant’s cells, preserving the plant-level meaning every other variable
carries; a plant with only one cell needs no selector. The positional block
argument, when present, precedes the named selector:
hydro_turbined(5, 0, bus=2) addresses block 0 of that cell.
To constrain an anticipated thermal’s commitment, use
anticipated_decision(N) (stage-level scalar, no block index); see
system/thermals.json above for anticipated_config.
constraints/generic_constraint_bounds.parquet
Section titled “constraints/generic_constraint_bounds.parquet”Stage-varying, optionally per-block RHS bound overrides for generic
constraints declared in constraints/generic_constraints.json. This table
is the activation grid: a constraint is active at a given (stage[, block]) if and only if a row exists for it here, regardless of whether that
row supplies any bound value itself (an inline affine remainder can supply the
value instead — see below).
| Column | Type | Required | Description |
|---|---|---|---|
constraint_id | INT32 | Yes | Generic constraint ID |
stage_id | INT32 | Yes | Stage ID |
block_id | INT32 (nullable) | No | Zero-based block index within the stage. null applies to all blocks of the given stage. |
bound_lower | DOUBLE (nullable) | No | Lower interval endpoint. null when the constraint is unbounded below at this cell. |
bound_upper | DOUBLE (nullable) | No | Upper interval endpoint. null when the constraint is unbounded above at this cell. |
The pair derives the constraint’s shape: bound_lower alone is floor,
bound_upper alone is cap, both present and different is band, both
present and bit-identical is equality. A row with both endpoints null
is rejected unless the constraint’s own inline affine remainder (an inline
<=/>=/== on the constraint’s expression, or a per_stage_block
generic_parameters.json reference) supplies at least one endpoint for that
cell — the row still activates the cell, but contributes no value. A
constraint with no rows at all here is rejected outright: the activation grid
is mandatory even when every value comes from a remainder. See
Generic Constraints: The activation grid
for the full validation rules and worked examples.
constraints/generic_parameters.json
Section titled “constraints/generic_parameters.json”Named scalar parameters that can be referenced from generic-constraint coefficient
expressions using the @name sigil. The file is optional; when absent, no
parameters are loaded and any @name token in a constraint expression causes a
load error.
Top-level structure:
{ "$schema": "https://docs.cobre-rs.dev/schemas/generic_parameters.schema.json", "scalar_parameters": [ { "id": 1, "name": "rho_eq_h1", "kind": "computed", "computed_spec": { "tag": "equivalent_productivity", "hydro_id": 1 } } ]}The top-level JSON key stays "scalar_parameters" — only the file’s location
and name changed.
Per-entry fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | Unique parameter identifier (int32) |
name | string | Yes | Unique parameter name (non-empty, no leading/trailing whitespace) |
kind | string | Yes | One of constant, per_stage, seasonal, computed, per_stage_block |
value | number | kind dep | Finite f64 value. Required for constant. Absent otherwise. |
values | array | kind dep | Array of [index, value] pairs. Required for per_stage and seasonal. |
computed_spec | object | kind dep | {"tag": "<variant>", "hydro_id": <int>}. Required for computed. |
block_values | array | kind dep | Array of [stage_id, block_id, value] triples. Required for per_stage_block; each (stage_id, block_id) pair within an entry must be unique. |
computed_spec tag values:
tag | Description |
|---|---|
equivalent_productivity | Equivalent productivity ρ_eq |
accumulated_productivity | Accumulated cascade productivity ρ_acum |
reference_volume | Reference reservoir volume V_ref |
reference_turbine | Reference turbined flow Q_ref |
min_storage | Minimum operational storage V_min |
max_storage | Maximum operational storage V_max |
specific_productivity | Specific productivity ρ_esp |
Validation:
idvalues must be unique across all entries.namevalues must be unique (case-sensitive), non-empty, and have no leading or trailing whitespace.kindmust be exactly one of the five legal values.- For
per_stage:valuespairs must have contiguousstage_idkeys starting at 0; duplicates and gaps are rejected. - For
seasonal:season_idkeys within an entry must be unique; duplicates are rejected. - For
computed:computed_specmust be present with a validtagand integerhydro_id. The referenced hydro must exist inhydros.json. - For
per_stage_block:block_valuesmust be present, and each(stage_id, block_id)pair within an entry must be unique; duplicates are rejected. - Unknown JSON fields on any entry are rejected immediately.
See Generic Constraints: the five parameter kinds
for the authoring narrative, the per_stage_block worked example, and a note
on the vendored schema’s stale kind description.
Penalty override files
Section titled “Penalty override files”All penalty override files use sparse storage. Only rows for (entity_id, stage_id)
pairs where the penalty differs from the entity-level or global default are required.
All penalty values must be strictly positive (> 0.0) and finite.
constraints/penalty_overrides_bus.parquet
Section titled “constraints/penalty_overrides_bus.parquet”| Column | Type | Required | Description |
|---|---|---|---|
bus_id | INT32 | Yes | Bus ID |
stage_id | INT32 | Yes | Stage ID |
excess_cost | DOUBLE | No | Excess injection cost override (USD/MWh) |
Note: Bus deficit segments are not stage-varying. Only excess_cost can be
overridden per stage for buses.
constraints/penalty_overrides_line.parquet
Section titled “constraints/penalty_overrides_line.parquet”| Column | Type | Required | Description |
|---|---|---|---|
line_id | INT32 | Yes | Transmission line ID |
stage_id | INT32 | Yes | Stage ID |
exchange_cost | DOUBLE | No | Exchange flow cost override (USD/MWh) |
constraints/penalty_overrides_hydro.parquet
Section titled “constraints/penalty_overrides_hydro.parquet”| Column | Type | Required | Description |
|---|---|---|---|
hydro_id | INT32 | Yes | Hydro plant ID |
stage_id | INT32 | Yes | Stage ID |
spillage_cost | DOUBLE | No | Spillage penalty override |
turbined_cost | DOUBLE | No | Turbined cost override |
diversion_cost | DOUBLE | No | Diversion penalty override |
storage_violation_below_cost | DOUBLE | No | Storage below-minimum violation override |
filling_target_violation_cost | DOUBLE | No | Filling target violation override |
turbined_violation_below_cost | DOUBLE | No | Turbined below-minimum violation override |
outflow_violation_below_cost | DOUBLE | No | Outflow below-minimum violation override |
outflow_violation_above_cost | DOUBLE | No | Outflow above-maximum violation override |
generation_violation_below_cost | DOUBLE | No | Generation below-minimum violation override |
evaporation_violation_cost | DOUBLE | No | Evaporation violation override |
water_withdrawal_violation_cost | DOUBLE | No | Water withdrawal violation override |
constraints/penalty_overrides_ncs.parquet
Section titled “constraints/penalty_overrides_ncs.parquet”| Column | Type | Required | Description |
|---|---|---|---|
ncs_id | INT32 | Yes | Non-controllable source ID |
stage_id | INT32 | Yes | Stage ID |
curtailment_cost | DOUBLE | No | Curtailment penalty override (USD/MWh) |