Pular para o conteúdo

Case Directory Format

Este conteúdo não está disponível em sua língua ainda.

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.

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)
FileFormatRequiredDescription
config.jsonJSONYesSolver configuration
penalties.jsonJSONYesGlobal penalty defaults
stages.jsonJSONYesStage sequence and policy graph
initial_conditions.jsonJSONYesInitial reservoir storage
post_study_stages.jsonJSONNoPost-study boundary calendar and per-thermal delivery bounds
system/buses.jsonJSONYesElectrical bus registry
system/lines.jsonJSONYesTransmission line registry
system/hydros.jsonJSONYesHydro plant registry
system/thermals.jsonJSONYesThermal plant registry
system/non_controllable_sources.jsonJSONNoIntermittent source registry
system/pumping_stations.jsonJSONNoPumping station registry
system/energy_contracts.jsonJSONNoBilateral energy contract registry
system/hydro_geometry.parquetParquetNoReservoir geometry elevation tables
system/hydro_production_models.jsonJSONNoFPHA production function configs
system/fpha_hyperplanes.parquetParquetNoFPHA hyperplane coefficients
system/hydro_energy_productivity.parquetParquetNoPer-plant, per-stage energy-conversion overrides
system/tailrace_curves.parquetParquetNoPiecewise-quartic tailrace curves with backwater families
scenarios/inflow_history.parquetParquetNoHistorical inflow time series
scenarios/inflow_seasonal_stats.parquetParquetNoPAR model seasonal statistics
scenarios/inflow_ar_coefficients.parquetParquetNoPAR autoregressive coefficients
scenarios/external_inflow_scenarios.parquetParquetNoExternal inflow scenario realizations (hydro_id, stage_id, scenario_id, value_m3s)
scenarios/external_load_scenarios.parquetParquetNoExternal load scenario realizations (bus_id, stage_id, scenario_id, value_mw)
scenarios/external_ncs_scenarios.parquetParquetNoExternal NCS scenario realizations (ncs_id, stage_id, scenario_id, availability_factor)
scenarios/load_seasonal_stats.parquetParquetNoLoad model seasonal statistics
scenarios/load_factors.jsonJSONNoLoad scaling factors per bus/stage
scenarios/non_controllable_factors.jsonJSONNoNCS block scaling factors per source/stage
scenarios/non_controllable_stats.parquetParquetNoNCS stochastic availability factors
scenarios/correlation.jsonJSONNoCross-series correlation model
scenarios/noise_openings.parquetParquetNoUser-supplied backward-pass opening tree
constraints/thermal_bounds.parquetParquetNoStage-varying thermal generation bounds
constraints/hydro_bounds.parquetParquetNoStage-varying hydro operational bounds
constraints/hydro_unit_group_bounds.parquetParquetNoStage-varying, optionally per-block hydro unit group bounds
constraints/line_bounds.parquetParquetNoStage-varying line flow capacity
constraints/pumping_bounds.parquetParquetNoStage-varying pumping flow bounds
constraints/contract_bounds.parquetParquetNoStage-varying contract power bounds
constraints/ncs_bounds.parquetParquetNoStage-varying NCS available generation bounds
constraints/generic_constraints.jsonJSONNoUser-defined LP constraints
constraints/generic_constraint_bounds.parquetParquetNoGeneric constraint RHS bounds
constraints/generic_parameters.jsonJSONNoNamed parameters for constraint expressions
constraints/penalty_overrides_bus.parquetParquetNoStage-varying bus excess cost
constraints/penalty_overrides_line.parquetParquetNoStage-varying line exchange cost
constraints/penalty_overrides_hydro.parquetParquetNoStage-varying hydro penalty costs
constraints/penalty_overrides_ncs.parquetParquetNoStage-varying NCS curtailment cost

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:

SectionTypeDefaultPurpose
$schemastringnullJSON Schema URI for editor validation (ignored during processing)
modelingobject{}Inflow non-negativity treatment; objective cost-scale conditioning
trainingobjectrequiredIteration count, stopping rules, cut selection, parallelism, solver
estimationobject{}PAR(p) model estimation settings (max order, selection criterion)
upper_bound_evaluationobject{}Inner approximation upper-bound settings
policyobjectfresh modePolicy directory path and warm-start mode
simulationobjectdisabledPost-training simulation settings
exportsobjectall enabledOutput file selection flags

modeling section:

FieldTypeDefaultDescription
modeling.inflow_non_negativity.methodstring"penalty"How to handle negative modelled inflows. One of "none", "penalty", "truncation", "truncation_with_penalty"
modeling.cost_scale_factornumber1_000_000.0Divisor 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):

FieldTypeDefaultDescription
training.selectionobjectrequiredScenario-selection method — sampled{forward_passes} or enumerated{} (see below); a missing training.selection is a hard load error
training.stopping_rulesarrayrequiredAt least one stopping rule entry; must include an iteration_limit rule
training.stopping_modestring"any"How multiple rules combine: "any" (stop when any triggers) or "all" (stop when all trigger)
training.enabledbooleantrueWhen false, skip training and proceed directly to simulation
training.tree_seedinteger or nullnullRandom seed for reproducible noise generation (see Seed resolution)
training.scenario_sourceobject or nullnullPer-class sampling scheme for the training forward pass (see below)
training.parallelismobject or null{backward_scheduler: trial_point}Backward-pass scheduler selection; see Configuration
training.solverobject or nullnullLP 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.

