test(history): end-to-end ConvergenceOptions propagation tests
Two integration tests on a 4-team ranked event: - max_iter=1 set on HistoryBuilder produces measurably different posteriors than default, proving the inner loop honors the propagated max_iter - alpha=0.5 with extra iterations reaches the same fixed point as alpha=1.0, proving damping doesn't break correctness on the History path Also updates the alpha doc comment to clarify it applies only to the within-game EP loop, not the outer cross-history sweep.
This commit is contained in:
@@ -1714,4 +1714,97 @@ mod tests {
|
||||
fn history_builder_rejects_zero_score_sigma() {
|
||||
let _ = History::builder().score_sigma(0.0).build();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn history_propagates_convergence_to_inner_run_chain() {
|
||||
use crate::ConvergenceOptions;
|
||||
|
||||
let events_for =
|
||||
|h: &mut History<i64, ConstantDrift, crate::observer::NullObserver, &'static str>| {
|
||||
h.event(0)
|
||||
.team(["a"])
|
||||
.team(["b"])
|
||||
.team(["c"])
|
||||
.team(["d"])
|
||||
.ranking([0u32, 1, 2, 3])
|
||||
.commit()
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
let mut h_capped: History<i64, _, _, &'static str> = History::builder()
|
||||
.convergence(ConvergenceOptions {
|
||||
max_iter: 1,
|
||||
..ConvergenceOptions::default()
|
||||
})
|
||||
.build();
|
||||
events_for(&mut h_capped);
|
||||
h_capped.converge().unwrap();
|
||||
|
||||
let mut h_full: History<i64, _, _, &'static str> = History::builder().build();
|
||||
events_for(&mut h_full);
|
||||
h_full.converge().unwrap();
|
||||
|
||||
let curves_capped = h_capped.learning_curves();
|
||||
let curves_full = h_full.learning_curves();
|
||||
|
||||
let mut max_diff: f64 = 0.0;
|
||||
for (key, capped_pts) in curves_capped.iter() {
|
||||
let full_pts = curves_full.get(key).expect("agent missing in full");
|
||||
for (capped, full) in capped_pts.iter().zip(full_pts.iter()) {
|
||||
max_diff = max_diff.max((capped.1.mu() - full.1.mu()).abs());
|
||||
max_diff = max_diff.max((capped.1.sigma() - full.1.sigma()).abs());
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
max_diff > 1e-6,
|
||||
"max_iter=1 inner loop should differ from default; max_diff={max_diff}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn history_with_damping_reaches_same_fixed_point_as_undamped() {
|
||||
use crate::ConvergenceOptions;
|
||||
|
||||
let events_for =
|
||||
|h: &mut History<i64, ConstantDrift, crate::observer::NullObserver, &'static str>| {
|
||||
h.event(0)
|
||||
.team(["a"])
|
||||
.team(["b"])
|
||||
.team(["c"])
|
||||
.team(["d"])
|
||||
.ranking([0u32, 1, 2, 3])
|
||||
.commit()
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
let mut h_undamped: History<i64, _, _, &'static str> = History::builder().build();
|
||||
events_for(&mut h_undamped);
|
||||
h_undamped.converge().unwrap();
|
||||
|
||||
let mut h_damped: History<i64, _, _, &'static str> = History::builder()
|
||||
.convergence(ConvergenceOptions {
|
||||
alpha: 0.5,
|
||||
max_iter: 200,
|
||||
..ConvergenceOptions::default()
|
||||
})
|
||||
.build();
|
||||
events_for(&mut h_damped);
|
||||
h_damped.converge().unwrap();
|
||||
|
||||
let curves_u = h_undamped.learning_curves();
|
||||
let curves_d = h_damped.learning_curves();
|
||||
|
||||
let mut max_diff: f64 = 0.0;
|
||||
for (key, u_pts) in curves_u.iter() {
|
||||
let d_pts = curves_d.get(key).expect("agent missing in damped");
|
||||
for (u, d) in u_pts.iter().zip(d_pts.iter()) {
|
||||
max_diff = max_diff.max((u.1.mu() - d.1.mu()).abs());
|
||||
max_diff = max_diff.max((u.1.sigma() - d.1.sigma()).abs());
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
max_diff < 1e-3,
|
||||
"α=0.5 should reach the same fixed point as α=1.0; max_diff={max_diff}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user