Python Quickstart
Este conteúdo não está disponível em sua língua ainda.
Install the cobre Python package, scaffold the 1dtoy example case, run it with cobre.run.run(), then read and plot the results. The remaining sections change the configuration, validate a case and handle errors.
Install
Section titled “Install”pip install cobre-python polars pyarrow matplotlibThe package imports as cobre; polars, pyarrow and matplotlib serve the read and plot steps. Installation lists the supported Python versions and platforms.
Scaffold a Case
Section titled “Scaffold a Case”The Python package has no scaffold command. Create the 1dtoy example case with the cobre command-line tool from Installation:
cobre init --template 1dtoy my_studyAlternatively, copy the repository’s examples/1dtoy at the v0.18.0 tag to my_study/. Run the snippets below, in order, from the directory that holds my_study/.
Run the Study
Section titled “Run the Study”cobre.run.run() loads the case, trains an SDDP policy, simulates 100 scenarios and writes the results to my_study/output. It returns a dict:
import cobre
result = cobre.run.run("my_study")print(f"converged: {result['converged']}")print(f"iterations: {result['iterations']}")print(f"lower_bound: {result['lower_bound']:.6g}")print(f"upper_bound: {result['upper_bound']:.6g}")print(f"gap_percent: {result['gap_percent']:.1f}")print(f"simulation: {result['simulation']}")print(f"output_dir: {result['output_dir']}")converged: Falseiterations: 128lower_bound: 1.55955e+07upper_bound: 579592gap_percent: -96.3simulation: {'n_scenarios': 100, 'completed': 100}output_dir: my_study/outputThe printed keys are part of the result dict; see RunResult for the full shape and cobre.run.run for the parameters. converged is True when a gap or bound_stalling rule stops training. Here the 128-iteration limit stopped it, so False is expected. The bounds and the negative gap match the CLI quickstart, which explains them in What You Just Ran.
Read the Results
Section titled “Read the Results”Read the convergence record and the simulation costs back from the output directory:
import cobreimport polars as pl
conv = pl.from_arrow(cobre.results.load_convergence_arrow("my_study/output"))print(conv.select("iteration", "lower_bound", "upper_bound", "gap_percent").tail(3))
costs = pl.from_arrow( cobre.results.load_simulation_arrow("my_study/output", entity_type="costs"))per_scenario = costs.group_by("scenario_id").agg( (pl.col("discount_factor") * pl.col("immediate_cost")).sum().alias("cost"))print(per_scenario.select( pl.col("cost").mean().alias("mean"), pl.col("cost").median().alias("median"),))shape: (3, 4)┌───────────┬─────────────┬───────────────┬─────────────┐│ iteration ┆ lower_bound ┆ upper_bound ┆ gap_percent ││ --- ┆ --- ┆ --- ┆ --- ││ i32 ┆ f64 ┆ f64 ┆ f64 │╞═══════════╪═════════════╪═══════════════╪═════════════╡│ 126 ┆ 1.5596e7 ┆ 615923.497494 ┆ -96.050638 ││ 127 ┆ 1.5596e7 ┆ 615923.497494 ┆ -96.050638 ││ 128 ┆ 1.5596e7 ┆ 579592.198622 ┆ -96.283598 │└───────────┴─────────────┴───────────────┴─────────────┘shape: (1, 2)┌──────────┬───────────────┐│ mean ┆ median ││ --- ┆ --- ││ f64 ┆ f64 │╞══════════╪═══════════════╡│ 9.6794e6 ┆ 615923.497494 │└──────────┴───────────────┘The two cobre.results loaders return pyarrow.Tables, which polars.from_arrow wraps. A scenario’s cost is the sum of discount_factor * immediate_cost over its costs rows, one per stage here; the mean matches the Expected cost line of the CLI run summary, and the median, far below the mean, shows that most scenarios are cheap.
Plot the Convergence
Section titled “Plot the Convergence”Plot both bounds against the iteration:
import cobreimport matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltimport polars as pl
conv = pl.from_arrow(cobre.results.load_convergence_arrow("my_study/output"))fig, ax = plt.subplots()ax.plot(conv["iteration"], conv["lower_bound"], label="lower bound")ax.plot(conv["iteration"], conv["upper_bound"], label="upper bound")ax.set_yscale("log")ax.set_xlabel("iteration")ax.set_ylabel("expected cost")ax.legend()fig.savefig("convergence.png")print("saved convergence.png")saved convergence.pngThe saved convergence.png uses a log axis. The lower bound rises from about 5.1e6 and flattens near 1.56e7. The upper bound is the cost of one forward pass per iteration; it sits near 6e5 with spikes above 1e8. matplotlib.use("Agg") draws to a file, so the script needs no display.
Change the Configuration
Section titled “Change the Configuration”config_overrides changes config.json settings for one call without editing the file. This call reruns the study with another seed:
import cobre
result = cobre.run.run( "my_study", output_dir="my_study/output-seed7", config_overrides={"training.tree_seed": 7},)print(f"lower_bound: {result['lower_bound']:.6g}")lower_bound: 9.7055e+06The keys are dotted config.json paths. training.tree_seed seeds the opening tree and the in-sample draws (default 42), so another seed gives another tree and another lower bound: 9.7055e+06 here, against 1.55955e+07 for the first run. Pass nested settings as dotted keys: a dict under a non-dotted key replaces that whole object. output_dir keeps this run apart from the first. Seed resolution describes the seeds.
A run with training disabled simulates the policy already in the output directory, here the one the first run wrote:
import cobre
result = cobre.run.run("my_study", config_overrides={"training.enabled": False})print(f"iterations: {result['iterations']}")print(f"upper_bound: {result['upper_bound']}")print(f"gap_percent: {result['gap_percent']}")iterations: 0upper_bound: 579592.1986224409gap_percent: NoneWith training disabled and simulation enabled, iterations is 0, gap_percent is None, and upper_bound is the last training upper bound recorded in the policy checkpoint the run loads (None if it records no finite one). With both phases disabled, lower_bound is 0.0 and upper_bound, gap_percent and simulation are None. The threads argument of cobre.run.run sets the worker count (default 1); 0 raises ValueError.
Validate a Case
Section titled “Validate a Case”cobre.io.validate checks a case directory without running it:
import cobre
print(cobre.io.validate("my_study")){'valid': True, 'errors': [], 'warnings': []}The dict has the keys valid, errors and warnings; each entry of errors is a dict with kind and message. cobre.Study("my_study").validate() returns a dict with the same keys.
Error Handling
Section titled “Error Handling”A setting that fails validation raises a typed exception. This call misspells a config key:
import cobre
try: cobre.run.run("my_study", config_overrides={"training.tree_seeds": 7})except cobre.errors.CobreError as err: print(type(err).__name__, isinstance(err, ValueError))ValidationError TrueThe typed exceptions are in cobre.errors. Each specific class derives from the base CobreError and from a builtin (ValueError, OSError or RuntimeError), so an existing except ValueError also catches this ValidationError. Argument checks such as threads=0 or a missing case directory raise the builtin directly. See cobre.errors for the classes, Error Codes for the messages and Policy-load errors for which load path raises which class.
Policy Checkpoints
Section titled “Policy Checkpoints”Load a trained policy with cobre.results.load_policy, edit its cuts, and write a checkpoint Cobre can load with
cobre.write_policy_checkpoint. The round-trip how-to is
Read and Write Checkpoints from Python.
Next Steps
Section titled “Next Steps”- See Case Format for input file specifications.
- Explore the Worked Examples for small, fully worked cases.