FieldTypeRequiredDescription
training.selection.methodstringYes"sampled" or "enumerated"
training.selection.forward_passesintegersampled onlyNumber 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).

FieldTypeDefaultDescription
training.scenario_source.inflow.schemestring"in_sample"Inflow sampling scheme: "in_sample", "historical", "external", or "out_of_sample"
training.scenario_source.load.schemestring"in_sample"Load sampling scheme: "in_sample", "historical", "external", or "out_of_sample"
training.scenario_source.ncs.schemestring"in_sample"NCS sampling scheme: "in_sample", "historical", "external", or "out_of_sample"
training.scenario_source.historical_yearsarray or objectnullYears eligible as inflow replay windows. List ([2010, 2015]) or range ({"from": 2010, "to": 2023})

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_seed is 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_seed is absent or null, 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_seed explicitly 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:

TypeRequired fieldsStops when
iteration_limitlimit: integerIteration count reaches limit
time_limitseconds: numberWall-clock time exceeds seconds
bound_stallingiterations: integer, tolerance: numberLower bound improvement falls below tolerance over iterations window
simulationreplications, period, bound_window, distance_tol, bound_tolBoth 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.

FieldTypeDefaultDescription
row_activity_tolerancenumber0.0Minimum dual multiplier for a row to count as binding
max_active_per_stageintegernullHard cap on active rows per stage; null = no cap
selectionobjectnullActive method and its parameters; method is one of "level1", "lml1", "domination", "dynamic"

upper_bound_evaluation section:

FieldTypeDefaultDescription
enabledbooleannullEnable vertex-based inner approximation
initial_iterationintegernullFirst iteration to compute the upper bound
interval_iterationsintegernullIterations between upper-bound evaluations
lipschitz.modestringnullLipschitz constant computation mode: "auto"
lipschitz.fallback_valuenumbernullFallback when automatic computation fails
lipschitz.scale_factornumbernullMultiplicative safety margin

policy section:

FieldTypeDefaultDescription
pathstring"./policy"Directory for policy data (cuts, states, vertices, basis)
modestring"fresh"Initialization mode: "fresh", "warm_start", or "resume"
boundaryobject or nullnullTerminal boundary cut config: path (string) + source_stage (int)
checkpointing.enabledbooleannullEnable periodic checkpointing
checkpointing.initial_iterationintegernullFirst iteration to write a checkpoint
checkpointing.interval_iterationsintegernullIterations between checkpoints
checkpointing.store_basisbooleannullInclude LP basis in checkpoints
checkpointing.compressbooleannullCompress checkpoint files

simulation section:

FieldTypeDefaultDescription
enabledbooleanfalseEnable post-training simulation
simulation.selectionobject or nullnullScenario-selection method — sampled{num_scenarios} or enumerated{} (see below); absent resolves to the default sampled count (2000)
io_channel_capacityinteger64Channel capacity between simulation and I/O writer threads
simulation.scenario_sourceobject or nullnullPer-class sampling scheme for the simulation pass (see below)
simulation.scenario_source.inflow.schemestring"in_sample"Inflow sampling scheme: "in_sample", "historical", "external", or "out_of_sample"
simulation.scenario_source.load.schemestring"in_sample"Load sampling scheme: "in_sample", "historical", "external", or "out_of_sample"
simulation.scenario_source.ncs.schemestring"in_sample"NCS sampling scheme: "in_sample", "historical", "external", or "out_of_sample"
simulation.scenario_source.historical_yearsarray or objectnullYears 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.

FieldTypeRequiredDescription
simulation.selection.methodstringYes (if present)"sampled" or "enumerated"
simulation.selection.num_scenariosintegersampled onlyNumber of simulation trajectories to draw (>= 0). Rejected under "enumerated"

exports section:

FieldTypeDefaultDescription
statesbooleanfalseExport visited forward-pass trial points to the policy checkpoint
stochasticbooleanfalseExport 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 }]
}
}

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).

