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
//! `linear_system_scaling` values must change the solve, not just parse
//! (#677).
//!
//! `slack-based` was registered — so `OptionsList` accepted it — but the
//! parser routed it to `LinearSystemScalingChoice::None` through a
//! catch-all arm. `mc19` reached the same fallback through a named arm
//! that warns; `slack-based` warned nothing, while a comment directly
//! above the match claimed both did. Setting it was indistinguishable
//! from not setting it.
//!
//! That is not an obscure value: it is what Ipopt's recommended
//! configuration for large collocation NLPs uses, and it appeared in a
//! user's golden configuration — validated across 25 models — reported
//! on #677. The people most likely to set it were the least likely to
//! find out it did nothing.
//!
//! So the test that matters is not "is it accepted" but "does the solve
//! move". #551 puts it directly: "A read site that parses a value and
//! discards it is the same silent no-op this whole line of work exists
//! to kill, and it is indistinguishable from a real fix by inspection.
//! That test is the deliverable, not the line that reads the field."
//!
//! ## Why this stopped comparing iteration counts (gh#693)
//!
//! The original guard read: `slack-based` must not take the *same number
//! of iterations* as `none`. That is a proxy for the real property, and
//! it turned out to be a bad one in two independent ways.
//!
//! It is **platform-fragile**. Iteration counts are the most
//! platform-sensitive numbers a solver reports, and on macOS at
//! `ac18ba6d` — before gh#693 — this file already failed, on the
//! `slack-based` vs `ruiz` leg: both took 103 iterations on `cresc4`
//! while CI's Linux runners saw them differ. A guard that is red on a
//! developer's machine and green in CI gets read as noise, which is how
//! it stops being a guard.
//!
//! It is **fixture-dependent in a way the property is not**. gh#693
//! left `cresc4` better conditioned, and all four scaling choices now
//! reach the optimum in 69 iterations. Nothing is wrong: `slack-based`
//! still reaches the linear solver and still changes the arithmetic —
//! `cresc4`'s objective is 0.8718975393087962 under `none` and
//! 0.8718975392737567 under `slack-based`. The *option* works; the
//! *proxy* went quiet, and an assertion on the proxy would have gone
//! quiet with it while claiming to guard gh#677.
//!
//! So the comparison is now on the solve's numerical output, which is
//! what gh#677's defect actually looked like — the report above says it
//! in as many words: "`slack-based` produced byte-identical output to
//! `none`, because it *was* `none`." An option routed to a catch-all
//! cannot perturb a single bit, on any platform, so byte-identity is
//! both the exact signal and a portable one.
//!
//! Three relations are pinned, on three fixtures, and they hold
//! identically on `ac18ba6d` and after gh#693:
//!
//! ```text
//! slack != none slack != ruiz mc19 == none
//! cresc4 yes yes yes
//! airport yes yes yes
//! csfi2 yes yes yes
//! ```
//!
//! `mc19 == none` is the positive control and it is the more valuable
//! half: it is a genuine fallback, so it proves the comparison can still
//! see a no-op. A test that only ever asserts "these differ" cannot tell
//! a working option from a comparison that has broken open.
//!
//! (`eigenb2` is a fixture where `slack-based` and `none` *do* agree
//! bit for bit, on both builds. It is named here so the next reader
//! knows the relation is a property of the fixture as well as the
//! option, and does not add a fixture to this list without checking.)
use PathBuf;
use Command;
use ;
/// Scratch directories must not collide: the two tests here run
/// concurrently and both solve the same fixtures, so naming the
/// directory after the fixture and the scaling choice is not enough —
/// one test's cleanup would delete the other's output mid-run.
static RUN: AtomicUsize = new;
/// Solve a fixture and return the raw JSON report as text.
///
/// `linear_scaling_on_demand=no` is essential: the default is `yes`,
/// which computes scaling only on a factorization that already looks
/// troubled. On a fixture that solves cleanly, every scaling choice is
/// then identical — which is exactly how a test could "pass" against an
/// unimplemented option. Forcing scaling on every factorization is what
/// makes the comparison meaningful.
/// The numerical content of a solve, with the parts that cannot be
/// compared across two runs removed: the whole `fair_metadata` block
/// (a result id, timestamps, an elapsed time, and the scratch path the
/// fixture was copied to) and the two wall-clock timings. Everything
/// left is deterministic for a fixed binary and a fixed set of options.
/// gh#677's defect, stated as the property it actually was: setting
/// `slack-based` must not be indistinguishable from not setting it.
///
/// An option parsed into a catch-all arm cannot change a single bit of
/// the answer, so comparing the numerical output is exact — and unlike
/// an iteration count it means the same thing on every platform.
/// The positive control for the test above, and the more valuable half.
///
/// `mc19` is still unimplemented and still falls back to no scaling, so
/// its output must be *identical* to `none`. That pins two things at
/// once: the fallback stays deliberate rather than becoming another
/// silent one, and `numeric_output` can still see a no-op — a file that
/// only ever asserted "these differ" would pass just as happily if the
/// comparison itself broke open.