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
//! The `increase_quality` rung costs two solves on `square_flowsheet_resto`,
//! and `feral_increase_quality=no` is the recovery (gh #850, the underlying
//! regression rather than the reporting one).
//!
//! # What it is
//!
//! `2c4f25f1` wired `FeralSolverInterface::increase_quality`, which had
//! returned a hard-coded `false`, through to FERAL's escalation ladder. Ipopt
//! calls `IncreaseQuality` when `PdFullSpaceSolver`'s refinement stalls, and
//! every upstream backend that can escalate does, so wiring it looked like
//! restoring a missing rung.
//!
//! The contract it restores is not the one the ladder satisfies. MA57 answers
//! the call by raising `pivtol` toward `pivtolmax`: strictly more conservative
//! each time, so keeping it raised for the rest of the solve can only make the
//! factorization safer. FERAL's ladder changes *which pivots are taken* — a
//! lateral move in trajectory terms — and it persists identically, across every
//! later factorization including a restoration sub-solve's. So it reroutes
//! solves, and on this fixture it reroutes both legs into failure:
//!
//! ```text
//! exact Optimal/99 -> RestorationFailed/131, shipped only because a
//! second-opinion rung rescues it at 185 total
//! lbfgs Optimal/178 -> 3000 iterations, at the cap, rescued by nothing
//! ```
//!
//! The lbfgs line reads "rescued by nothing" as of gh#850, and gh#857 changed
//! that: `feral_increase_quality_retry` re-solves it without the escalation
//! and promotes `Optimal`/178 at 3178 total. That rung is on by default, so
//! the lbfgs test below now passes `feral_increase_quality_retry=no` in order
//! to still see the **base** solve's 3000 — which is what this file is about.
//! The rung is the cost whether or not something later recovers it, and the
//! recovery costs a second full budget to buy the same 178 iterations the
//! option buys outright. `feral_increase_quality` remains on by default and
//! remains the trade this file pins; `issue857_escalation_gated_quality_rung.rs`
//! is where the recovery itself is pinned.
//!
//! The lbfgs leg is worse than the reported exact one and was found by the
//! `2nd=` sweep column added alongside this: it showed the leg failing with no
//! ladder behind it at all.
//!
//! # Why this is an option and not a changed default
//!
//! **The rung also buys things nothing else supplies, so flipping it is a
//! trade, not a fix.** The 12-variable model in
//! `pounce-rs/tests/watchdog_trial_is_not_a_divergence_verdict.rs` ends
//! `SolvedToAcceptableLevel` at `obj = 3.7e-6` with the rung and at
//! `obj = 3.42` against `f* = 0` without it — a wrong-ish answer under a
//! success-shaped status, which is worse than an honest failure. It also buys
//! 15–25% of the iterations on five fixture-legs.
//!
//! And nothing separates the two sides. The rung fires exactly twice in this
//! fixture's **base solve** on the exact leg — once in the main loop, printed
//! as a `q` on the row labelled 26, and once inside restoration at `76r`,
//! printed nowhere — and allowing **only the first** still loses the leg, so
//! scoping it out of restoration would not help. Nor does a count: `deb7` and
//! `square_flowsheet_resto` each fire it exactly twice on their exact legs,
//! one gaining 16% of its iterations and the other losing its verdict.
//!
//! Read the scope on that "twice": it is the base solve. gh#857 added the
//! `quality_escalations` statistic, which reproduces it independently and
//! reports the rest — six process-wide on the exact leg once the
//! second-opinion rung's own solve is counted, twenty-five per solve on the
//! lbfgs leg. It is also what gh#857's recovery rung gates on, and it uses the
//! count only as a `>= 1` admission test, for exactly the reason in the
//! paragraph above.
//!
//! One correction this file owes its own text: FERAL's ladder is documented as
//! scaling-then-pivot-threshold, but the scaling rung is **unreachable** as
//! pounce ships — feral takes it only under `ScalingStrategy::Identity` and
//! pounce defaults to `Auto`. Every escalation here is a `pivot_threshold`
//! bump, the first 1e-8 → 1e-6, and no milder ladder helps: every static
//! `feral_pivtol` in {1e-6, 3.16e-5, 4.2e-4, 1e-2, 0.5} loses the lbfgs leg
//! from iteration 0.
//!
//! So the default stands, the option is the documented recovery, and what this
//! file pins is the **trade itself** — that the rung is what costs the two
//! solves, and that turning it off recovers them. Resolving it properly needs a
//! *revertible* escalation, which FERAL's `quality_level` cannot express today.
use PathBuf;
use Command;
/// `(stdout + stderr, exit banner, iteration count)` for one solve.
///
/// Both streams, because the two things this file reads live on different
/// ones: the `EXIT:` banner and the iteration count are printed to stdout, and
/// the second-opinion ladder's narration is `eprintln!`.
/// The exact leg, which gh #850 reports: with the rung on, the base solver
/// fails and only a second-opinion rung makes the verdict come out right.
/// Turning the rung off recovers the base solve outright.
/// The lbfgs leg, which is worse and which no ladder was rescuing when gh#850
/// was filed: with the rung on, the base solve runs to the 3000-iteration cap.
/// The L-BFGS path is not exotic coverage — the Python frontend and the CasADi
/// plugin both select `limited-memory` on their own whenever no exact
/// Lagrangian Hessian is available.
///
/// `feral_increase_quality_retry=no` is what keeps this measuring the *base*
/// cost. gh#857 added a second-opinion rung that catches exactly this exit and
/// re-solves without the escalation, so without the flag the run reports the
/// promoted `Optimal`/178 and the regression this file exists to pin becomes
/// invisible. Disabling the recovery is not disabling the subject: the rung
/// under test is `feral_increase_quality`, which stays on here.