SectionFieldTypeDescription
busdeficit_segmentsarrayPiecewise-linear deficit cost tiers
busdeficit_segments[].depth_mwnumber or nullSegment depth (MW); null for the final unbounded segment
busdeficit_segments[].costnumberCost per MWh of deficit in this tier (USD/MWh)
busexcess_costnumberCost per MWh of excess injection (USD/MWh)
lineexchange_costnumberCost per MWh of inter-bus exchange flow (USD/MWh)
hydrospillage_costnumberSpillage penalty
hydroturbined_costnumberTurbined flow regularization cost (applied to every hydro)
hydrodiversion_costnumberDiversion flow penalty
hydrostorage_violation_below_costnumberStorage below-minimum violation penalty
hydrofilling_target_violation_costnumberFilling target violation penalty
hydroturbined_violation_below_costnumberTurbined flow below-minimum violation penalty
hydrooutflow_violation_below_costnumberTotal outflow below-minimum violation penalty
hydrooutflow_violation_above_costnumberTotal outflow above-maximum violation penalty
hydrogeneration_violation_below_costnumberGeneration below-minimum violation penalty
hydroevaporation_violation_costnumberSymmetric evaporation violation penalty
hydroevaporation_violation_pos_costnumber or nullOptional over-evaporation override; supersedes evaporation_violation_cost for the positive direction. Omitted = symmetric value
hydroevaporation_violation_neg_costnumber or nullOptional under-evaporation override; supersedes evaporation_violation_cost for the negative direction. Omitted = symmetric value
hydrowater_withdrawal_violation_costnumberSymmetric water withdrawal violation penalty
hydrowater_withdrawal_violation_pos_costnumber or nullOptional over-withdrawal override; supersedes water_withdrawal_violation_cost for the positive direction. Omitted = symmetric value
hydrowater_withdrawal_violation_neg_costnumber or nullOptional under-withdrawal override; supersedes water_withdrawal_violation_cost for the negative direction. Omitted = symmetric value
hydroinflow_nonnegativity_costnumber or nullOptional inflow non-negativity penalty. Omitted = default 1000.0
non_controllable_sourcecurtailment_costnumberCurtailment 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 }
}

Defines the temporal structure of the study: stage sequence, block decomposition, and policy graph horizon type.

Top-level fields:

FieldRequiredDescription
policy_graphYesHorizon type, annual discount rate, and stage transitions/nodes (see below)
stagesYesArray of study stage definitions
season_definitionsNoSeason labeling for seasonal model alignment
pre_study_stagesNoPre-study stages for AR model warm-up (negative IDs)

policy_graph sub-object:

FieldRequiredDescription
typeYesHorizon type. Only "finite_horizon" is supported; "cyclic" is reserved and rejected at load (no engine consumer today)
annual_discount_rateYesGlobal annual discount rate (>= 0.0)
nodes[]NoPolicy-graph nodes (see below). Absent ⇒ the graph is a stage chain and transitions[] endpoints are read directly as stage ids
transitions[]NoStage 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:

FieldRequiredDescription
idYesUnique node id, referenced by transitions[] endpoints when nodes[] is declared
stage_idYesDeclared study-stage id this node sits at; resolved against the study stages, never an array index
scenario_idNoPer-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 }]
labelNoOptional human-readable label

policy_graph.transitions[] entry fields:

FieldRequiredDescription
source_idYesSource endpoint: a node id when policy_graph.nodes[] is non-empty, a stage id otherwise
target_idYesTarget endpoint: a node id when policy_graph.nodes[] is non-empty, a stage id otherwise
probabilityYesTransition probability
annual_discount_rate_overrideNoOptional per-transition discount rate override; absent uses policy_graph.annual_discount_rate

stages[] entry fields:

FieldRequiredDescription
idYesStage identifier (non-negative integer, unique)
start_dateYesISO 8601 date (e.g., "2024-01-01")
end_dateYesISO 8601 date; must be after start_date
blocksYesArray of load blocks (id, name, hours)
num_openingsYesNumber 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_idNoReference to a season in season_definitions
block_modeNoBlock execution mode: "parallel" (default) or "chronological"
state_variablesNoCut 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_measureNoPer-stage risk measure: "expectation" or CVaR config
sampling_methodNoNoise 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.

FieldRequiredDescription
cycle_typeYes"monthly", "weekly", or "custom"
seasonsYesArray of season entries (see below)

season_definitions.seasons[] entry fields:

FieldRequiredDescription
idYesSeason identifier (0-based integer, unique within the season map)
labelYesHuman-readable label (e.g., "January", "Q1", "Wet Season")
month_startYesCalendar month where the season starts (1–12)
day_startCustom onlyCalendar day where the season starts (1–31). Required for custom cycle type.
month_endCustom onlyCalendar month where the season ends (1–12). Required for custom cycle type.
day_endCustom onlyCalendar 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). Only id, label, and month_start are needed per entry.
  • "weekly" — seasons map to ISO calendar weeks (52 seasons). Only id, label, and month_start are needed per entry.
  • "custom" — user-defined date ranges with explicit month_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 reservoir storage, pre-study releases already in transit on declared travel-time arcs, and recent observations at the start of the study.

FieldRequiredDescription
storageYesArray of { "hydro_id": integer, "value_hm3": number } entries for operating hydros
filling_storageYesArray of { "hydro_id": integer, "value_hm3": number } entries for filling hydros
past_defluencesNoArray of windowed pre-study release records seeding the in-transit buckets of a declared travel-time arc (see below)
recent_observationsNoArray of observed inflow entries for mid-season study starts (see below)
past_anticipated_commitmentsNoArray 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:

