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
Section titled “Minimal Config”{ "training": { "selection": { "method": "sampled", "forward_passes": 50 }, "stopping_rules": [{ "type": "iteration_limit", "limit": 100 }] }}All other sections are optional with defaults documented below.
training
Section titled “training”Controls the SDDP training phase.
Mandatory Fields
Section titled “Mandatory Fields”| Field | Type | Description |
|---|---|---|
selection | object | Scenario-selection method for the training forward pass — sampled{forward_passes} or enumerated{} (see training.selection below). No default: a missing training.selection is a hard load error. |
stopping_rules | array | At least one stopping rule (see below). The rule set must contain at least one iteration_limit rule. |
training.selection
Section titled “training.selection”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 (deny_unknown_fields).
| Field | Type | Required | Description |
|---|---|---|---|
method | string | Yes | "sampled" or "enumerated" |
forward_passes | integer | sampled only | Number of scenario trajectories per iteration. Larger values reduce variance in each iteration’s cut but increase cost per iteration. Rejected under "enumerated". |
There is no default forward-pass count: an absent training.selection is
a hard load error (LoadError::SchemaError, naming the field
training.selection, message “a forward-pass count is required via
training.selection”). The removed root-level training.forward_passes field
from pre-v0.14.0 releases is rejected as an unknown field
(deny_unknown_fields) rather than silently accepted.
Example — sampled:
{ "method": "sampled", "forward_passes": 50 }Example — enumerated (the forward pass exhaustively walks the scenario openings instead of drawing a fixed count):
{ "method": "enumerated" }Optional Fields
Section titled “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
Section titled “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".
Under the "external" scheme a class’s mean and standard deviation are derived
from its external scenario file itself, so that class’s seasonal-statistics
input (scenarios/inflow_seasonal_stats.parquet, load_seasonal_stats.parquet,
or non_controllable_stats.parquet) is optional — it is not consulted when
the class is external everywhere. A constant (σ = 0) external column is accepted
for load, NCS, and an order-0 inflow model; for an autoregressive inflow model
of order > 0 it is rejected (see Error Codes).
Example — out-of-sample inflow with in-sample load and NCS:
{ "training": { "tree_seed": 42, "selection": { "method": "sampled", "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
Section titled “Stopping Rules”Each entry in stopping_rules is a JSON object with a "type" discriminator.
iteration_limit
Section titled “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
Section titled “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
Section titled “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. |
Stop when the exact upper bound has closed to within tolerance of the lower bound. Requires an enumerated training forward pass, so the upper bound being compared is exact rather than a statistical estimate.
{ "type": "gap", "tolerance": 1000.0, "relative_tolerance": 0.01 }| Field | Type | Description |
|---|---|---|
tolerance | float | Optional. Absolute gap tolerance, in canonical R$. |
relative_tolerance | float | Optional. Relative gap tolerance in percent — 0.01 means 0.01%, compared against 100 · gap / max(1, |lower_bound|). |
At least one of tolerance / relative_tolerance must be present; a gap
rule with neither field is a load-time error: “gap stopping rule requires at
least one of tolerance / relative_tolerance to be present”. When both are
present, the two arms combine by disjunction — training stops as soon as
either arm is satisfied, not both. The gap itself is
max(0, upper_bound − lower_bound).
Admissibility — the gap rule is accepted only when both hold:
training.selection.methodis"enumerated"(the forward pass exhaustively enumerates the scenario tree, so the upper bound is exact rather than a sampled estimate).- The risk measure is uniform across every stage: either
risk_measure: "expectation"at every stage, or the samecvarmeasure (identicallambdaandalpha) at every stage instages.json(acvarwithlambda: 0is expectation-equivalent). Under a uniformcvarthe exact upper bound is computed as a nested, time-consistent risk recursion over the enumerated tree, so it still brackets the risk-averse lower bound.
Under sampled selection, or a stage-varying measure (the risk measure
differs across stages), the gap rule is rejected at load with a validation
error — the exact-bound comparison it depends on is unavailable in those
cases.
stopping_mode
Section titled “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": { "selection": { "method": "sampled", "forward_passes": 50 }, "stopping_mode": "all", "stopping_rules": [ { "type": "iteration_limit", "limit": 500 }, { "type": "bound_stalling", "iterations": 20, "tolerance": 0.0001 } ] }}parallelism
Section titled “parallelism”Groups the result-preserving worker-scheduling knobs that shape how backward-pass work is distributed across workers, alongside the algorithm-semantics fields at the training root. This is a pure addition — no existing training field moved.
backward_scheduler selects how backward-pass work units are claimed by workers. It is tagged on method; supplying a field that does not belong to the selected method is a load-time error (deny_unknown_fields), not a silently ignored key.
-
"by_scenario"(default) — each parallel work unit is one whole trial point, matching the byte-neutral pre-v0.12.0 behavior.{ "method": "by_scenario" } -
"by_node"— each parallel work unit is one(trial point, opening block)pair, claimed dynamically off a shared counter.Field Type Default Description block_sizeinteger or nullnullOpenings per block. Absent resolves per stage to ⌈|Ω_s|/2⌉(half the openings, rounded up); a set value is silently clamped tomin(|Ω_s|, block_size)— no error, no warning.Two configurations are load-time errors, not silently accepted:
block_sizesupplied under"by_scenario"— rejected (deny_unknown_fields).block_size: 0— rejected (minimum1).
Within a stage, opening-blocks are claimed hardest-first: blocks are ranked by the previous iteration’s mean simplex-pivot cost per (stage, block), descending (ties broken by ascending block index; the first iteration, and any block with no prior data, sort last). This claim order changes only which worker processes which block, and when — it is result-neutral: the produced cut set and the training lower bound are identical to canonical-order claiming.
Example — default (by_scenario; no parallelism block needed):
{ "training": { "selection": { "method": "sampled", "forward_passes": 50 }, "stopping_rules": [{ "type": "iteration_limit", "limit": 100 }] }}Example — by_node with an explicit block_size:
{ "training": { "selection": { "method": "sampled", "forward_passes": 50 }, "stopping_rules": [{ "type": "iteration_limit", "limit": 100 }], "parallelism": { "backward_scheduler": { "method": "by_node", "block_size": 4 } } }}For the backward pass this schedules work within, see SDDP Algorithm §3.4 (Execution Model and Performance Considerations).
simulation
Section titled “simulation”Controls the optional post-training simulation phase.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable the simulation phase after training. |
selection | object | null | Scenario-selection method for the simulation phase — sampled{num_scenarios} or enumerated{} (see simulation.selection below). Absent resolves to a sampled count of 2000. |
simulation.selection
Section titled “simulation.selection”Chooses how many trajectories the post-training simulation runs, or whether
it exhaustively enumerates the scenario set 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 removed flat num_scenarios field carried before this field
existed.
| Field | Type | Required | Description |
|---|---|---|---|
method | string | Yes (if present) | "sampled" or "enumerated" |
num_scenarios | integer | sampled only | Number of independent Monte Carlo simulation scenarios to evaluate. Rejected under "enumerated". |
When simulation.enabled is false, or the sampled num_scenarios count
resolves to 0, the simulation phase is skipped entirely.
simulation.solver (optional, default null) overrides the LP solver
profile used during the simulation phase. It takes the identical
PhaseSolverProfileConfig shape as training.solver.backward and
training.solver.forward — see training.solver under
Advanced Fields for the full field table, caveats, and validation ranges.
Example:
{ "simulation": { "enabled": true, "selection": { "method": "sampled", "num_scenarios": 1000 } }}scenario_source
Section titled “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": { "selection": { "method": "sampled", "forward_passes": 50 }, "stopping_rules": [{ "type": "iteration_limit", "limit": 200 }] }, "simulation": { "enabled": true, "selection": { "method": "sampled", "num_scenarios": 2000 }, "scenario_source": { "seed": 77, "inflow": { "scheme": "out_of_sample" }, "load": { "scheme": "in_sample" }, "ncs": { "scheme": "in_sample" } } }}modeling
Section titled “modeling”Controls physical modeling options.
| Field | Type | Default | Description |
|---|---|---|---|
inflow_non_negativity | object | see below | Strategy for handling negative PAR model inflow draws. |
cost_scale_factor | number | 1_000_000.0 | Divisor on every non-θ objective coefficient (objective conditioning; does not alter the model, unlike this section’s other fields). See below. |
inflow_non_negativity
Section titled “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" } }}cost_scale_factor
Section titled “cost_scale_factor”Divisor applied to every non-θ objective coefficient at LP template build
time; every cost-domain output (objective value, duals, cut coefficients) is
multiplied back by the same factor at every reporting boundary. This is
objective conditioning, not a physical modeling choice — unlike
inflow_non_negativity above, results are identical in exact arithmetic;
changing it never alters the model.
Defaults to 1_000_000.0 when absent or null (byte-identical to the
pre-v0.12.0 hard-coded behaviour). Must be finite and > 0; a value outside
[1.0, 1e12] is accepted but logs an advisory warning — it is not
rejected.
Example:
{ "modeling": { "cost_scale_factor": 1000000.0 }}For the methodology, see LP Scaling §12.1 (Cost Scaling).
cut_selection
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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. |
boundary | object or null | null | Terminal boundary cut configuration for coupling with an outer model’s FCF. See below. |
checkpointing
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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_openings": 50 }, { "id": 1, "start_date": "2024-01-08", "end_date": "2024-01-15", "season_id": 0, "num_openings": 50 }, { "id": 2, "start_date": "2024-01-15", "end_date": "2024-01-22", "season_id": 0, "num_openings": 50 }, { "id": 3, "start_date": "2024-01-22", "end_date": "2024-02-01", "season_id": 0, "num_openings": 50 }, { "id": 4, "start_date": "2024-02-01", "end_date": "2024-03-01", "season_id": 1, "num_openings": 50 }]Recommended Alternative: Weekly Blocks Within a Monthly Stage
Section titled “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
Block Formulation Variants for the chronological
formulation behind this pattern and
Case Directory Format for the
stages.json block_mode field.
See Also (Temporal Resolution)
Section titled “See Also (Temporal Resolution)”- Multi-Resolution Studies — mixed-resolution mechanics: same-season noise groups, duration-weighted observation aggregation, and PAR fitting on aggregated statistics
- Weekly+Monthly Coupled Studies — the two-study coupling alternative when true weekly-resolution noise data exists
exports
Section titled “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
Section titled “Full Example”{ "$schema": "https://docs.cobre-rs.dev/schemas/config.schema.json", "training": { "tree_seed": 42, "selection": { "method": "sampled", "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, "selection": { "method": "sampled", "num_scenarios": 2000 } }, "policy": { "path": "./policy", "mode": "fresh" }, "exports": { "states": false, "stochastic": false }}Advanced Fields
Section titled “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 retry policy and optional per-phase profile overrides (backward, forward) — see training.solver below; for the performance-tuning treatment of the same profiles, see Solver Safeguards |
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.
training.solver
Section titled “training.solver”LP solver retry policy plus optional per-phase profile overrides.
retry_max_attempts and retry_time_budget_seconds are pre-existing
fields; backward and forward are new in v0.12.0.
| Field | Type | Default | Description |
|---|---|---|---|
retry_max_attempts | integer | 5 | Maximum solver retry attempts before propagating a hard error. |
retry_time_budget_seconds | float | 30.0 | Total time budget in seconds across all retry attempts for one solve. |
backward | object or null | null | Per-phase LP solver profile override applied during the backward pass. |
forward | object or null | null | Per-phase LP solver profile override applied during the forward pass. |
simulation.solver (see simulation above) is a third,
independent sibling that takes the identical shape — training.solver.backward,
training.solver.forward, and simulation.solver all resolve against one
shared PhaseSolverProfileConfig, documented once below.
PhaseSolverProfileConfig fields
Section titled “PhaseSolverProfileConfig fields”Every field is optional and defaults to null. An absent field leaves the
corresponding option at the phase’s built-in tuned profile — not the
underlying solver’s own default profile; the two differ (for example, all
three phase profiles set price to "row_hyper_sparse", while HiGHS’s own
default pricing is "row"). A study with no solver-profile configuration at
all (no backward, forward, or simulation.solver) resolves
byte-identically to the prior, unconfigurable per-phase defaults. Resolution
happens once at setup — before any LP template is built — and the result is
broadcast identically to every rank.
| Field | Type / values |
|---|---|
dual_edge_weight | "devex" | "steepest_edge" | "dantzig" |
scale | "off" | "solver_scaling" |
price | "row" | "row_hyper_sparse" |
presolve | "on" | "off" | "choose" |
primal_feasibility_tolerance | number |
dual_feasibility_tolerance | number |
simplex_update_limit | uint32 |
cost_perturbation | number |
refactor_error_tolerance | number |
factor_pivot_threshold | number |
use_warm_start | boolean |
steepest_edge_devex_fallback_threshold | number |
presolveaffects only a genuinely cold solve — a warm-started solve skips presolve regardless of the setting.use_warm_startis a diagnostic override, not an intended production setting: setting it tofalseforces every solve in the phase cold.dual_edge_weight: "steepest_edge"is a request, not a guarantee — HiGHS silently falls back to Devex pricing once the dual steepest-edge weight log-error exceedssteepest_edge_devex_fallback_threshold.
Validation
Section titled “Validation”Seven fields are range-checked; a value outside its range is rejected at config-load time with an error naming the phase and the offending field:
primal_feasibility_tolerance— must be finite.dual_feasibility_tolerance— must be finite and>= 1e-10.simplex_update_limit— must be<= 2147483647(i32::MAX).cost_perturbation— must be finite and>= 0.refactor_error_tolerance— must be finite and>= 0.factor_pivot_threshold— must be in[8e-4, 0.5].steepest_edge_devex_fallback_threshold— must be finite and>= 1.0.
The closed enums (dual_edge_weight, scale, price, presolve) and
use_warm_start are unconditionally valid — every variant is supported.
CLP backend
Section titled “CLP backend”Per-phase solver-profile overrides are HiGHS-only. On the CLP backend, any
field set under backward, forward, or simulation.solver is rejected at
setup — deterministically, on every rank, before any LP template is built —
with a named error identifying the phase and the field. An empty override
block (for example "solver": {} or "backward": {}) and an absent block
are both legal on CLP; only a field that is actually set triggers rejection.
See Installation — Choosing a Backend for
backend selection and CLP’s other limitations.
Example — training.solver.backward with two overrides:
{ "training": { "solver": { "retry_max_attempts": 5, "retry_time_budget_seconds": 30.0, "backward": { "dual_edge_weight": "steepest_edge", "factor_pivot_threshold": 0.05 } } }}See Also
Section titled “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 - Multi-Resolution Studies — how stage resolution affects PAR noise sharing (noise groups) and observation aggregation