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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//! `ReducedHessianCalculator` — port of upstream
//! [`SensReducedHessianCalculator.{hpp,cpp}`](../../../ref/Ipopt/contrib/sIPOPT/src/SensReducedHessianCalculator.cpp).
//!
//! # What this computes
//!
//! Given a converged KKT factor `K` and a parameter-row selector `B`
//! that picks out the **free variables** (post active-set
//! elimination), the reduced Hessian is
//!
//! ```text
//! H_R = obj_scal · B · K⁻¹ · Bᵀ
//! ```
//!
//! per upstream's sign + obj-scaling convention at
//! [`SensReducedHessianCalculator.cpp:90-97`](../../../ref/Ipopt/contrib/sIPOPT/src/SensReducedHessianCalculator.cpp):
//! the raw Schur output is `S = -B K⁻¹ Bᵀ` (with the leading minus
//! from the augmented-system reduction), which is then multiplied
//! by `-obj_scal` to produce the unscaled reduced Hessian.
//!
//! In pounce we default `obj_scal = 1.0` so the operation reduces to
//! `H_R = -S = B K⁻¹ Bᵀ`.
//!
//! # `B K⁻¹ Bᵀ` is `H_R` or `−H_R` depending on which rows `B` picks
//!
//! This function is agnostic about that; a caller must not be
//! (gh#937). Upstream — and pounce's CLI `red_hessian` suffix path,
//! which mirrors it — selects **free-variable (x-block) rows**, where
//! the corresponding block of `K⁻¹` is itself an inverse. The
//! `pounce-sensitivity` pin path instead selects **`y_c` multiplier
//! rows**, and for `K = [[H, Aᵀ], [A, 0]]` that block of `K⁻¹` is
//! `−(A H⁻¹ Aᵀ)⁻¹` — an inverse of an inverse, so the result is
//! `±H_R` itself rather than a submatrix of `K⁻¹`, and the `-S` above
//! leaves it at `−H_R`.
//!
//! The two blocks sit on opposite sides of one inversion, so the unit
//! test below — which feeds a synthetic dense `K` and selects two of
//! its rows — looks like it generalizes to the pin path and does not.
//! `pounce-sensitivity/tests/issue_937_reduced_hessian_sign.rs` pins
//! the pin path's orientation at the public API, on a model where
//! `±H_R` and `H_R⁻¹` differ in magnitude as well as sign.
//!
//! Unlike upstream, no NLP-side scaling needs
//! folding in here: since pounce#128 the live-factor backsolver
//! (`PdSensBacksolver`, in `pounce-sensitivity` — a downstream crate, so not
//! linkable from here) conjugates every back-solve by the
//! NLP scaling diagonal, so `K⁻¹` is already the natural-units KKT
//! inverse and `obj_scal` survives purely as a user-side extra
//! multiplier.
//!
//! Reference: Pirnay, López-Negrete & Biegler 2012, §5
//! (reduced-Hessian use case), DOI:
//! [10.1007/s12532-012-0043-2](https://doi.org/10.1007/s12532-012-0043-2).
use cratePCalculator;
use crateSchurData;
use Number;
/// Compute the (column-major) reduced Hessian into the caller-supplied
/// buffer.
///
/// Mirrors `ReducedHessianCalculator::ComputeReducedHessian`
/// ([`SensReducedHessianCalculator.cpp:42-113`](../../../ref/Ipopt/contrib/sIPOPT/src/SensReducedHessianCalculator.cpp)).
///
/// # Arguments
///
/// - `pcalc`: a P-calculator that was built with `data_A = hess_data`
/// (the **same** SchurData on both sides — the reduced-Hessian
/// computation is the diagonal `B = A` case of `schur_matrix`).
/// Must have completed `compute_p` before this call; this function
/// calls `schur_matrix(hess_data, …)` which will run `compute_p`
/// lazily if needed.
/// - `hess_data`: the free-variable selector, in the IndexSchurData
/// format. Conceptually the same matrix as `pcalc.data_a()`.
/// - `obj_scal`: per-NLP objective scaling factor; pounce defaults to
/// `1.0` (no scaling). Mirrors upstream's `apply_obj_scaling(1.0)`
/// ([`SensReducedHessianCalculator.cpp:91`](../../../ref/Ipopt/contrib/sIPOPT/src/SensReducedHessianCalculator.cpp)).
/// - `out`: caller-allocated buffer of length `n_rows × n_rows` where
/// `n_rows = hess_data.nrows()`. Column-major. Overwritten on
/// success.
///
/// Returns `false` if the underlying `schur_matrix` call fails or
/// `out` is mis-sized.
///
/// # Note on scaling-induced warnings
///
/// Upstream prints a J_WARNING block when any of `x` / `c` / `d`
/// scaling is active because "a correct unscaled solution of the
/// reduced hessian cannot be guaranteed"
/// ([`SensReducedHessianCalculator.cpp:64-88`](../../../ref/Ipopt/contrib/sIPOPT/src/SensReducedHessianCalculator.cpp)).
/// Pounce's Phase-C surface takes `obj_scal` as an explicit argument
/// rather than reaching into an `NLP_scaling()` object; users
/// constructing a reduced Hessian outside a configured pounce IPM
/// own the responsibility to pass an `obj_scal` consistent with
/// whatever scaling their `K` factor encodes.
/// Convert a natural-units reduced Hessian (column-major `n×n`,
/// `n = dc.len()`) into the solver's internal scaled space in place:
/// `H̃_ij = (df / (dc_i·dc_j)) · H_ij`, with `df` the effective
/// objective scaling factor and `dc` the pin rows' constraint scaling
/// factors. This is the inverse of the natural-units correction the
/// live-factor backsolver applies (pounce#128) — i.e. the value
/// pounce returned before #128. Single home for the formula so the
/// `SensSolve` and `Solver` surfaces cannot drift.
///
/// Returns `false` when `hr` is mis-sized.