FieldTypeDescription
hydro_idintegerUpstream plant whose release feeds the arc
start_datestringStart of the release window (inclusive), ISO 8601 YYYY-MM-DD
end_datestringEnd of the release window (exclusive), ISO 8601 YYYY-MM-DD; must be after start_date
value_m3snumberAverage 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:

FieldTypeDescription
hydro_idintegerHydro plant identifier
start_datestringStart of the observation period (inclusive), ISO 8601 YYYY-MM-DD
end_datestringEnd of the observation period (exclusive), ISO 8601 YYYY-MM-DD
value_m3snumberAverage 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:

FieldTypeDescription
thermal_idintegerAnticipated thermal plant identifier; must reference a thermal declaring anticipated_config
start_datestringStart of the commitment window (inclusive), ISO 8601 YYYY-MM-DD
end_datestringEnd of the commitment window (exclusive), ISO 8601 YYYY-MM-DD; must be after start_date
value_mwnumberCommitted 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
}
]
}

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:

FieldTypeDescription
start_datestringStage start date (inclusive), ISO 8601 YYYY-MM-DD
duration_hoursnumberStage 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:

FieldTypeDescription
thermal_idintegerThermal plant identifier; must reference a thermal declaring anticipated_config
post_study_stage_indexintegerZero-based index into stages[]; must be < stages.len()
cost_per_mwhnumberFuel cost ($/MWh) at this cell; finite
min_mwnumberLower bound of the delivered MW rate at this cell; finite
max_mwnumberUpper 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).


Electrical bus registry. Buses are the nodes of the transmission network.

FieldRequiredDescription
buses[].idYesBus identifier (integer, unique)
buses[].nameYesHuman-readable bus name (string)
buses[].operational_start_dateYesCalendar 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_segmentsNoEntity-level deficit cost tiers; when absent, global defaults from penalties.json apply
buses[].deficit_segments[].depth_mwNoSegment MW depth; null for the final unbounded segment
buses[].deficit_segments[].costNoCost per MWh of deficit in this tier (USD/MWh)

Transmission line registry. Lines connect buses and carry power flows.

FieldRequiredDescription
lines[].idYesLine identifier (integer, unique)
lines[].nameYesHuman-readable line name (string)
lines[].source_bus_idYesSending-end bus ID
lines[].target_bus_idYesReceiving-end bus ID
lines[].operational_start_dateYesCalendar 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_idNoStage when line enters service; null = always exists
lines[].exit_stage_idNoStage when line is decommissioned; null = never
lines[].capacity.direct_mwYesMaximum power flow in the direct direction (MW)
lines[].capacity.reverse_mwYesMaximum power flow in the reverse direction (MW)
lines[].exchange_costNoEntity-level exchange cost override ($/MWh); absent = global default
lines[].losses_percentNoTransmission losses as percentage (default: 0.0)

Hydro plant registry. Each entry defines a complete hydro plant with reservoir, turbine, and optional cascade linkage.

Key fields:

FieldRequiredDescription
hydros[].idYesPlant identifier (integer, unique)
hydros[].nameYesHuman-readable plant name
hydros[].unit_groupsYesTurbine groups partitioning the plant’s generation envelope, each on its own bus (see below); an absent, null, or empty array is rejected
hydros[].downstream_idNoDownstream plant ID in the cascade; null = tailwater
hydros[].travel_time_hoursNoWater 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_dateYesCalendar 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_idNoStage when plant enters service; null = always exists
hydros[].exit_stage_idNoStage when plant is decommissioned; null = never
hydros[].reservoirYesmin_storage_hm3 and max_storage_hm3 (both >= 0)
hydros[].outflowYesmin_outflow_m3s and max_outflow_m3s total outflow bounds
hydros[].generationYesGeneration model: model, turbine flow bounds, generation MW bounds
hydros[].generation.modelYes"constant_productivity", "linearized_head", or "fpha"
hydros[].specific_productivity_mw_per_m3s_per_mNoSpecific productivity ρ_esp [MW/(m³/s)/m]. Required for FPHA hydros that rely on VHA geometry to derive ρ_eq.
hydros[].tailraceNoTailrace model: "polynomial" or "piecewise"
hydros[].hydraulic_lossesNoHead loss model: "factor" or "constant"
hydros[].efficiencyNoTurbine efficiency model: "constant"
hydros[].evaporationNoEvaporation config: coefficients_mm (12 values) and optional reference_volumes_hm3
hydros[].diversionNoDiversion channel: downstream_id and max_flow_m3s
hydros[].fillingNoFilling config: start_stage_id and filling_min_rate_m3s
hydros[].penaltiesNoEntity-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:

