fix: close out four small issues and pin #27's repro
#29 — log_evidence and log_evidence_for took &mut self while mutating nothing. Loosening them to &self is not source-breaking for ordinary callers (a &mut reborrows as & transparently) and brings them in line with the filtered_* accessors added last week. Not the mechanical change it looked like: under the rayon feature the closure in log_evidence_internal captured all of &self rather than just the competitor store, which drags KeyTable<K> in and demands K: Sync from every caller. That compiled while the method took &mut self and stopped compiling the moment it did not. Binding `let agents = &self.agents;` before the closure narrows the capture; the comment there says why, because the next person to inline it will reintroduce the bound. #31 — TimeSlice::add_events constructed Skill with ..Default::default() while filtered_step spells every field out. The design relies on a new Skill field being a compile error at construction sites rather than a silent default, and that tripwire only fired at one of the two. Now both. #28 — log_evidence_internal's `forward` flag is a genuine forward-only quantity only on a history that has never been converged, because iteration alternates sweeps and the likelihood feeding the forward message absorbs backward information from the second iteration onward. Documented, with a pointer to filtered_log_evidence for the quantity that survives convergence. That trap is one function away from the one #19 was about. #23 — color_greedy carried #[allow(dead_code)] despite being called by recompute_color_groups: a mute button on a live function, which is the specific complaint in that issue. #27 was already fixed — the guard landed inf4e2922and the issue was filed against7742b2b, which merge-base confirms predates it — but nothing pinned it. Added the issue's own reproduction, which matters because the two profiles fail differently and a debug-only test would miss the release path. Removing both guards reproduces the issue verbatim: "attempt to subtract with overflow" in debug, "index out of bounds: the len is 0 but the index is 18446744073709551615" in release. Also amended the filtered-estimates spec (#30): the tolerance-not-bit-identity caveat is conservative. Forcing the scratch onto the sequential sweep instead of the grouped one — a far larger perturbation than a permuted event order — still agrees within 1e-8 under tight convergence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T5SYDExxL4vZgvunrcNSMc
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
use trueskill_tt::{
|
||||
ConstantDrift, ConvergenceOptions, Game, GameOptions, Gaussian, History, InferenceError,
|
||||
Outcome, Rating,
|
||||
NullObserver, Outcome, Rating,
|
||||
};
|
||||
|
||||
type R = Rating<i64, ConstantDrift>;
|
||||
@@ -127,6 +127,20 @@ fn empty_history_converges_trivially() {
|
||||
assert!(report.converged);
|
||||
}
|
||||
|
||||
/// Issue #27's exact reproduction: a non-default key type reaching `converge`
|
||||
/// with no events at all. The underflow it reported trapped in debug and
|
||||
/// indexed out of bounds in release, so this must run in both profiles.
|
||||
#[test]
|
||||
fn converge_on_an_empty_history_with_owned_keys() {
|
||||
let mut history: History<i64, ConstantDrift, NullObserver, String> =
|
||||
History::builder_with_key().score_sigma(5.0).build();
|
||||
|
||||
let report = history.converge().unwrap();
|
||||
|
||||
assert_eq!(report.iterations, 0);
|
||||
assert!(report.converged);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_event_stream_then_converge() {
|
||||
let mut h = History::default();
|
||||
|
||||
Reference in New Issue
Block a user