Configuration
All runtime parameters for cobre run are controlled by config.json in the
case directory. This page documents every section and field.
Minimal Config
{
"training": {
"forward_passes": 50,
"stopping_rules": [{ "type": "iteration_limit", "limit": 100 }]
}
}
All other sections are optional with defaults documented below.
training
Controls the SDDP training phase.
Mandatory Fields
| Field | Type | Description |
|---|---|---|
forward_passes | integer | Number of scenario trajectories per iteration. Larger values reduce variance in each iteration’s cut but increase cost per iteration. |
stopping_rules | array | At least one stopping rule (see below). The rule set must contain at least one iteration_limit rule. |
Optional Fields
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Set to false to skip training and proceed directly to simulation (requires a pre-trained policy). |
tree_seed | integer | null | Random seed for the opening scenario tree. When null, a default seed of 42 is used (deterministic but arbitrary). See Stochastic Modeling for the dual-seed architecture. |
stopping_mode | "any" or "all" | "any" | How multiple stopping rules combine: "any" stops when the first rule is satisfied; "all" requires all rules to be satisfied simultaneously. |
For the per-class scenario_source configuration, see the
scenario_source sub-section below and
Stochastic Modeling.
scenario_source
Controls where the forward-pass noise comes from for each entity class during
training. When absent, all classes default to in_sample (reusing the
pre-generated opening tree).
| Field | Type | Default | Description |
|---|---|---|---|
seed | integer or null | null | Shared forward-pass seed for out_of_sample, historical, and external schemes. |
inflow | object | in_sample | Sampling scheme for hydro inflow. Object with "scheme" key. |
load | object | in_sample | Sampling scheme for bus load. Object with "scheme" key. |
ncs | object | in_sample | Sampling scheme for NCS availability. Object with "scheme" key. |
historical_years | array or object | auto-discover | Restrict the pool of historical windows. List ([1940, 1953]) or range ({"from": 1940, "to": 2010}). |
Valid values for "scheme": "in_sample", "out_of_sample", "historical", "external".
Example — out-of-sample inflow with in-sample load and NCS:
{
"training": {
"tree_seed": 42,
"forward_passes": 50,
"stopping_rules": [{ "type": "iteration_limit", "limit": 200 }],
"scenario_source": {
"seed": 99,
"inflow": { "scheme": "out_of_sample" },
"load": { "scheme": "in_sample" },
"ncs": { "scheme": "in_sample" }
}
}
}
See Stochastic Modeling — Sampling Schemes
for a full description of each scheme and the historical_years field.
Stopping Rules
Each entry in stopping_rules is a JSON object with a "type" discriminator.
iteration_limit
Stop after a fixed number of training iterations.
{ "type": "iteration_limit", "limit": 200 }
| Field | Type | Description |
|---|---|---|
limit | integer | Maximum number of SDDP iterations to run. |
time_limit
Stop after a wall-clock time budget is exhausted.
{ "type": "time_limit", "seconds": 3600.0 }
| Field | Type | Description |
|---|---|---|
seconds | float | Maximum training time in seconds. |
bound_stalling
Stop when the relative improvement in the lower bound falls below a threshold.
{ "type": "bound_stalling", "iterations": 20, "tolerance": 0.0001 }
| Field | Type | Description |
|---|---|---|
iterations | integer | Window size: the number of past iterations over which to compute the relative improvement. |
tolerance | float | Relative improvement threshold. Training stops when the improvement over the window is below this value. |
simulation
Stop when both the lower bound and a Monte Carlo policy cost estimate have stabilized. Periodically runs a batch of forward simulations and compares the result against previous evaluations.
{
"type": "simulation",
"replications": 100,
"period": 10,
"bound_window": 5,
"distance_tol": 0.01,
"bound_tol": 0.0001
}
| Field | Type | Description |
|---|---|---|
replications | integer | Number of Monte Carlo forward simulations per check. |
period | integer | Iterations between simulation checks. |
bound_window | integer | Number of past iterations for bound stability check. |
distance_tol | float | Normalized distance threshold between consecutive simulation results. |
bound_tol | float | Relative tolerance for bound stability. |
stopping_mode
When multiple stopping rules are listed, stopping_mode controls how they
combine:
"any"(default): stop when any one rule is satisfied."all": stop only when every rule is satisfied simultaneously.
{
"training": {
"forward_passes": 50,
"stopping_mode": "all",
"stopping_rules": [
{ "type": "iteration_limit", "limit": 500 },
{ "type": "bound_stalling", "iterations": 20, "tolerance": 0.0001 }
]
}
}
simulation
Controls the optional post-training simulation phase.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable the simulation phase after training. |
num_scenarios | integer | 2000 | Number of independent Monte Carlo simulation scenarios to evaluate. |
When simulation.enabled is false or num_scenarios is 0, the simulation
phase is skipped entirely.
Example:
{
"simulation": {
"enabled": true,
"num_scenarios": 1000
}
}
scenario_source
Controls where the forward-pass noise comes from during the simulation phase.
When absent, simulation falls back to the scheme configured under
training.scenario_source. This allows you to train with in-sample noise and
simulate with a different scheme (for example, out-of-sample or historical)
without modifying the training configuration.
The fields are identical to training.scenario_source:
| Field | Type | Default | Description |
|---|---|---|---|
seed | integer or null | null | Shared forward-pass seed for out_of_sample, historical, and external schemes. |
inflow | object | in_sample | Sampling scheme for hydro inflow. Object with "scheme" key. |
load | object | in_sample | Sampling scheme for bus load. Object with "scheme" key. |
ncs | object | in_sample | Sampling scheme for NCS availability. Object with "scheme" key. |
historical_years | array or object | auto-discover | Restrict the pool of historical windows. List ([1940, 1953]) or range ({"from": 1940, "to": 2010}). |
Example — simulate with out-of-sample inflow while training uses in-sample:
{
"training": {
"forward_passes": 50,
"stopping_rules": [{ "type": "iteration_limit", "limit": 200 }]
},
"simulation": {
"enabled": true,
"num_scenarios": 2000,
"scenario_source": {
"seed": 77,
"inflow": { "scheme": "out_of_sample" },
"load": { "scheme": "in_sample" },
"ncs": { "scheme": "in_sample" }
}
}
}
modeling
Controls physical modeling options.
| Field | Type | Default | Description |
|---|---|---|---|
inflow_non_negativity | object | see below | Strategy for handling negative PAR model inflow draws. |
inflow_non_negativity
| Field | Type | Default | Description |
|---|---|---|---|
method | string | "penalty" | One of "none", "penalty", "truncation", or "truncation_with_penalty". |
"none"– no treatment; negative inflows are passed through to the LP."penalty"– adds a slack variable to the LP that absorbs negative inflow realisations. The slack carries a per-hydro objective cost frompenalties.json::hydro.inflow_nonnegativity_cost."truncation"– clamps negative PAR model draws to zero before applying noise."truncation_with_penalty"– combines both: clamps the inflow to zero and adds a bounded slack variable penalised bypenalties.json::hydro.inflow_nonnegativity_cost, providing a smooth backstop for extreme tail realisations.
Example:
{
"modeling": {
"inflow_non_negativity": {
"method": "penalty"
}
}
}
cut_selection
Controls the row management pipeline for managing row pool growth. The pipeline has up to two stages: strategy-based selection and budget enforcement. Row management periodically scans the row pool and deactivates rows that are unlikely to improve the policy, reducing LP size without sacrificing convergence quality. For a detailed explanation of each stage, see Performance Accelerators.
The block has two always-on knobs at the top level plus a selection
sub-object that chooses the method and carries only that method’s parameters.
Omitting selection (or setting it to null) disables row selection — that is
the default.
Always-on fields
| Field | Type | Default | Description |
|---|---|---|---|
row_activity_tolerance | float | 0.0 | Minimum dual-multiplier magnitude for a constraint row to count as binding at a solution point. Rows whose dual falls below this are treated as inactive in tracking. |
max_active_per_stage | integer | null | Hard cap on active rows per stage LP, enforced after the selection method runs. null = no cap. |
selection | object | null | The active selection method and its parameters (see below). null (the default) disables row selection. |
The selection object
selection.method is the discriminator; each method exposes only its own
parameters. Supplying a parameter that belongs to a different method is a config
load error, and a misspelled method is rejected with the list of valid methods.
-
"level1"— evaluates all populated rows at every visited state and retains any row whose value is withintie_toleranceof the per-state maximum at some state. Least aggressive; preserves the convergence guarantee.Field Type Default Description tie_tolerancefloat 1e-10A row is active at a state when within this of the best row value there. check_frequencyinteger 5Iterations between periodic pruning checks. Must be > 0. -
"lml1"— at each visited state, retains only the oldest eligible row withintie_toleranceof the per-state maximum; the selected set is the union of those per-state survivors. More aggressive than"level1". Same fields as"level1"(tie_tolerance,check_frequency). -
"domination"— removes rows dominated at all visited states.Field Type Default Description domination_tolerancefloat – A row survives if within this of the maximum at any visited state. Required. check_frequencyinteger 5Iterations between periodic pruning checks. Must be > 0. -
"dynamic"— a per-solve lazy loop that loads only a small resident subset of rows per solve while retaining the full pool. The resident set is seeded from the most recent iterations, and each lazy-solve round adds the most-violated candidate rows.Field Type Default Description start_iterationinteger 2First 1-based iteration at which the lazy loop becomes active. Must be >= 1.seed_windowinteger 5Number of most-recent iterations whose rows seed the initial resident set. 0is valid (seeds only the current iteration).candidate_recencyinteger nullOnly rows generated within the last candidate_recencyiterations are scored.null(the default) is unbounded — every pool row is a candidate, which preserves exactness.Some(n)(must be>= 1) makes the loop deliberately inexact: rows older than the window are never added.max_added_per_roundinteger 10Maximum rows added per lazy-solve round. Must be >= 1.violation_tolerancefloat 1e-10Violation tolerance for accepting a candidate row. Must be > 0.
The dynamic method is mutually exclusive with the periodic-pruning methods by
construction — choosing it from the tagged selection block means none of
level1 / lml1 / domination can run.
Example with the dynamic method:
{
"training": {
"cut_selection": {
"row_activity_tolerance": 1e-6,
"max_active_per_stage": 4000,
"selection": {
"method": "dynamic",
"start_iteration": 2,
"seed_window": 5,
"max_added_per_round": 10,
"violation_tolerance": 1e-10
}
}
}
}
Example with the level1 method and a per-stage budget:
{
"training": {
"cut_selection": {
"row_activity_tolerance": 1e-6,
"max_active_per_stage": 500,
"selection": {
"method": "level1",
"tie_tolerance": 1e-10,
"check_frequency": 5
}
}
}
}
estimation
Controls the PAR(p) model estimation pipeline. When the case provides
inflow_history.parquet, Cobre can automatically estimate AR coefficients
instead of requiring pre-computed inflow_ar_coefficients.parquet.
| Field | Type | Default | Description |
|---|---|---|---|
max_order | integer | 6 | Maximum lag order considered during autoregressive model fitting. |
order_selection | string | "pacf" | Order selection criterion: "pacf" (PACF-based) or "pacf_annual" (PACF with annual component). |
min_observations_per_season | integer | 30 | Minimum observations per (entity, season) group to proceed with estimation. |
max_coefficient_magnitude | float | null | Safety net: reduce to order 0 if any coefficient exceeds this magnitude. |
Example:
{
"estimation": {
"max_order": 6,
"order_selection": "pacf",
"min_observations_per_season": 30
}
}
Setting "order_selection": "pacf_annual" activates the annual component extension. When
enabled, the estimation pipeline performs four additional steps beyond the classical PAR
path: (1) the Yule-Walker system is extended to include a cross-correlation term between
the current-season inflow and the rolling 12-month average; (2) per-season sample
statistics (mean and standard deviation) of that rolling average are computed for each
hydro plant; (3) the coefficient, mean, and standard deviation are written to
inflow_annual_component.parquet in the output directory; and (4) the lag stride used
when building the LP noise columns is widened to accommodate the extra annual term. Use
this option when your inflow series shows persistence that extends beyond the
standard seasonal lag window.
policy
Controls policy persistence (checkpoint saving and warm-start loading).
| Field | Type | Default | Description |
|---|---|---|---|
path | string | "./policy" | Directory where policy data (cuts, states) is stored. |
mode | "fresh", "warm_start", or "resume" | "fresh" | Initialization mode. "fresh" starts from scratch; "warm_start" loads cuts from a previous run; "resume" continues an interrupted run. |
validate_compatibility | boolean | true | When loading a policy, verify that entity counts, stage counts, and cut dimensions match the current system. |
boundary | object or null | null | Terminal boundary cut configuration for coupling with an outer model’s FCF. See below. |
checkpointing
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable periodic checkpointing during training. |
initial_iteration | integer | null | First iteration to write a checkpoint. |
interval_iterations | integer | null | Iterations between checkpoints. |
store_basis | boolean | false | Include LP basis in checkpoints for warm-start. |
compress | boolean | false | Compress checkpoint files. |
boundary
Optional configuration for loading terminal-stage boundary cuts from a different Cobre policy checkpoint. When present, the solver loads cuts from the source checkpoint and injects them as fixed boundary conditions at the terminal stage of the current study. The imported cuts are not updated by training — they remain fixed throughout.
This enables Cobre-to-Cobre model coupling: a monthly study produces a policy checkpoint, and a weekly+monthly coupled study loads that checkpoint’s cuts as its terminal-stage future cost function.
| Field | Type | Description |
|---|---|---|
path | string | Path to the source policy checkpoint directory. |
source_stage | integer | 0-based stage index in the source checkpoint to load cuts from. |
Example — load stage 2’s cuts from a monthly policy as terminal boundary:
{
"policy": {
"mode": "fresh",
"boundary": {
"path": "../monthly_study/policy",
"source_stage": 2
}
}
}
See Policy Management — Boundary Cuts for a full explanation of the coupling workflow.
Temporal Resolution
Cobre does not have dedicated config.json fields for temporal resolution. The
resolution of each stage is determined entirely by the date boundaries in
stages.json. However, when stages.json defines stages at different temporal
resolutions — for example, four weekly stages within a month followed by monthly
stages, or monthly stages transitioning to quarterly stages — three mechanisms
activate automatically that users should understand.
Noise Group Sharing
When multiple SDDP stages share the same season_id within the same calendar
period (for example, four weekly stages all assigned season_id: 0 for
January), they receive identical PAR noise draws. This ensures that sub-monthly
stages present an inflow trajectory consistent with the monthly PAR model they
were fitted from, rather than fabricating independent weekly variability that
the historical record does not support.
Observation Aggregation
When the study includes stages at different resolutions (for example, monthly
and quarterly), Cobre automatically aggregates fine-grained historical
observations into coarser season buckets before PAR fitting. A user supplying
monthly inflow_history.parquet for a study that includes quarterly stages does
not need to pre-aggregate the data; Cobre derives one observation per
(entity, season, year) at the appropriate coarser resolution. Aggregating in the
opposite direction (disaggregating coarser observations to a finer resolution)
is not supported and will produce a validation error at case load time.
Lag Resolution Transition
For studies that transition from monthly to quarterly stages, the PAR lag state changes resolution at the boundary. During the monthly phase, each monthly inflow is accumulated into a ring buffer indexed by the downstream (quarterly) lag. When the first quarterly stage is reached, the ring buffer contains a complete set of duration-weighted monthly contributions, and the lag state is rebuilt automatically. This transition is transparent to the LP and the cut representation; it introduces no additional LP variables.
Example: Weekly Stages Within a Month
The following stages.json excerpt shows four weekly stages within January
(stages 0-3, all with season_id: 0) followed by a normal monthly stage for
February (season_id: 1). Stages 0-3 share the same season_id and will
therefore receive identical PAR noise draws during training:
[
{
"id": 0,
"start_date": "2024-01-01",
"end_date": "2024-01-08",
"season_id": 0,
"num_scenarios": 50
},
{
"id": 1,
"start_date": "2024-01-08",
"end_date": "2024-01-15",
"season_id": 0,
"num_scenarios": 50
},
{
"id": 2,
"start_date": "2024-01-15",
"end_date": "2024-01-22",
"season_id": 0,
"num_scenarios": 50
},
{
"id": 3,
"start_date": "2024-01-22",
"end_date": "2024-02-01",
"season_id": 0,
"num_scenarios": 50
},
{
"id": 4,
"start_date": "2024-02-01",
"end_date": "2024-03-01",
"season_id": 1,
"num_scenarios": 50
}
]
Recommended Alternative: Weekly Blocks Within a Monthly Stage
When weekly dispatch granularity is needed but true weekly-resolution noise
data is unavailable, the recommended approach is to use a single monthly SDDP
stage with chronological blocks rather than four separate weekly SDDP stages.
This provides weekly LP granularity while keeping one noise realization per
month — consistent with the data resolution — and avoids the lag-accumulation
complications that arise with multiple independent weekly stages. See
Stochastic Modeling — Temporal Resolution and PAR
for the full explanation and a stages.json example of the block pattern.
See Also (Temporal Resolution)
- Stochastic Modeling — Multi-Resolution Studies — detailed mechanism descriptions including the noise group precomputation algorithm, observation aggregation internals, and lag ring buffer design
- Stochastic Modeling — Temporal Resolution and PAR — the honest representation principle, the recommended weekly-block pattern, and validation rules 27-31
exports
Controls which outputs are written to the results directory.
| Field | Type | Default | Description |
|---|---|---|---|
states | boolean | false | Write visited forward-pass trial points to the policy checkpoint (FlatBuffers). |
stochastic | boolean | false | Export stochastic preprocessing artifacts to output/stochastic/. |
fpha_deviation_points | boolean | false | Export the per-grid-point computed-FPHA fit-deviation table to output/hydro_models/fpha_deviation_points.parquet. Opt-in because it emits one row per (hydro, stage, V, Q) sample point at spillage = 0. |
Full Example
{
"$schema": "https://raw.githubusercontent.com/cobre-rs/cobre/refs/heads/main/book/src/schemas/config.schema.json",
"training": {
"tree_seed": 42,
"forward_passes": 50,
"stopping_rules": [
{ "type": "iteration_limit", "limit": 200 },
{ "type": "bound_stalling", "iterations": 20, "tolerance": 0.0001 }
],
"stopping_mode": "any",
"scenario_source": {
"seed": 99,
"inflow": { "scheme": "out_of_sample" },
"load": { "scheme": "in_sample" },
"ncs": { "scheme": "in_sample" }
},
"cut_selection": {
"row_activity_tolerance": 1e-6,
"max_active_per_stage": null,
"selection": {
"method": "level1",
"tie_tolerance": 1e-10,
"check_frequency": 5
}
}
},
"modeling": {
"inflow_non_negativity": {
"method": "penalty"
}
},
"simulation": {
"enabled": true,
"num_scenarios": 2000
},
"policy": {
"path": "./policy",
"mode": "fresh"
},
"exports": {
"states": false,
"stochastic": false
}
}
Advanced Fields
The Config struct supports additional sections not documented on this page.
These fields are deserialized from config.json when present but are intended
for advanced use cases and may change between releases:
| Section | Purpose |
|---|---|
upper_bound_evaluation | Inner approximation upper-bound evaluation settings |
training.solver | LP solver options (see Solver Safeguards for details) |
simulation.io_channel_capacity | Async I/O channel buffer size for simulation output writing |
All fields have defaults and can be omitted. Every JSON input file rejects
unknown keys, so misspelled fields raise a parse error rather than being
silently ignored. For the complete list of fields and their types, see the
Config struct in the cobre-io API docs.
See Also
- Case Directory Format — full schema for all input files
- Running Studies — end-to-end workflow guide
- Error Codes — validation errors including
SchemaErrorfor config fields - Stochastic Modeling — Temporal Resolution — how stage resolution affects PAR noise sharing and validation rules