FieldRequiredDescription
idYesGroup identifier: dense, 0-based, unique within the plant (not globally)
nameYesHuman-readable group name
bus_idYesBus where this group’s generation is injected
min_turbined_m3sYesMinimum turbined flow for this group [m³/s]
max_turbined_m3sYesMaximum turbined flow for this group [m³/s]
min_generation_mwYesMinimum generation for this group [MW]
max_generation_mwYesMaximum 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 penaltiesOptionalDescription
spillage_costYesSpillage penalty ($/m³/s).
turbined_costYesTurbined flow regularization cost; applied to every hydro’s turbine column in the LP objective.
diversion_costYesDiversion flow penalty.
storage_violation_below_costYesStorage below-minimum violation penalty.
filling_target_violation_costYesFilling target violation penalty.
turbined_violation_below_costYesTurbined flow below-minimum violation penalty.
outflow_violation_below_costYesTotal outflow below-minimum violation penalty.
outflow_violation_above_costYesTotal outflow above-maximum violation penalty.
generation_violation_below_costYesGeneration below-minimum violation penalty.
evaporation_violation_costYesSymmetric evaporation violation penalty (applies to both directions when directional fields are absent).
water_withdrawal_violation_costYesSymmetric water withdrawal violation penalty (applies to both directions when directional fields are absent).
water_withdrawal_violation_pos_costYesOverride cost for over-withdrawal violations (actual > target). Supersedes water_withdrawal_violation_cost for the positive direction.
water_withdrawal_violation_neg_costYesOverride cost for under-withdrawal violations (actual < target). Supersedes water_withdrawal_violation_cost for the negative direction.
evaporation_violation_pos_costYesOverride cost for over-evaporation violations (actual > modelled). Supersedes evaporation_violation_cost for the positive direction.
evaporation_violation_neg_costYesOverride cost for under-evaporation violations (actual < modelled). Supersedes evaporation_violation_cost for the negative direction.
inflow_nonnegativity_costYesOverride global inflow non-negativity penalty cost for this plant ($/m³/s).

Thermal plant registry. Each entry defines a dispatchable generation unit.

FieldRequiredDescription
thermals[].idYesPlant identifier (integer, unique)
thermals[].nameYesHuman-readable plant name
thermals[].bus_idYesBus where generation is injected
thermals[].generationYesDispatch-bounds object with min_mw and max_mw
thermals[].generation.min_mwYesMinimum dispatch level (MW)
thermals[].generation.max_mwYesMaximum dispatch level (MW)
thermals[].cost_per_mwhYesLinear generation cost (USD/MWh)
thermals[].operational_start_dateYesCalendar 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_idNoStage when the unit enters service (null = present from stage 0)
thermals[].exit_stage_idNoStage when the unit is decommissioned (null = never)
thermals[].anticipated_configNoAnticipated-dispatch config (object with lead_stages ≥ 1)

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.

FieldRequiredDescription
non_controllable_sources[].idYesSource identifier (integer, unique within the file)
non_controllable_sources[].nameYesHuman-readable source name (string)
non_controllable_sources[].bus_idYesBus into which the source’s generation is injected
non_controllable_sources[].operational_start_dateYesCalendar 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_mwYesMaximum generation (installed capacity) [MW]
non_controllable_sources[].allow_curtailmentNoWhether 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_costNoEntity-level curtailment cost override [$/MWh]; falls back to the global ncs_curtailment_cost when absent
non_controllable_sources[].entry_stage_idNoStage when the source enters service; null or absent = present from stage 0
non_controllable_sources[].exit_stage_idNoStage 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
}
]
}

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.

FieldRequiredDescription
pumping_stations[].idYesStation identifier (integer, unique)
pumping_stations[].nameYesHuman-readable station name (string)
pumping_stations[].bus_idYesBus from which electrical power is consumed
pumping_stations[].source_hydro_idYesHydro plant from whose reservoir water is extracted
pumping_stations[].destination_hydro_idYesHydro plant into whose reservoir water is injected
pumping_stations[].consumption_mw_per_m3sYesPower drawn per unit of pumped flow [MW/(m³/s)]; must be >= 0
pumping_stations[].operational_start_dateYesCalendar 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_idNoStage when the station enters service; null or absent = present from stage 0
pumping_stations[].exit_stage_idNoStage when the station is decommissioned; null or absent = never
pumping_stations[].flowYesNested object with min_m3s and max_m3s (see below)
pumping_stations[].flow.min_m3sYesMinimum pumped flow [m³/s]; must be >= 0
pumping_stations[].flow.max_m3sYesMaximum 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 }
}
]
}

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.

FieldRequiredDescription
contracts[].idYesContract identifier (integer, unique)
contracts[].nameYesHuman-readable contract name (string)
contracts[].bus_idYesBus where power is injected (import) or withdrawn (export)
contracts[].typeYesEnergy flow direction: "import" or "export"
contracts[].price_per_mwhYesContract price [monetary units/MWh]. Positive = cost (import); negative = revenue (export)
contracts[].limits.min_mwYesMinimum dispatch level [MW]; use 0.0 unless a take-or-pay floor applies
contracts[].limits.max_mwYesMaximum dispatch level [MW]; must be >= limits.min_mw
contracts[].operational_start_dateYesCalendar 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_idNoStage when the contract enters service; null or absent = present from stage 0
contracts[].exit_stage_idNoStage 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 }
}
]
}

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.

ColumnTypeRequiredDescription
hydro_idINT32YesHydro plant ID
volume_hm3DOUBLEYesTotal reservoir volume at this point (hm³). Non-negative and finite.
height_mDOUBLEYesReservoir surface elevation at this volume (m). Non-negative and finite.
area_km2DOUBLEYesWater 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.


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:

