Trait coverage (#76), all additive: History Debug is still absent - see below HistoryBuilder + Debug (it derived Clone but not Debug) Rating + PartialEq (Gaussian had it; Rating is a Gaussian plus three scalars and had none) Event/Team/Member + PartialEq (input value types with no way to compare them, which made round-trip tests awkward) ConvergenceReport + PartialEq `#[must_use]` (#67). The coverage had no rule: `filtered_log_evidence` had it and `log_evidence` did not; `rating` had it and `current_skill` did not; `Rating::with_drift_scale` had it and `Member::with_drift_scale` did not. Now on the types — `EventBuilder`, `HistoryBuilder`, `Prediction`, `Gaussian`, `OwnedGame` — which covers most method returns at once, plus the `History` accessors individually. `EventBuilder` gets a message, because a dropped builder is the worst case in the set: measured, `h.event(1).team(["x"]).team(["y"]).winner(0)` without `.commit()` leaves `time_slices_len() == 0` and every skill `None`, with no warning at all. And `ConvergenceReport`'s `#[must_use]` moves off the TYPE onto `converge_partial`, where its stated reason is true. It read "from `converge_partial` this may describe a fit that stopped at max_iter" but fired on `converge` too — where that is false, since `converge` returns `Err(NotConverged)` in exactly that case. So the crate's own front-page example warned, and every quickstart had to write `let _ =`. Verified from a consumer crate: `h.converge()?;` now compiles clean. Marking the types made eight method-level attributes redundant, which clippy's `double_must_use` caught — that is the type-level marker doing its job, and the eight are removed. Refs #76, #67 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
58 lines
2.0 KiB
Rust
58 lines
2.0 KiB
Rust
//! One slice's event sweep.
|
|
//!
|
|
//! Written against the public API rather than against `TimeSlice` directly.
|
|
//! It used to reach for `TimeSlice`, `KeyTable`, `CompetitorStore`,
|
|
//! `Competitor` and `EventKind`, and was the *only* thing outside `src/`
|
|
//! that did — so a benchmark was dictating five public types that no test,
|
|
//! example or consumer could otherwise obtain.
|
|
//!
|
|
//! A single-slice history's `converge` calls exactly the same per-slice sweep,
|
|
//! so capping at one iteration measures the same code path.
|
|
|
|
use criterion::{Criterion, criterion_group, criterion_main};
|
|
use smallvec::smallvec;
|
|
use trueskill_tt::{ConstantDrift, ConvergenceOptions, Event, History, Member, Outcome, Team};
|
|
|
|
fn criterion_benchmark(criterion: &mut Criterion) {
|
|
let build = || {
|
|
let mut h = History::builder()
|
|
.convergence(ConvergenceOptions {
|
|
max_iter: 1,
|
|
epsilon: 0.0,
|
|
alpha: 1.0,
|
|
})
|
|
.drift(ConstantDrift::new(0.0))
|
|
.build();
|
|
|
|
// 100 events, all at one time, so the history has a single slice.
|
|
let events: Vec<Event<i64, &'static str>> = (0..100)
|
|
.map(|_| Event {
|
|
time: 1,
|
|
teams: smallvec![
|
|
Team::with_members([Member::new("a")]),
|
|
Team::with_members([Member::new("b")]),
|
|
],
|
|
outcome: Outcome::winner(0, 2),
|
|
})
|
|
.collect();
|
|
h.add_events(events).expect("fixture ingests");
|
|
h
|
|
};
|
|
|
|
criterion.bench_function("slice_sweep_100_events", |b| {
|
|
b.iter_batched(
|
|
build,
|
|
|mut h| {
|
|
// `converge_partial`, not `converge`: one iteration is
|
|
// deliberately short of convergence and `converge` reports that
|
|
// as an error.
|
|
let _ = h.converge_partial();
|
|
},
|
|
criterion::BatchSize::SmallInput,
|
|
);
|
|
});
|
|
}
|
|
|
|
criterion_group!(benches, criterion_benchmark);
|
|
criterion_main!(benches);
|