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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
use super::{InnerProgressSnapshot, first_order_inner_cap_schedule};
fn snap(last_iters: usize, last_converged: bool) -> Option<InnerProgressSnapshot> {
Some(InnerProgressSnapshot {
last_iters,
last_converged,
last_ift_residual: None,
last_accept_rho: None,
})
}
fn snap_with_accept_rho(
last_iters: usize,
last_converged: bool,
accept_rho: f64,
) -> Option<InnerProgressSnapshot> {
Some(InnerProgressSnapshot {
last_iters,
last_converged,
last_ift_residual: None,
last_accept_rho: Some(accept_rho),
})
}
fn snap_with_residual(
last_iters: usize,
last_converged: bool,
residual: f64,
) -> Option<InnerProgressSnapshot> {
Some(InnerProgressSnapshot {
last_iters,
last_converged,
last_ift_residual: Some(residual),
last_accept_rho: None,
})
}
/// The bridge's snapshot reader must distinguish "no signal yet"
/// (NaN sentinel, encoded as `IFT_RESIDUAL_NO_SIGNAL_BITS`) from
/// "residual was 0.0" (a real signal). Previously the bridge used
/// `bits == 0` to detect no-signal, which collided with
/// `f64::to_bits(0.0) == 0`. This test pins down the new
/// NaN-sentinel discipline at the bridge layer.
#[test]
fn snapshot_distinguishes_zero_residual_from_no_signal() {
use super::InnerProgressFeedback;
use crate::solver::estimate::reml::outer_eval::IFT_RESIDUAL_NO_SIGNAL_BITS;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize};
// Helper to build a feedback channel with concrete values.
let make_feedback = |iters: usize, converged: bool, residual_bits: u64| InnerProgressFeedback {
cap: Arc::new(AtomicUsize::new(0)),
accepted_iter: Arc::new(AtomicUsize::new(0)),
last_iters: Arc::new(AtomicUsize::new(iters)),
last_converged: Arc::new(AtomicBool::new(converged)),
ift_residual: Arc::new(AtomicU64::new(residual_bits)),
accept_rho: Arc::new(AtomicU64::new(
crate::solver::estimate::reml::outer_eval::IFT_RESIDUAL_NO_SIGNAL_BITS,
)),
};
// Sentinel → no IFT signal (last_ift_residual = None).
let fb = make_feedback(5, true, IFT_RESIDUAL_NO_SIGNAL_BITS);
let snap = fb.snapshot().expect("iters > 0, snapshot present");
assert!(
snap.last_ift_residual.is_none(),
"sentinel must decode to None"
);
// 0.0 residual → genuine signal (last_ift_residual = Some(0.0)).
// This is the bug: previously the reader treated `bits == 0` as
// no-signal, dropping the genuine 0.0 residual.
let fb = make_feedback(5, true, 0.0_f64.to_bits());
let snap = fb.snapshot().expect("iters > 0, snapshot present");
assert_eq!(
snap.last_ift_residual,
Some(0.0),
"residual of exactly 0.0 must round-trip as a real signal, \
not be confused with the no-signal sentinel",
);
// Modest finite residual round-trips.
let fb = make_feedback(5, true, 0.05_f64.to_bits());
let snap = fb.snapshot().expect("snapshot present");
assert_eq!(snap.last_ift_residual, Some(0.05));
// last_iters == 0 → entire snapshot is None (no inner-Newton
// signal yet at all). Sentinel residual irrelevant.
let fb = make_feedback(0, false, IFT_RESIDUAL_NO_SIGNAL_BITS);
assert!(fb.snapshot().is_none());
}
#[test]
fn schedule_falls_back_to_iter_tier_without_feedback() {
// No inner-progress history yet → coarse iter-count fallback so
// the cold-start cap is shallow even before the adaptive signal
// arrives.
assert_eq!(first_order_inner_cap_schedule(0, None, None), 3);
assert_eq!(first_order_inner_cap_schedule(1, None, None), 5);
assert_eq!(first_order_inner_cap_schedule(2, None, None), 10);
assert_eq!(first_order_inner_cap_schedule(20, None, None), 10);
}
#[test]
fn schedule_uses_last_iters_plus_margin_when_converged() {
// Inner converged in 4 iters last time → cap = 4+2 = 6.
assert_eq!(first_order_inner_cap_schedule(2, None, snap(4, true)), 6);
// Inner converged in 12 → cap = 14.
assert_eq!(first_order_inner_cap_schedule(5, None, snap(12, true)), 14);
}
#[test]
fn schedule_geometric_backoff_when_last_hit_cap() {
// Last hit cap at 5 → 2*5=10, max(10, 5+4=9) = 10.
assert_eq!(first_order_inner_cap_schedule(2, None, snap(5, false)), 10);
// Last hit cap at 1 → 2*1=2, max(2, 1+4=5) = 5.
assert_eq!(first_order_inner_cap_schedule(2, None, snap(1, false)), 5);
// Last hit cap at 30 → would be 60 but ceiling is 64, so 60.
assert_eq!(first_order_inner_cap_schedule(2, None, snap(30, false)), 60);
}
#[test]
fn schedule_clamps_floor_and_ceiling() {
// Last converged in 0 (degenerate; should never happen because
// the producer only writes nonzero, but defensively check the
// floor of 3).
assert_eq!(first_order_inner_cap_schedule(2, None, snap(0, true)), 3);
// Last converged in 100 → ceiling 64.
assert_eq!(first_order_inner_cap_schedule(2, None, snap(100, true)), 64);
}
#[test]
fn schedule_uncaps_when_outer_converged() {
// g_ratio < 1% trumps everything: cached β must be at full
// inner tolerance for the convergence guard.
assert_eq!(first_order_inner_cap_schedule(0, Some(0.0001), None), 0);
assert_eq!(
first_order_inner_cap_schedule(0, Some(0.005), snap(4, true)),
0
);
assert_eq!(
first_order_inner_cap_schedule(20, Some(0.001), snap(50, false)),
0
);
}
#[test]
fn schedule_ignores_modest_g_ratio_decay() {
// Old schedule had tiered ratio caps at 0.50/0.20/0.05; the new
// schedule only special-cases the deep-convergence threshold
// (<1%). Modest decay no longer overrides the adaptive cap.
assert_eq!(
first_order_inner_cap_schedule(2, Some(0.30), snap(4, true)),
6
);
assert_eq!(
first_order_inner_cap_schedule(2, Some(0.05), snap(4, true)),
6
);
}
#[test]
fn schedule_uses_ift_residual_to_pick_margin() {
// Excellent IFT prediction (residual < 0.01): warm-start lands
// essentially AT the KKT β, so +1 of margin suffices.
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_residual(4, true, 0.005)),
5
);
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_residual(4, true, 0.0001)),
5
);
// Default zone (0.01 ≤ residual < 0.10): +2, current behavior.
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_residual(4, true, 0.05)),
6
);
// Poor IFT prediction (residual ≥ 0.10): +4, the inner Newton
// has more recovery work after a near-flat warm-start.
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_residual(4, true, 0.20)),
8
);
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_residual(4, true, 0.80)),
8
);
// Margin policy is monotone non-decreasing in residual: a worse
// predictor never produces a tighter cap than a better one.
let residuals = [0.001, 0.05, 0.30];
let caps: Vec<usize> = residuals
.iter()
.map(|&r| first_order_inner_cap_schedule(2, None, snap_with_residual(4, true, r)))
.collect();
for w in caps.windows(2) {
assert!(
w[0] <= w[1],
"ift-residual margin policy regressed monotonicity: {caps:?}"
);
}
}
#[test]
fn schedule_bumps_margin_on_poor_lm_accept_rho() {
// Healthy LM model fidelity (accept_rho ≥ 0.5): margin
// unchanged from the no-accept-rho baseline (+2 default).
// last_iters=4, default margin=2 → cap=6.
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_accept_rho(4, true, 0.95)),
6
);
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_accept_rho(4, true, 0.5)),
6
);
// Poor LM model fidelity (accept_rho < 0.5): +2 margin bump
// beyond the IFT-residual base. last_iters=4, default base=2,
// accept_rho<0.5 bump=+2 → margin=4 → cap=8.
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_accept_rho(4, true, 0.4)),
8
);
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_accept_rho(4, true, 0.1)),
8
);
// accept_rho saturation guard: `r < 0.5` is the strict
// textbook "good agreement" cutoff for trust-region gain
// ratios. Boundary at 0.5 admits, just below 0.5 bumps.
assert_eq!(
first_order_inner_cap_schedule(2, None, snap_with_accept_rho(4, true, 0.49)),
8
);
}
#[test]
fn schedule_escalates_geometric_backoff_on_very_poor_accept_rho() {
// Cap-hit (last_converged=false) with VERY poor LM model
// (accept_rho < 0.3): triple instead of double the cap, so the
// next solve has materially more iter budget when the model is
// both insufficient (cap-hit) AND mis-calibrated (poor rho).
// last_iters=4 → 4*3 = 12, vs 4*2=8 with doubling.
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: false,
last_ift_residual: None,
last_accept_rho: Some(0.15),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 12);
// Cap-hit with moderately-poor accept_rho (0.3 ≤ r < 0.5):
// standard doubling. The threshold for escalation is 0.3, not
// 0.5, because the +2-margin path (commit 04b30163) already
// covers the 0.3-0.5 case for the converged branch.
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: false,
last_ift_residual: None,
last_accept_rho: Some(0.4),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 8);
// Cap-hit with healthy accept_rho ≥ 0.5: standard doubling.
// The previous solve hit the cap because it needed more iters,
// not because the LM was mis-calibrated.
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: false,
last_ift_residual: None,
last_accept_rho: Some(0.9),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 8);
// Cap-hit with no accept_rho signal: standard doubling. No
// escalation when we don't have evidence of LM trouble.
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: false,
last_ift_residual: None,
last_accept_rho: None,
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 8);
// Boundary at exactly 0.3: NOT escalated (`< 0.3` is strict).
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: false,
last_ift_residual: None,
last_accept_rho: Some(0.3),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 8);
}
#[test]
fn schedule_skips_lm_accept_rho_bump_when_signal_absent() {
// None for last_accept_rho means "no signal yet" — the schedule
// must NOT bump the margin in that case (otherwise a fresh
// surface with the NaN sentinel would get penalty cap inflation
// for no reason). last_iters=4, last_ift_residual=None →
// default base margin=2 → cap=6, regardless of accept_rho being
// unset.
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: true,
last_ift_residual: None,
last_accept_rho: None,
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 6);
// Regression-lock the boundary: accept_rho exactly at 0.5
// (textbook good-agreement cutoff) does NOT bump (`< 0.5` is
// strict). cap = 4 + 2 = 6.
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: true,
last_ift_residual: None,
last_accept_rho: Some(0.5),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 6);
// accept_rho = 1.0 is the textbook "perfect agreement" — never
// bumps. cap = 4 + 2 = 6.
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: true,
last_ift_residual: None,
last_accept_rho: Some(1.0),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 6);
}
#[test]
fn schedule_combines_ift_residual_and_lm_accept_rho() {
// When BOTH signals fire (poor IFT prediction AND poor LM
// accept_rho), the bumps compose: IFT base = 4, accept_rho
// bump = +2 → total margin = 6, cap = last_iters + 6.
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: true,
last_ift_residual: Some(0.30),
last_accept_rho: Some(0.20),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 10);
// When only LM accept_rho is poor (IFT residual is excellent),
// the bumps still compose: IFT base = 1 (excellent), accept_rho
// bump = +2 → margin = 3, cap = 4 + 3 = 7.
let snap = Some(InnerProgressSnapshot {
last_iters: 4,
last_converged: true,
last_ift_residual: Some(0.005),
last_accept_rho: Some(0.30),
});
assert_eq!(first_order_inner_cap_schedule(2, None, snap), 7);
}