FieldRequiredDescription
hydro_idYesHydro plant ID. Must be unique within the file.
selection_modeYesHow 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 rangeRequiredDescription
start_stage_idYesFirst stage (inclusive) to which this entry applies
end_stage_idYesLast stage (inclusive); null means open-ended
modelYesModel name: "constant_productivity", "linearized_head", or "fpha"
fpha_configNoRequired when model is "fpha". See FPHA config fields below.
reference_volumeNoReference 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_m3sNoPositive 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.

FieldRequiredDescription
default_modelYesFallback model name for unlisted seasons
seasonsYesArray of season overrides: season_id, model, optional fpha_config, reference_volume, productivity_mw_per_m3s

reference_volume fields (optional sibling of fpha_config):

FieldRequiredDescription
volume_hm3NoAbsolute reference volume [hm³]; finite and > 0.0. Mutually exclusive with percentile.
percentileNoReference 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"):

FieldRequiredDefaultDescription
sourceYes"precomputed" or "computed"
volume_discretization_pointsNosolver defaultNumber of volume grid points for hyperplane computation
turbine_discretization_pointsNosolver defaultNumber of turbine-flow grid points for hyperplane computation
spillage_discretization_pointsNosolver defaultNumber of spillage grid points for hyperplane computation
max_planes_per_hydroNosolver defaultMaximum hyperplanes per plant after selection heuristic
fitting_windowNofull rangeVolume 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.

FieldTypeDescription
volume_min_hm3numberExplicit minimum volume for fitting (hm³)
volume_max_hm3numberExplicit maximum volume for fitting (hm³)
volume_min_percentilenumberMinimum as a percentile of the operating range (0–1)
volume_max_percentilenumberMaximum 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" }
}
]
}
]
}

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.

ColumnTypeNullableDescription
hydro_idINT32NoHydro plant ID
stage_idINT32YesStage the plane applies to. null = valid for all stages
plane_idINT32NoPlane index within this hydro (and stage)
gamma_0DOUBLENoIntercept coefficient (MW)
gamma_vDOUBLENoVolume coefficient (MW/hm³). Positive.
gamma_qDOUBLENoTurbined flow coefficient (MW per m³/s)
gamma_sDOUBLENoSpillage coefficient (MW per m³/s). Typically non-positive.
kappaDOUBLEYesCorrection factor. Defaults to 1.0 when absent or null.
valid_v_min_hm3DOUBLEYesVolume range minimum where this plane is valid (hm³)
valid_v_max_hm3DOUBLEYesVolume range maximum where this plane is valid (hm³)
valid_q_max_m3sDOUBLEYesMaximum 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.


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.

ColumnParquet typeNullableDescription
hydro_idINT32noHydro plant identifier
stage_idINT32yesStage; NULL means “applies to all stages”
equivalent_productivity_mw_per_m3sDOUBLEyesDirect ρ_eq override [MW/(m³/s)]; finite and >= 0.0 (0.0 marks a planned-outage stage)
reference_outflow_m3sDOUBLEyesQ_ref override [m³/s]; finite and >= 0.0
specific_productivity_mw_per_m3s_per_mDOUBLEyesρ_esp override [MW/(m³/s)/m]; finite and > 0.0

Validation:

  • hydro_id must not be null.
  • equivalent_productivity_mw_per_m3s, when set, must be finite and >= 0.0; 0.0 is 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.0 mirrors the equivalent_productivity_mw_per_m3s planned-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) via reference_volume in system/hydro_production_models.json. A legacy reference_volume_hm3 column, if still present, is ignored (a one-time warning is emitted).

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).

ColumnTypeNullableDescription
hydro_idINT32NoPlant whose tailrace this describes
family_idINT32NoFamily index within the plant (sequential grouping key)
downstream_reference_level_mDOUBLEYesDownstream reservoir reference level keying this family (m). null when the plant has a single family and no backwater dependency.
segment_idINT32NoPiece index within the family
outflow_min_m3sDOUBLENoSegment lower validity bound (m³/s). Non-negative.
outflow_max_m3sDOUBLENoSegment upper validity bound (m³/s). Non-negative, >= outflow_min_m3s.
coefficient_0DOUBLENoDegree-0 polynomial coefficient. Any sign.
coefficient_1DOUBLENoDegree-1 polynomial coefficient. Any sign.
coefficient_2DOUBLENoDegree-2 polynomial coefficient. Any sign.
coefficient_3DOUBLENoDegree-3 polynomial coefficient. Any sign.
coefficient_4DOUBLENoDegree-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_m3s and outflow_max_m3s must be non-negative and finite.
  • outflow_max_m3s >= outflow_min_m3s (segments are non-inverted).
  • coefficient_0 through coefficient_4 must be finite.
  • downstream_reference_level_m, when non-null, must be non-negative and finite.

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.

