Python Quickstart
Install Cobre and run a study in a few steps.
Installation
Section titled “Installation”pip install cobre-pythonRequires Python 3.12, 3.13, or 3.14.
Run a Case
Section titled “Run a Case”import cobre
result = cobre.run.run("path/to/case")The cobre.run.run() function loads the case, trains an SDDP policy, optionally
runs simulation, and writes output files. It returns a dictionary with the
following keys:
| Key | Type | Description |
|---|---|---|
converged | bool | Whether training converged |
iterations | int | Number of training iterations completed |
lower_bound | float | Final lower bound |
upper_bound | float or None | Final upper bound (None if no simulation) |
gap_percent | float or None | Optimality gap percentage (None if unavailable) |
total_time_ms | int | Total wall-clock time in milliseconds |
output_dir | str | Path to the output directory |
simulation | dict or None | Simulation summary (if enabled) |
stochastic | dict or None | Stochastic preprocessing summary |
hydro_models | dict or None | Hydro model summary |
provenance | dict | Build version and environment metadata |
print(f"Converged: {result['converged']}")print(f"Iterations: {result['iterations']}")print(f"Lower bound: {result['lower_bound']:.2f}")if result['gap_percent'] is not None: print(f"Gap: {result['gap_percent']:.2f}%")print(f"Output dir: {result['output_dir']}")Optional Parameters
Section titled “Optional Parameters”result = cobre.run.run( "path/to/case", output_dir="path/to/output", # default: case_dir/output threads=4, # default: 1 skip_simulation=True, # default: False)Read Output with Polars
Section titled “Read Output with Polars”Cobre writes results as Parquet files, which can be loaded directly with Polars or any Arrow-compatible library:
import polars as pl
# Convergence trajectoryconvergence = pl.read_parquet("output/training/convergence.parquet")print(convergence.head())
# Simulation costs (if simulation was enabled) — Hive-partitionedcosts = pl.read_parquet("output/simulation/costs/")print(costs.describe())Arrow Zero-Copy Loading
Section titled “Arrow Zero-Copy Loading”For larger datasets, use the built-in Arrow loaders that avoid serialization overhead:
# Returns a pyarrow.Table (zero-copy)convergence_table = cobre.results.load_convergence_arrow("output/")simulation_tables = cobre.results.load_simulation_arrow("output/")
# Convert to Polars without copyingimport polars as pldf = pl.from_arrow(convergence_table)Policy Checkpoints
Section titled “Policy Checkpoints”A trained policy is a checkpoint of Benders cuts on disk (the on-disk
AffinePiece layout is documented in the
FlatBuffers Policy Schema).
cobre.results.load_policy reads a checkpoint into plain Python dicts, and
cobre.write_policy_checkpoint writes one from dicts of the same shape — so a
loaded checkpoint round-trips: load, edit, write. See
Policy Management for the cross-study
boundary-cuts workflow this API supports.
Load a Policy (load_policy)
Section titled “Load a Policy (load_policy)”cobre.results.load_policy(output_dir, policy_subdir="policy")output_dir is the study’s output directory; load_policy reads the
checkpoint from <output_dir>/<policy_subdir> (policy_subdir defaults to
"policy"). It returns a dict with three top-level keys:
| Key | Type | Description |
|---|---|---|
metadata | dict | format_version, cobre_version, created_at, num_stages, graph_manifest, producer |
stage_cuts | list[dict] | One entry per stage — see below |
stage_bases | list[dict] | One entry per saved LP basis: stage_id, iteration, column_status, row_status, num_cut_rows |
Each stage_cuts entry carries:
| Key | Type | Description |
|---|---|---|
stage_id | int | Stage index |
state_dimension | int | Length every cut’s coefficients must have |
capacity | int | Cut-pool slot capacity |
warm_start_count | int | Cuts loaded from a previous artifact at run start |
populated_count | int | Number of populated cut slots |
entity_manifest | list[dict] | Per-slot entity markers — see below |
cuts | list[dict] | Cut records — see below |
Each entry in cuts carries cut_id, slot_index, iteration,
forward_pass_index, intercept, coefficients, and is_active. The
Python dict key for a cut’s id is cut_id (the FlatBuffers wire field is
piece_id; cut_id is the record-level name used everywhere in this API).
entity_manifest is new this release: a list of per-slot markers,
{entity_type, entity_id, subindex, was_active, delivery_date}, that let an
externally-authored boundary-cut checkpoint round-trip through
write_policy_checkpoint without losing slot identity. Its delivery marker
is delivery_date — a YYYYMMDD calendar date — which replaced the former
delivery_anchor (a month-integer).
Write a Checkpoint (write_policy_checkpoint)
Section titled “Write a Checkpoint (write_policy_checkpoint)”write_policy_checkpoint(path, stage_cuts, metadata, stage_bases=None, stage_states=None, inflow_lag_depth=None)stage_cuts and metadata mirror the load_policy shapes above (the dict
shape is unchanged from earlier releases), so a checkpoint loaded from disk
round-trips through Python without reshaping. inflow_lag_depth, when set to
N > 0, reserves N canonical HydroInflowLag state slots per storage hydro
and places each cut’s inflow_lag_coefficients at their (hydro, depth)
positions (for authoring a boundary policy of a case with no autoregressive
inflow model); absent or 0 it is byte-identical.
write_policy_checkpoint writes manifest.bin, cuts/, basis/, and
states/ directly under path — pass a policy/-style directory as path,
then load with load_policy on its parent directory (which joins
policy_subdir="policy" onto it).
The following example is copy-runnable and mirrors the shipped test fixture
(crates/cobre-python/tests/test_write_policy_checkpoint.py): it authors a
checkpoint from scratch, writes it, loads it back, appends a third cut, and
writes the edited policy back to the same path.
import tempfile
import cobreimport cobre.results
parent = tempfile.mkdtemp()
stage_cuts = [ { "stage_id": 0, "state_dimension": 3, "capacity": 10, "cuts": [ { "cut_id": 1, "slot_index": 0, "iteration": 1, "forward_pass_index": 0, "intercept": 42.0, "coefficients": [1.0, 2.0, 3.0], "is_active": True, }, { "cut_id": 2, "slot_index": 1, "iteration": 1, "forward_pass_index": 1, "intercept": 10.5, "coefficients": [0.5, -1.5, 2.5], "is_active": True, }, ], }]
metadata = { "cobre_version": "0.13.0", "created_at": "2026-07-30T00:00:00Z", "num_stages": 1, "producer": { "completed_iterations": 5, "final_lower_bound": 123.45, "best_upper_bound": 130.0, "max_iterations": 10, "forward_passes": 4, "warm_start_cuts": 0, "warm_start_counts": [0], "rng_seed": 42, "total_visited_states": 0, "training_block_mode": "parallel", "training_block_mode_per_stage": [], "cost_scale_factor": 2_500_000.0, },}
# Write to `<parent>/policy` — `load_policy` joins policy_subdir="policy" onto# the directory it is given, so it is loaded from `parent` below.cobre.write_policy_checkpoint(f"{parent}/policy", stage_cuts, metadata)
loaded = cobre.results.load_policy(parent)assert loaded["metadata"]["format_version"] == 1assert loaded["stage_cuts"][0]["populated_count"] == 2
# Edit: append a third cut. Its coefficients must match state_dimension (3).cuts = loaded["stage_cuts"][0]["cuts"]cuts.append( { "cut_id": 3, "slot_index": 2, "iteration": 2, "forward_pass_index": 0, "intercept": -5.0, "coefficients": [4.0, -2.0, 0.0], "is_active": True, })loaded["stage_cuts"][0]["populated_count"] = len(cuts)
# Write back: same round-trip shape, one more cut than before.cobre.write_policy_checkpoint( f"{parent}/policy", loaded["stage_cuts"], loaded["metadata"])Input Validation
Section titled “Input Validation”write_policy_checkpoint raises ValueError on two malformed-input shapes,
naming the offending stage and cut (or stage) in the message.
A cut whose coefficients length disagrees with its stage’s
state_dimension:
stage_cuts = [ { "stage_id": 0, "state_dimension": 3, "capacity": 10, "cuts": [ { "cut_id": 1, "slot_index": 0, "iteration": 1, "forward_pass_index": 0, "intercept": 42.0, "coefficients": [1.0, 2.0], # length 2, but state_dimension=3 "is_active": True, }, ], }]
cobre.write_policy_checkpoint("output/policy", stage_cuts, metadata)# ValueError: stage 0 cut 1: coefficients has 2 entries, expected state_dimension=3A stage_states entry whose flat data length disagrees with
count * state_dimension:
stage_states = [ { "stage_id": 0, "state_dimension": 3, "count": 2, "data": [1.0, 2.0, 3.0], # length 3, but count * state_dimension = 6 }]
cobre.write_policy_checkpoint( "output/policy", stage_cuts, metadata, stage_states=stage_states)# ValueError: stage 0: states data has 3 entries, expected count*state_dimension=6Next Steps
Section titled “Next Steps”- See the case directory format for input file specifications.
- Explore the examples for ready-to-run cases.
- Read the Jupyter quickstart notebook for a complete end-to-end workflow with visualization.