1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! Benders subgradient extraction from a solved backward LP [SEALED].
//!
//! Owns the cut-sign / `col_scale`-division contract from `.claude/rules/sddp.md`
//! ("Benders cut sign"): the subgradient is `rc_scaled / col_scale[col]` —
//! divided, not multiplied — because the incoming-state column pin sets
//! `v_scaled = v_orig / col_scale`. Both functions are `pub(crate)` so the
//! per-opening solve dispatch in [`super::StageOpeningSolver`] (`trial_point`)
//! can call them across the submodule boundary; their bodies and Contract
//! rustdoc move verbatim and must not be edited.
use SolutionView;
use crateStageIndexer;
use SuccessorSpec;
/// Extract state and cut duals from the solver view into pre-warmed scratch buffers.
///
/// Called while `view` is still live (borrowing the solver). The output buffers
/// (`state_duals`, `cut_duals`) were taken out of `ws.backward_accum` before the
/// solve and are passed here directly so that no `ws` borrow is needed.
///
/// Returns the LP objective value.
///
/// # Dual-fill layout
///
/// `state_duals`: unscaled reduced costs at the LP columns pinned by
/// `fill_col_state_patches`, one entry per state-vector index.
/// Scaling: `rc_original[j] = rc_scaled[j] / col_scale[col]` (the pin sets
/// `v_scaled = v_orig / col_scale`, so the subgradient w.r.t. the original
/// state divides by `col_scale`); when `col_scale` is empty the raw reduced
/// costs are used directly.
///
/// `cut_duals`: raw duals for cut rows `[template_num_rows, template_num_rows + num_cuts)`.
/// These always have implicit `row_scale = 1.0`.
pub
/// Extract only the cut gradient (state-fixing-column reduced costs) and return
/// the objective, for the lazy-solve path.
///
/// Identical to the state-dual half of [`extract_duals_from_view`]: it fills
/// `state_duals[j] = rc_scaled[col] / col_scale[col]` (raw reduced costs when
/// `col_scale` is empty) at the incoming-state column for each state index `j`,
/// where the negation into the `−∇·x + θ ≥ intercept` row happens later in
/// cut-row construction. The Benders gradient and intercept come solely from
/// these structural state columns, which are identical in the all-cuts LP and
/// the lazy-solve LP, so the resulting cut matches the all-cuts cut by
/// exactness.
///
/// Unlike [`extract_duals_from_view`], it does NOT read cut-row duals: under the
/// lazy-solve path the resident cut rows are a subset in row-map insertion
/// order, so the all-cuts cut-row→slot mapping does not apply. Binding-count
/// metadata and basis capture for that layout are handled separately and are
/// not driven from this function.
pub