ColumnTypeRequiredDescription
hydro_idINT32YesHydro plant ID
start_dateDATEYesWindow start, inclusive
end_dateDATEYesWindow end, exclusive; after start_date
value_m3sDOUBLEYesMean 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.

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.

ColumnTypeRequiredDescription
hydro_idINT32YesHydro plant ID
stage_idINT32YesStage ID
mean_m3sDOUBLEYesSeasonal mean inflow (m³/s); must be finite
std_m3sDOUBLEYesSeasonal standard deviation (m³/s); must be >= 0 and finite

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.

ColumnTypeRequiredDescription
hydro_idINT32YesHydro plant ID
stage_idINT32YesStage ID
lagINT32YesLag index (1-based)
coefficientDOUBLEYesAR 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.


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.

ColumnTypeRequiredDescription
stage_idINT32YesZero-based stage index (0 to n_stages − 1)
opening_indexUINT32YesZero-based opening index within the stage (0 to openings_per_stage − 1)
entity_indexUINT32YesZero-based entity index in system dimension order (see entity ordering below)
valueDOUBLEYesNoise 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_index values must equal n_hydros + n_load_buses.
  • Stage count mismatch — the number of distinct stage_id values must equal the configured number of study stages.
  • Missing opening indices — for each stage, every opening index from 0 to openings_per_stage − 1 must 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.


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:

FieldTypeDescription
bus_idintegerBus entity ID. Must refer to a bus defined in system/buses.json.
stage_idintegerStudy stage index. Must be a valid stage ID from stages.json.
block_factorsarrayArray of { block_id, factor } pairs for each load block.

block_factors entry fields:

FieldTypeConstraintsDescription
block_idintegerMust be a valid block for stageZero-based block index within the stage.
factornumber> 0, finiteMultiplier 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.


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:

FieldTypeDescription
ncs_idintegerNCS entity ID. Must refer to a source in system/non_controllable_sources.json.
stage_idintegerStudy stage index. Must be a valid stage ID from stages.json.
block_factorsarrayArray of { block_id, factor } pairs for each load block.

block_factors entry fields:

FieldTypeConstraintsDescription
block_idintegerMust be a valid block for stageZero-based block index within the stage.
factornumber>= 0, finiteMultiplier 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).


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).

ColumnTypeRequiredDescription
ncs_idINT32YesNon-controllable source ID
stage_idINT32YesStage ID (0-based)
meanDOUBLEYesMean availability factor in [0, 1]
stdDOUBLEYesStandard deviation of availability factor (>= 0)

When absent, NCS availability is deterministic from constraints/ncs_bounds.parquet or the entity’s max_generation_mw.


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.

Stage-varying generation and cost overrides for thermal plants.

ColumnTypeRequiredDescription
thermal_idINT32YesThermal plant ID
stage_idINT32YesStage ID
min_generation_mwDOUBLENoMinimum generation override (MW)
max_generation_mwDOUBLENoMaximum generation override (MW)
cost_per_mwhDOUBLENoDispatch cost override (USD/MWh)
block_idINT32 (nullable)NoZero-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.


Stage-varying operational bound overrides for hydro plants.

ColumnTypeRequiredDescription
hydro_idINT32YesHydro plant ID
stage_idINT32YesStage ID
min_turbined_m3sDOUBLENoMinimum turbined flow (m³/s)
max_turbined_m3sDOUBLENoMaximum turbined flow (m³/s)
min_storage_hm3DOUBLENoMinimum reservoir storage (hm³)
max_storage_hm3DOUBLENoMaximum reservoir storage (hm³)
min_outflow_m3sDOUBLENoMinimum total outflow (m³/s)
max_outflow_m3sDOUBLENoMaximum total outflow (m³/s)
min_generation_mwDOUBLENoMinimum generation (MW)
max_generation_mwDOUBLENoMaximum generation (MW)
max_diversion_m3sDOUBLENoMaximum diversion flow (m³/s)
min_diversion_m3sDOUBLENoMinimum diversion flow (m³/s); requires a declared diversion channel
min_spillage_m3sDOUBLENoMinimum spillage flow (m³/s)
max_spillage_m3sDOUBLENoMaximum spillage flow (m³/s); >= min_spillage_m3s
filling_min_rate_m3sDOUBLENoFilling minimum-rate override (m³/s)
water_withdrawal_m3sDOUBLENoWater withdrawal (m³/s)
block_idINT32 (nullable)NoZero-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.

ColumnTypeRequiredDescription
hydro_idINT32YesHydro plant ID
hydro_unit_group_idINT32YesUnit group ID (unit_groups[].id), unique within the plant
stage_idINT32YesStage ID
min_turbined_m3sDOUBLENoMinimum turbined flow override for this group (m³/s)
max_turbined_m3sDOUBLENoMaximum turbined flow override for this group (m³/s)
min_generation_mwDOUBLENoMinimum generation override for this group (MW)
max_generation_mwDOUBLENoMaximum generation override for this group (MW)
block_idINT32 (nullable)NoZero-based block index within the stage, selecting one block’s override. null applies at the stage level; all four bound columns are block-eligible.

