0.6.2 - 2026-05-20
Release Notes
Added
- New
allow_curtailment: boolfield onNonControllableSource(default
true, preserving existing case behaviour). When set tofalseon an
entity, the LP pins its generation column to the realized availability
on every scenario: lower and upper column bounds both equal
max_generation_mw * alpha * block_factor, where
alpha = clamp(mean + std*eta, 0, 1)is the per-(stage, scenario)
availability ratio drawn fromnon_controllable_stats.parquetand
block_factoris the per-(stage, block) shape factor from
non_controllable_factors.json. Use this on NEWAVE-derived
geracao_usinas_nao_simuladasaggregates (PCH, PCT, EOL, UFV, MMGD)
that the source model pre-nets fromMERC; with the default Cobre
schema the LP was free to curtail these because curtailment is one of
the cheapest LP slacks, leading on the bundled deterministic 1983 case
to ≈ 18 % of total NCS supply being curtailed, a ≈ +15 % hydro-dispatch
swing, and ≈ −23 % spillage versus NEWAVE. Setting
allow_curtailment = falseon the must-run aggregates restores
dispatch parity with NEWAVE while preserving per-source observability
in the simulation outputs. JSON schema accepts the field as optional;
absent →true. ModelProvenanceReport.historical_library_past_inflows_digest: Option<u64>
records a SipHash-1-3 fingerprint ofinitial_conditions.past_inflows
when the historical scenario scheme is active. Downstream consumers can
compare the stored digest against the currentpast_inflowsto detect
a stale historical library that needs re-standardisation. Serialised
under#[serde(skip_serializing_if)]so non-historical runs keep their
JSON provenance unchanged. The cobre-python production path populates
the field fromsetup.scenario_libraries.training.historical.
Changed
- API (breaking, hydro penalty rename):
HydroPenalties.fpha_turbined_cost
is renamed toHydroPenalties.turbined_cost, cascading through
RawHydroPenalties, thepenalty_overrides.parquetcolumn, the output
schemas, and thesimulation_writerrecord. The turbined-flow
regularisation cost is now applied universally to every hydro's turbine
column in the LP objective (previously gated behindis_fphain
fill_turbine_columns, so constant-productivity plants paid nothing and
diverged from NEWAVE). The deadturbined_costfield on
ResolvedProductionModel::Fphais dropped (the cost now lives in the
penalty cascade). A latent extraction bug is fixed in the process:
SimulationCostResult.turbined_costwas summingprimal*objover
indexer.generation(FPHA generation columns, objective = 0), so the
field was silently zero in real runs; it now sums overindexer.turbine
where the cost lives. JSON schemas regenerated viacobre schema export
with no hand edits. Two LB regression pins shift by ≈ 0.10 % under the
new universal cost (basis_reconstruct_churn::PINNED_FINAL_LB,
deterministic::D03_EXPECTED_COST). - API (breaking, PAR primitives): dropped the explicit
order: usize
parameter fromevaluate_par,evaluate_par_inflow,solve_par_noise, and
removed the equivalentpar.order(h)inner-loop bound from
evaluate_par_batch/solve_par_noise_batch/solve_par_noises/
evaluate_par_inflows. All four primitives now iterate the full
psi.iter()slice (viapsi.len()in the batch variants), and require
lags.len() >= psi.len().psi.len()is the authoritative number of lag
terms in the model — equal to the AR order for classical PAR(p) and to 12
when PAR(p)-A annual is active (the materialised annual coefficient is
spread across the extra positions inPrecomputedPar::psi_slice). Callers
that previously truncated to AR order silently dropped the annual
contribution; see the matchingFixedentry below.
Fixed
-
PAR(p)-A annual coefficients were omitted from the cut sparse mask,
producing over-estimating Benders cuts and LB > UB at convergence.
StudySetup::newwas passingpar.order(h)(the classical AR order) as
the per-hydro lag-state-slot count toStageIndexer::set_nonzero_mask.
With PAR(p)-A active,PrecomputedPar::psi_slicewidens to
max_order = 12and the standardised annual coefficientψ̂/12fills
positionsp..12. The LP loads all 12 psi entries into the AR-dynamics
row, but the cut sparse mask omitted state coefficients on lag slots
p..12— so cut rows built viabuild_cut_row_batch_intowere missing
the trailing state dependencies. At the visited state this shifted the
cut hyperplane above the true LP value, producing systematically
over-estimating cuts. On the bundled NEWAVE 1983 case (Camargos
AR(4)+A on every hydro), the gap converged to LB > UB by ≈ 7 % with 41
of 50 iterations recording negative gaps; disabling annual via
order_selection = pacfcollapsed the gap to 0.05 %. After the fix the
same case withpacf_annualconverges to 0.003 % final gap with zero
negative-gap iterations across all 50.PrecomputedParnow records a
per-hydrohas_annual: Box<[bool]>at build time and exposes
effective_lag_count(h)(returnsmax_orderwhenhas_annual[h],
elseorders[h]);StageIndexer::set_nonzero_mask'sar_orders
parameter is renamed tolag_countsto reflect its true meaning. New
regression testnonzero_mask_par_a_includes_full_psi_stridepins
the contract. This is the direct analog of the η-inversion bug fixed
above — sameorder-vs-psi.len()confusion, cut-construction path
instead of standardisation path. -
PAR(p)-A annual was silently dropped at standardisation time when the
scheme washistoricalorexternal.standardize_historical_windows
(and the analogous external path) built their per-hydrolag_bufonly up
topar.order(h)lag slots and passedorder_has the iteration bound to
solve_par_noise. With PAR-A active,PrecomputedPar::psi_slicereturns
12 entries (AR coefficients in slots0..order, plus the spread annual
coefficientψ̂/12in slotsorder..12), all of which the LP loads via
lp_builder/matrix.rs:834-841. The standardisedηtherefore omitted the
annual contribution from lagsorder..12, while the LP applied it in
full — so forward replays produced biased inflows even when the stage-0
lag state matched the window's pre-study lags exactly. Worst-case observed
on the bundled NEWAVE 1983-anchored case (Camargos, AR(4) +
annual_coefficient ≈ −0.225): a ≈ 11 % shortfall vs the raw historical
observation. After the fix, the same case reconstructs every hydro at
stage 0 to within 10⁻¹² of the historical observation. The fix combines
the API change above with two callsite updates that now fill the full
psi.len()lag slots instandardize_historical_windowsand
standardize_external_inflow. New regression test
crates/cobre-stochastic/tests/par_a_historical_replay.rspins the
invariant directly. -
ClassSampler::Historical::apply_initial_stateno longer overwrites the
inflow-lag portion of the stage-0 state vector with the window-preceding
raw historical inflows. Previously, the forward pass replayed the lag
state of the historical year being sampled (e.g. when scenariom
replayed 1983, the forward LP started from the 1982-Q4 lag values), while
the lower-bound and backward-pass evaluators kept the user-supplied
initial_conditions.past_inflowslags. The two paths therefore evaluated
V₀ at differentx_0on every historical-replay case, producing a
structural, typically negative SDDP gap (≈ −19 % on the bundled
NEWAVE-derived 1983 deterministic case) that did not close with
iteration count. The historical window now contributes only its
standardized noise residuals viafill; the initial inflow lags come
uniformly frominitial_conditions.past_inflowsfor every scenario,
matching NEWAVE'sTENDENCIA HIDROLOGICAconvention. Cases using
InSample,OutOfSample, orExternalschemes are unaffected
(bit-identical output — those variants were already no-ops). Cases using
theHistoricalscheme will see different forward upper bounds and
meaningful gap closure to cut tightness. -
Historical η inversion re-rooted at
past_inflows(LB/UB x₀ sharing).
standardize_historical_windowspreviously inverted the PAR noise residual
η for each window stage usingwindow_pre_study_lagsas the lag state seed
— i.e. the raw historical inflows from the year before each window year.
After commit dc96030 (apply_initial_stateno-op), the SDDP forward pass
starts every scenario frominitial_conditions.past_inflows(the
user-supplied x₀), not from the window-preceding lags. The two paths
therefore built their lag chains from different x₀, producing a
systematic per-stage offsetz_h = target + Σψ·(past_inflows − window_lag)that propagated through all stages and prevented exact
historical replay even at stage 0 after d0e4a42. The fix re-roots the
inversion on a rolling lag chain seeded frompast_inflows, following the
same accumulate/finalize pattern asstandardize_external_inflow. The
rolling chain is advanced each month viaStageLagTransition; uniform-
monthly transitions are used by default, with noop transitions (produced
by an emptySeasonMap) falling back to uniform-monthly in the inner loop.
Apast_inflows_digest: u64field (SipHash-1-3 of allpast_inflows
values) is stored onHistoricalScenarioLibraryto enable stale-library
detection whenpast_inflowschange between calls. Adebug_assert!
guard fires whenstage_lag_transitionscontain non-trivial (non-monthly)
entries, markingTODO(historical-replay-non-monthly)for future work on
sub-monthly and multi-monthly study configurations.
build_historical_inflow_libraryandbuild_opening_tree_libraryboth
forwardpast_inflowsandstage_lag_transitionsto
standardize_historical_windows. After the fix, every historical-scheme
forward pass that starts frompast_inflowsreconstructs the raw
historical observation to within floating-point precision at every stage,
restoring LB/UB consistency at x₀. New tests T2–T6 in
crates/cobre-stochastic/tests/par_a_historical_replay.rspin the
invariant for differingpast_inflows, AR(0), truncated lag vectors,
multi-window cases, and the non-trivial-transition guard.
cobre-cli 0.6.2
Install cobre-cli 0.6.2
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/cobre-rs/cobre/releases/download/v0.6.2/cobre-cli-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/cobre-rs/cobre/releases/download/v0.6.2/cobre-cli-installer.ps1 | iex"Download cobre-cli 0.6.2
| File | Platform | Checksum |
|---|---|---|
| cobre-cli-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| cobre-cli-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| cobre-cli-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| cobre-cli-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| cobre-cli-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |
cobre-mcp 0.6.2
Install cobre-mcp 0.6.2
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/cobre-rs/cobre/releases/download/v0.6.2/cobre-mcp-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/cobre-rs/cobre/releases/download/v0.6.2/cobre-mcp-installer.ps1 | iex"Download cobre-mcp 0.6.2
| File | Platform | Checksum |
|---|---|---|
| cobre-mcp-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| cobre-mcp-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| cobre-mcp-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| cobre-mcp-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| cobre-mcp-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |