Cut Management
Este conteúdo não está disponível em sua língua ainda.
Purpose
Section titled “Purpose”This spec defines the complete Benders cut lifecycle in Cobre: what cuts represent mathematically, how cut coefficients relate to LP dual variables, how per-scenario cuts are aggregated into a single cut, under what conditions cuts are valid, and what selection strategies are available to control cut pool growth.
1. Cut Definition
Section titled “1. Cut Definition”A Benders cut at stage is a linear inequality that provides a lower bound on the cost-to-go function :
where:
- is the future cost variable in the stage LP
- is the cut intercept
- is the storage coefficient for hydro (marginal value of water)
- is the AR lag coefficient for hydro , lag (marginal value of inflow history)
- , are the state variables (see LP Formulation)
The cut carries a coefficient for each enabled state dimension: storage and AR-lag dimensions are included according to the stage’s state_variables selection (§2), while in-transit travel-time buckets and anticipated-thermal slots, when present, are always projected in. Within that projected subspace the coefficients are dense — every included coordinate has a non-zero coefficient in every cut — a consequence of the column-bound pinning used for reduced-cost extraction (§2).
Cobre adds cuts monotonically across iterations: an active cut is never removed from the lower-bound LP within a training run. Because the cut set only grows, the lower-bound estimate is non-decreasing across iterations of a training run. This is a methodology-level guarantee — the outer approximation of the value function can only become tighter iteration by iteration.
2. Reduced-Cost Extraction
Section titled “2. Reduced-Cost Extraction”After solving the stage subproblem for trial state and scenario , the cut coefficients are derived from the LP reduced costs of the pinned incoming-state columns — the columns whose lower and upper bounds were set equal to the incoming state value (see LP Formulation §4a).
Both storage and inflow lags use the same pattern: an incoming-state LP variable is pinned to the trial value by equal column bounds, and the reduced cost of that column gives the cut coefficient directly:
| Pinned column | Reduced cost | Cut coefficient | Units |
|---|---|---|---|
| Incoming storage (hydro ) | $/hm³ | ||
| AR lag (hydro , lag ) | $/(m³/s) |
where is the per-column prescaler factor that unscales the reduced cost; a further factor converts to original cost units at the reporting boundary (see LP Formulation §12). When anticipated thermals are present, each anticipated-state slot column contributes one coefficient by the same rule.
The cut intercept ensures the cut passes through the trial point:
where is the optimal objective value of the stage subproblem.
Sign convention: By the LP envelope theorem, , the sensitivity of the optimal value to the pinned incoming state. For a column pinned at , that sensitivity is exactly the column’s reduced cost — equal, by KKT parity, to the multiplier the former equality row would have carried — so the cut coefficient is taken directly from the reduced cost with no sign flip (after the per-column unscaling above). The reduced cost automatically captures all downstream effects: for storage, this includes contributions from the water balance, FPHA hyperplanes, and any generic constraints that reference the incoming storage variable (see LP Formulation).
Cut dimension: the enabled state subset
Section titled “Cut dimension: the enabled state subset”A stage does not have to project its cuts onto the full incoming state. Each stage carries a state_variables selection — { storage, inflow_lags }, defaulting to storage-only (inflow_lags off) — that governs the dimension of the cuts it emits, distinct from which state it reports. A dimension that is switched off is genuinely projected out of the subgradient: its reduced cost is never read into the cut, and the cut carries no coefficient for it — the result is a lower-dimensional supporting hyperplane, not a full cut with the omitted coordinates zero-padded.
- With
inflow_lagsoff (the default), a cut spans storage alone, even when the study fits a PAR() inflow model — the lag coefficients are omitted, not zeroed. - With
inflow_lagson, the AR-lag reduced costs (§2) enter the cut as usual, giving the full storage-plus-lag cut. - In-transit travel-time buckets and anticipated-thermal slots are always projected in, independent of this selection.
The cut dimension of a stage’s pool is set by its successor stage’s selection, since the successor generates the cost-to-go the pool approximates; the terminal pool is always full-dimensional. In a multi-rank run each stage’s cut synchronization derives its wire layout from that stage’s own cut dimension. Fitting a PAR() model but disabling inflow_lags on every stage — so the fitted inflow memory never reaches any cut — is surfaced as a model-quality warning. The main use of a storage-only projection under a PAR() model is to couple a PAR() study to a storage-only downstream boundary.
3. Single-Cut Aggregation
Section titled “3. Single-Cut Aggregation”In the single-cut formulation, per-scenario cuts from the backward pass are aggregated into one cut per trial point by taking the probability-weighted expectation:
where is the probability of scenario .
The aggregated cut is added to stage ‘s cut pool.
4. Cut Validity
Section titled “4. Cut Validity”A cut is valid if it is a lower bound on the true cost-to-go function everywhere in the feasible state space:
Validity conditions: The cuts generated by SDDP are valid under:
- Convexity of stage subproblems — guaranteed because all subproblems are LPs
- Relatively complete recourse — feasibility for all states and scenarios. In Cobre, this is guaranteed by the recourse slack system (Category 1 penalties): every constraint that could be violated by exogenous uncertainty has a penalty slack variable, ensuring the LP is always feasible. See Penalty System.
- Correct sensitivity extraction — the reduced costs used as cut coefficients must come from an optimal LP solution (not an infeasible or unbounded one)
5. Cut Growth and Selection Motivation
Section titled “5. Cut Growth and Selection Motivation”The number of cuts grows as . Many older cuts become redundant as newer, tighter cuts are generated. Without selection, the number of cut rows the LP carries grows linearly with iteration count.
Append-only pool with stable slots: Cuts are never deleted. Every cut ever generated is retained for the lifetime of the run at a stable, deterministic slot index — the slot is a fixed function of the iteration and forward-pass index, which is what makes the cut order reproducible across runs and rank counts (see Determinism Guarantees). The pool is never compacted.
Deactivation mechanism (periodic-pruning methods): A deactivated cut keeps its slot but is excluded from the per-iteration template rebake, so only active cuts are encoded as LP rows on each forward/backward solve. In the persistent lower-bound LP — where cut rows are never structurally removed, so the bound stays monotone — deactivation instead toggles the cut row’s bound to a trivially-satisfied sentinel. Both routes preserve the slot index for reproducibility and make reactivation exact: a cut that selection later restores is re-baked (or its bound restored) at the same slot.
Two families of selection strategy are available. Periodic-pruning methods (Level-1, LML1, Domination) run a value-evaluation pass after each -th iteration and deactivate redundant cuts from the pool. Dynamic Cut Selection (DCS) takes a different approach: it keeps the pool entirely append-only — never deactivating any cut — and instead controls which cuts are resident in each individual stage LP solve (§8).
6. Cut Activity
Section titled “6. Cut Activity”Cut selection works from a value-evaluation view of activity. At a visited forward-pass trial point , each cut’s value is , and the per-state best value is
taken over all populated cuts, active and inactive. A cut is active (near-optimal) at when its value lies within a tolerance band of the best:
This is equivalent to the cut being binding (or near-binding) at the LP optimum reached from — a cut at the per-state maximum is the one the future-cost variable rests on. The tolerance is the strategy’s tie-breaking band: tie_tolerance for Level-1/LML1, domination_tolerance for Domination (§10).
| Tolerance | Effect |
|---|---|
| 0 | Only exact-maximum cuts count as active |
| 1e-10 | Default: ties within rounding of the maximum are kept active |
| larger | Wider near-optimal band retained |
| very large | All cuts considered active (no deactivation) |
7. Periodic-Pruning Strategies
Section titled “7. Periodic-Pruning Strategies”Three periodic-pruning strategies are available, in increasing order of aggressiveness. All three share one value-evaluation kernel: every populated cut (active and inactive) is evaluated at every visited trial point, the per-state maximum is taken, and a per-state survival rule decides which cuts to keep. The kernel treats deactivation and reactivation symmetrically — in a single pass, a selected cut that is currently inactive is reactivated and an active cut not selected anywhere is deactivated. It is bit-deterministic regardless of thread count (see Determinism Guarantees).
Visited-states window: these strategies score cuts against the trial points held in the visited-states archive. To bound memory on long runs, the archive keeps only the most recent trial points — roughly those gathered over the last check_frequency iterations. After each selection run the archive is trimmed to that window, so a run sees up to about two windows of accumulated states before the trim; older trial points are then evicted. Dynamic Cut Selection (§8) does not use this archive.
7.1 Level-1
Section titled “7.1 Level-1”At each visited trial point, every cut within tie_tolerance of the per-state maximum value survives; the selected set is the union of these near-maximum cuts across all visited states. A cut is deactivated only if, at every visited state, its value is more than tie_tolerance below the maximum there. This strategy was originally proposed by de Matos, Philpott & Finardi (2015).
Properties:
- Least aggressive — retains any cut that is near-optimal at some visited state
- Preserves the convergence guarantee (see section 9)
7.2 Limited Memory Level-1 (LML1)
Section titled “7.2 Limited Memory Level-1 (LML1)”Like Level-1, but at each visited state only the single oldest eligible cut within tie_tolerance of the maximum survives (oldest = smallest slot index among non-warm-start cuts). The selected set is the union of these oldest-at-maximum cuts across visited states. Bandarra & Guigues (2021).
Properties:
- More aggressive than Level-1 — when several cuts tie at a state, only the oldest is kept, so redundant near-duplicates are shed faster
- Preserves the convergence guarantee (see section 9)
7.3 Domination
Section titled “7.3 Domination”A cut is dominated if, at every visited state, the maximum over all populated cuts exceeds its value by more than domination_tolerance. Dominated cuts contribute nothing to the policy at any visited state and are deactivated; inactive cuts that achieve the maximum somewhere are reactivated. Formally, cut is dominated when
This is the same max-survival logic as Level-1, using domination_tolerance in place of tie_tolerance.
Properties:
- Most aggressive — directly identifies cuts that provide no value at any known operating point
- Cost is per stage per check; the kernel evaluates it as a dense matrix product distributed across threads
- May deactivate cuts that would be active at unvisited states (acceptable as the visited set grows dense)
8. Dynamic Cut Selection
Section titled “8. Dynamic Cut Selection”Dynamic Cut Selection (DCS) is selected by selection.method = "dynamic" and is mutually exclusive with the periodic-pruning methods (Level-1, LML1, Domination). Unlike them, DCS never deactivates cuts from the pool — it keeps the pool entirely append-only and controls only which cuts are resident in each stage LP at solve time.
Motivation: rather than baking every active cut into each stage LP, DCS solves with a small resident subset and grows it lazily until the solution is provably exact, keeping per-solve LP size bounded as the pool grows. The full pool is retained off-LP and every cut remains a candidate.
8.1 Per-solve procedure
Section titled “8.1 Per-solve procedure”At iteration , the DCS loop for one (stage, solve) is:
-
Seed the resident set with cuts that were active at this stage within the last iterations (the seed window), plus all cuts generated in the current iteration. The seed is derived only from synchronized per-slot pool metadata (
last_active_iteranditeration_generated) — never from a per-worker solve trace. This makes the seed bit-identical across thread and MPI-rank counts. -
Solve the stage LP with the current resident set. Let be the optimal state vector and the optimal future-cost value.
-
Score the omitted (non-resident) candidate cuts. Cut coefficients are stored in raw (unscaled) space while the LP solves in scaled space, so both and each state column must first be unscaled: . For candidate cut with intercept and gradient , the future-cost floor it would impose at is
The candidate is violated iff (strict). This follows from the cut-row convention .
-
Add the top most-violated candidates (sorted by violation magnitude descending, ties broken by ascending slot id). Warm re-solve from the retained basis and return to step 2.
-
Stop when no candidate is violated. At this point the resident-subset optimum equals the full-pool optimum — every omitted cut is satisfied — so the result is exact. Backward duals and forward primals are extracted from this final LP.
8.2 Candidate-recency window
Section titled “8.2 Candidate-recency window k1k_1k1”By default every pool cut is a candidate (, i.e. candidate_recency absent), which preserves exactness. Setting candidate_recency to a finite restricts candidates to cuts generated within the last iterations. This is a deliberate, inexact speedup: cuts older than the window are never scored or added, even when violated.
8.3 Bounded inner loop with full-pool fallback
Section titled “8.3 Bounded inner loop with full-pool fallback”The add/re-solve loop is capped at 50 rounds (not user-configurable). If the cap is reached with violations remaining, all remaining violated candidates are added and one final solve is performed — degrading to a full-pool solve for that LP, which preserves exactness.
8.4 Pass uniformity and warm-start synergy
Section titled “8.4 Pass uniformity and warm-start synergy”DCS applies identically across the backward pass, the forward pass, and simulation. Forward and simulation also run to exactness — no early stop — to avoid trajectory drift: an early stop in the forward pass would shift the states the backward pass visits, producing policy drift.
Because each inner re-solve adds only a few rows from a retained basis, DCS and LP basis warm-start are complementary, not in tension. See LP Warm-Start.
8.5 Seed window
Section titled “8.5 Seed window k2=0k_2 = 0k2=0”seed_window = 0 is valid and meaningful: the resident set is seeded only with cuts generated in the current iteration, with no history (zero-history seeding), and the lazy loop then grows the resident set from scratch each solve. A larger seed_window trades a slightly larger initial LP for fewer inner iterations.
8.6 Determinism
Section titled “8.6 Determinism”The resident-set selection is bit-identical across thread and MPI-rank counts:
- The seed comes from synchronized pool metadata (
last_active_iter,iteration_generated), not per-worker traces. - The optimal state is scenario-deterministic (a function of the LP, not the rank).
- Candidate scoring uses a bit-deterministic batched matrix product (
gemm_block). - The violation sort uses a total ordering with an ascending-slot tie-break, making the top- selection stable.
9. Convergence Guarantee
Section titled “9. Convergence Guarantee”Theorem (Bandarra & Guigues, 2021): Under Level-1 or LML1 cut selection, SDDP with finitely many scenarios converges to the optimal value function with probability 1.
Key insight: Removing cuts that are never active at any visited state does not affect the outer approximation quality at those states. As the set of visited states becomes dense over iterations, the approximation converges.
DCS and convergence: DCS is exact at every solve (§8.1 step 5) — the optimum it returns equals the full-pool optimum — so it does not weaken the convergence argument. The exactness guarantee rests on (every pool cut remains a candidate); setting a finite candidate_recency sacrifices this guarantee.
Bandarra, M., & Guigues, V. (2021). “Single cut and multicut stochastic dual dynamic programming with cut selection for multistage stochastic linear programs: convergence proof and numerical experiments.” Computational Management Science, 18(2), 125-148. https://doi.org/10.1007/s10287-021-00387-8
10. Selection Parameters
Section titled “10. Selection Parameters”The training.cut_selection object has two always-on top-level knobs plus an optional tagged selection object. Omitting selection disables row selection. The selection.method field chooses the strategy; each method carries only its own parameters.
Always-on top-level keys: row_activity_tolerance and max_active_per_stage sit at the top level and apply to every method; all other keys are namespaced under the tagged selection object and read only by the method that owns them.
Removed keys (no longer accepted): enabled, the flat top-level method, threshold, memory_window, basis_activity_window.
| Math symbol | Config key | Default | Applies to |
|---|---|---|---|
| — | row_activity_tolerance | 0.0 | Always-on (top-level) |
| — | max_active_per_stage | none (no cap) | Always-on (top-level) |
selection.tie_tolerance | 1e-10 | level1, lml1 | |
| — | selection.check_frequency | 5 (must be > 0) | level1, lml1, domination |
selection.domination_tolerance | required, no default | domination | |
selection.seed_window | 5 (0 is valid) | dynamic | |
selection.candidate_recency | unset (, exact) | dynamic | |
selection.max_added_per_round | 10 (must be ≥ 1) | dynamic | |
selection.violation_tolerance | 1e-10 | dynamic | |
| — | selection.start_iteration | 2 (must be ≥ 1) | dynamic |
Implementation in Cobre
Section titled “Implementation in Cobre”The methodology above defines the complete Benders cut lifecycle; the tabs below cover how Cobre’s software surface configures, persists, and manages cuts at runtime.
Cobre’s cut-management knobs live entirely in config.json’s
training.cut_selection block — there is no per-case data file for cuts (the
cut pool is generated during training, not authored by the case). This tab is
the field-level configuration reference; the selection algorithms themselves
stay in the methodology sections above (§6–§9).
training.cut_selection — Row Management Configuration
Section titled “training.cut_selection — Row Management Configuration”The block has two always-on top-level fields plus an optional tagged
selection sub-object. Omitting selection (or setting it to null)
disables row selection — the pool then only grows, with no deactivation pass
of any kind. This is the default.
| Field | Type | Default | Description |
|---|---|---|---|
row_activity_tolerance | number | 0.0 | Minimum dual-multiplier magnitude for a cut row to count as binding at a solution point. Rows whose dual falls below this are treated as inactive for active_count / last_active_iter tracking — the staleness metadata Stage 2 (below) sorts on. This is not the same tolerance as a selection method’s own near-optimal band (tie_tolerance / domination_tolerance, methodology §6); it feeds tracking metadata, not the value-evaluation kernel itself. |
max_active_per_stage | integer | null (no cap) | Hard cap on active rows per stage LP, enforced every iteration after the selection method (if any) runs — this is “Stage 2” of the row-management pipeline, distinct from the strategy-based “Stage 1” the selection object configures. When the cap is exceeded, rows are evicted least-recently-active first (last_active_iter ascending), tie-broken by least-frequently-active (active_count ascending); rows generated in the current iteration are never evicted. |
selection | object or null | null | The active selection method and its parameters — see below. |
{ "training": { "cut_selection": { "row_activity_tolerance": 1e-6, "max_active_per_stage": 4000, "selection": null } }}The selection Object
Section titled “The selection Object”selection.method is the discriminator; each method exposes only its own
parameters. Supplying a field that belongs to a different method is a
config-load error, and a misspelled method is rejected with the list of
valid methods. The four methods are mutually exclusive by construction of
the tagged shape — choosing "dynamic" means none of "level1" / "lml1" /
"domination" can also run, and vice versa.
"level1" and "lml1"
Section titled “"level1" and "lml1"”Both periodic-pruning methods (methodology §7.1–§7.2) share the same two fields:
| Field | Type | Default | Description |
|---|---|---|---|
tie_tolerance | number | 1e-10 | A cut is active at a visited state when its value is within this of the per-state maximum there. |
check_frequency | integer | 5 | Iterations between periodic-pruning selection runs. Must be > 0. |
"selection": { "method": "level1", "tie_tolerance": 1e-10, "check_frequency": 5 }"domination"
Section titled “"domination"”The most aggressive periodic-pruning method (methodology §7.3):
| Field | Type | Default | Description |
|---|---|---|---|
domination_tolerance | number | required, no default | A cut survives if within this of the maximum at any visited state. |
check_frequency | integer | 5 | Iterations between periodic-pruning selection runs. Must be > 0. |
"selection": { "method": "domination", "domination_tolerance": 1e-6, "check_frequency": 5 }"dynamic" (Dynamic Cut Selection / DCS)
Section titled “"dynamic" (Dynamic Cut Selection / DCS)”The per-solve lazy method (methodology §8):
| Field | Type | Default | Description |
|---|---|---|---|
start_iteration | integer | 2 | First 1-based training iteration at which the lazy loop becomes active. Must be >= 1. |
seed_window | integer | 5 | Number of most-recent iterations whose cuts seed the initial resident set (methodology §8.5). 0 is valid — seeds only the current iteration. |
candidate_recency | integer or null | null (unbounded) | Only cuts generated within the last candidate_recency iterations are scored as candidates. null (the default) is unbounded — every pool cut is a candidate, which preserves the exactness guarantee (methodology §8.2, §9). Some(n) (must be >= 1) makes the loop deliberately inexact: cuts older than the window are never added, even when violated. |
max_added_per_round | integer | 10 | Maximum number of most-violated candidate cuts added per lazy-solve round. Must be >= 1. |
violation_tolerance | number | 1e-10 | Absolute violation tolerance for accepting a candidate cut. Must be > 0. |
"selection": { "method": "dynamic", "start_iteration": 2, "seed_window": 5, "max_added_per_round": 10, "violation_tolerance": 1e-10}Full Examples
Section titled “Full Examples”Dynamic Cut Selection with a per-stage budget:
{ "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 } } }}Level-1 periodic pruning with 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 } } }}This is a topic-scoped index of the files cut management touches. Cuts have
no case-directory input file of their own — the only configuration surface is
training.cut_selection in config.json (see the Configure tab). The topic’s
I/O surface is the policy checkpoint: the FlatBuffers .bin files that
persist the cut pool between runs. This tab names that surface and its role;
it does not repeat the field-by-field record layout, which is owned by the
Reference corpus (Case Directory Format
and Output Format pages).
Inputs
Section titled “Inputs”There is no case-directory input file for cuts. The one input path is a
re-read of a previous run’s output: when the run’s policy mode is
"warm_start" or "resume", Cobre loads policy/cuts/stage_NNN.bin from a
prior checkpoint directory and repopulates the cut pool before training
resumes. See Implementation notes for how a warm-started cut is tracked once
it is back in the pool.
Outputs
Section titled “Outputs”| File | Role |
|---|---|
policy/cuts/stage_NNN.bin | One FlatBuffers binary per stage, root table StageCuts: every cut’s intercept, coefficient vector, and active flag. The coefficient vector is a bare float64[]; each position maps to a state variable via the per-slot entity manifest embedded in the same file (see the Output Format reference). Written every training run (unless checkpointing is disabled) and re-read on the next warm_start / resume run (see Inputs above). |
For the complete field-by-field record layout (Cut, StageCuts) and the
checkpoint-wide policy/metadata.json summary, see the
Output Format reference page in the Reference
corpus. For the schema file itself and per-language flatc codegen
recipes (Python, C++, TypeScript, …) to read a checkpoint outside Cobre, see
the FlatBuffers Policy Schema reference page.
Non-normative software behavior for cut management — what Cobre does at runtime, beyond the equations above. This tab references the methodology body for the derivations rather than restating them.
Reduced-cost unscaling divides by col_scale, not multiplies
Section titled “Reduced-cost unscaling divides by col_scale, not multiplies”Methodology §2 derives each cut coefficient from the reduced cost of a pinned
incoming-state column, unscaled by that column’s prescaler factor. In code
terms the unscaling step is a division:
coefficient = reduced_cost / col_scale[col] (empty col_scale ⇒ the raw
reduced cost, unscaled). The cobre CHANGELOG’s prose for this step says
“multiply”; the shipped extraction code
(crates/cobre-sddp/src/training/backward/duals_extraction.rs) divides. This
tab — and the methodology body it supports — follows the code: divide.
Append-only pool, deterministic slots
Section titled “Append-only pool, deterministic slots”Every cut ever generated is retained for the lifetime of the run at a stable slot index — a fixed function of the iteration and forward-pass index that generated it (methodology §5). The pool is never compacted, and nothing is ever deleted from it; “deactivation” always means something narrower than removal (below).
Deactivation is a bound sentinel, never a row removal
Section titled “Deactivation is a bound sentinel, never a row removal”Cobre’s row-management pipeline touches two different LPs, and “deactivating” a cut means something slightly different in each:
- In the per-iteration forward/backward stage LP — rebuilt from the stage template at the start of each solve — a deactivated cut is simply omitted from that iteration’s template rebake. Only active cuts are encoded as LP rows on each forward/backward solve; there is no row to toggle because the row was never emitted.
- In the persistent lower-bound LP — where cut rows are never structurally removed, so the training lower bound stays monotone — the same deactivation instead toggles the cut row’s bound to a trivially-satisfied ±∞ sentinel. The row stays in place; it simply stops constraining the solve.
Both routes preserve the slot index, which is what makes reactivation exact: a cut that a later selection pass restores is re-baked into the template (or has its bound restored), at the same slot, with the same coefficients it was generated with.
Two-stage pipeline, run every iteration
Section titled “Two-stage pipeline, run every iteration”The row-management pipeline runs after each iteration’s backward pass and cut synchronization, in two stages:
- Strategy-based selection (
level1/lml1/domination) — gated bycheck_frequency; runs only at iterations that are a multiple of it. Stage 0 is always exempt from this pass, because stage 0’s rows drive the lower bound directly and are never a backward-pass successor. Selection runs in parallel across stages.dynamic(DCS) never runs this stage at all — it has nocheck_frequencygating and never deactivates pool cuts. - Budget enforcement (
max_active_per_stage) — runs every iteration, regardless ofcheck_frequency, as a hard-cap safety net on top of whatever Stage 1 (or DCS) leaves active.
DCS is lazy-exact, not a deactivation pass
Section titled “DCS is lazy-exact, not a deactivation pass”Dynamic Cut Selection (methodology §8) never deactivates a pool cut. Each
stage solve instead starts from a small resident subset — seeded from cuts
active at this stage within the last seed_window iterations plus every cut
generated this iteration — and grows it by repeatedly scoring the omitted
candidates and adding the most-violated ones, up to the bounded inner-loop cap
(50 rounds, not user-configurable). The loop stops as soon as no candidate is
violated, at which point the resident-subset optimum already equals the
full-pool optimum — no extra “final” solve is needed to confirm it. If the cap
is hit first, all remaining violated candidates are added and one closing
solve is performed, which still preserves exactness (methodology §8.3).
A cut loaded into the pool via policy.mode = "warm_start" or "resume"
(Inputs & Outputs tab) carries a reserved sentinel iteration_generated value
rather than a real iteration number. Under DCS’s candidate_recency window
this sentinel saturates to age zero, so a warm-started cut is always inside
any finite candidate_recency window — only cuts generated during the
current run’s own iterations can age out of it.
Cross-References
Section titled “Cross-References”- LP Formulation — Column-bound state pinning whose reduced costs give cut coefficients; Benders cut constraints in the LP
- LP Warm-Start — Basis warm-start that is complementary to DCS inner re-solves
- PAR Inflow Model — AR lag state variables that appear in cut coefficients
- SDDP Algorithm — Forward/backward pass structure that drives cut generation
- Scenario Generation — Fixed opening tree that defines backward pass branchings; sampling scheme abstraction
- Penalty System — Recourse slacks that guarantee relatively complete recourse (cut validity condition)
- Stopping Rules — Convergence criteria that depend on cut quality
- Discount Rate — Discount factor scaling in cut aggregation
- Risk Measures — Risk-averse cut generation (CVaR modifies aggregation weights)
- Block Formulations — Block structure that affects how per-block duals contribute to cut coefficients
- Determinism Guarantees — Bit-identical results across thread and MPI-rank counts, including DCS resident-set seeding
- Running Cobre: Policy Management — the software guide for persisting and reloading this cut pool across runs.