Stage-varying, absolute-MW flow capacity overrides for transmission lines.

ColumnTypeRequiredDescription
line_idINT32YesTransmission line ID
stage_idINT32YesStage ID
direct_mwDOUBLENoDirect-flow capacity override (MW)
reverse_mwDOUBLENoReverse-flow capacity override (MW)
block_idINT32 (nullable)NoZero-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.


Stage-varying flow bounds for pumping stations.

ColumnTypeRequiredDescription
pumping_station_idINT32YesPumping station ID
stage_idINT32YesStage ID
min_m3sDOUBLENoMinimum pumping flow (m³/s)
max_m3sDOUBLENoMaximum pumping flow (m³/s)
block_idINT32 (nullable)NoZero-based block index within the stage, selecting one block’s min_m3s/max_m3s override. null applies at the stage level.

Stage-varying power and price overrides for energy contracts.

ColumnTypeRequiredDescription
contract_idINT32YesEnergy contract ID
stage_idINT32YesStage ID
min_mwDOUBLENoMinimum power (MW)
max_mwDOUBLENoMaximum power (MW)
price_per_mwhDOUBLENoPrice override (USD/MWh)
block_idINT32 (nullable)NoZero-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.


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.

ColumnTypeRequiredDescription
ncs_idINT32YesNon-controllable source ID
stage_idINT32YesStage ID
available_generation_mwDOUBLEYesMaximum 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).


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:

FieldTypeRequiredDescription
idintegerYesConstraint identifier. Must be unique within the file.
namestringYesShort name used in reports and log output.
expressionstringYesExpression string over variable references and constants (see below).
descriptionstring or nullNoOptional human-readable description.
slackobjectYesSlack 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).

ColumnTypeRequiredDescription
constraint_idINT32YesGeneric constraint ID
stage_idINT32YesStage ID
block_idINT32 (nullable)NoZero-based block index within the stage. null applies to all blocks of the given stage.
bound_lowerDOUBLE (nullable)NoLower interval endpoint. null when the constraint is unbounded below at this cell.
bound_upperDOUBLE (nullable)NoUpper 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.


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:

FieldTypeRequiredDescription
idintegerYesUnique parameter identifier (int32)
namestringYesUnique parameter name (non-empty, no leading/trailing whitespace)
kindstringYesOne of constant, per_stage, seasonal, computed, per_stage_block
valuenumberkind depFinite f64 value. Required for constant. Absent otherwise.
valuesarraykind depArray of [index, value] pairs. Required for per_stage and seasonal.
computed_specobjectkind dep{"tag": "<variant>", "hydro_id": <int>}. Required for computed.
block_valuesarraykind depArray 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:

tagDescription
equivalent_productivityEquivalent productivity ρ_eq
accumulated_productivityAccumulated cascade productivity ρ_acum
reference_volumeReference reservoir volume V_ref
reference_turbineReference turbined flow Q_ref
min_storageMinimum operational storage V_min
max_storageMaximum operational storage V_max
specific_productivitySpecific productivity ρ_esp

Validation:

  • id values must be unique across all entries.
  • name values must be unique (case-sensitive), non-empty, and have no leading or trailing whitespace.
  • kind must be exactly one of the five legal values.
  • For per_stage: values pairs must have contiguous stage_id keys starting at 0; duplicates and gaps are rejected.
  • For seasonal: season_id keys within an entry must be unique; duplicates are rejected.
  • For computed: computed_spec must be present with a valid tag and integer hydro_id. The referenced hydro must exist in hydros.json.
  • For per_stage_block: block_values must 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.


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.

ColumnTypeRequiredDescription
bus_idINT32YesBus ID
stage_idINT32YesStage ID
excess_costDOUBLENoExcess 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”
ColumnTypeRequiredDescription
line_idINT32YesTransmission line ID
stage_idINT32YesStage ID
exchange_costDOUBLENoExchange flow cost override (USD/MWh)

constraints/penalty_overrides_hydro.parquet

Section titled “constraints/penalty_overrides_hydro.parquet”
ColumnTypeRequiredDescription
hydro_idINT32YesHydro plant ID
stage_idINT32YesStage ID
spillage_costDOUBLENoSpillage penalty override
turbined_costDOUBLENoTurbined cost override
diversion_costDOUBLENoDiversion penalty override
storage_violation_below_costDOUBLENoStorage below-minimum violation override
filling_target_violation_costDOUBLENoFilling target violation override
turbined_violation_below_costDOUBLENoTurbined below-minimum violation override
outflow_violation_below_costDOUBLENoOutflow below-minimum violation override
outflow_violation_above_costDOUBLENoOutflow above-maximum violation override
generation_violation_below_costDOUBLENoGeneration below-minimum violation override
evaporation_violation_costDOUBLENoEvaporation violation override
water_withdrawal_violation_costDOUBLENoWater withdrawal violation override

ColumnTypeRequiredDescription
ncs_idINT32YesNon-controllable source ID
stage_idINT32YesStage ID
curtailment_costDOUBLENoCurtailment penalty override (USD/MWh)