docs: document the whole public surface and deny(missing_docs)
80 undocumented public items, including three that are first contact:
`History::current_skill` — the method the crate's own first example calls
— `EventBuilder`, the type `h.event(t)` hands you, and `Gaussian::mu()`.
Now zero, and `#![deny(missing_docs)]` keeps it that way.
Several docs are measurements rather than readings of the code:
- `Outcome::Ranked` says ranks are used ordinally, so `[0, 1, 2]` and
`[0, 5, 90]` are the same observation. Measured: bit-identical
posteriors for both.
- `OwnedGame::log_evidence` says two identically-rated competitors give
exactly `ln(0.5)`. Written as a doctest, so it runs.
- `Member::weight` says zero and negative are accepted. Measured.
- `ConvergenceReport::final_step` is `(|Δmu|, |Δsigma|)` in skill units,
NOT natural parameters. That one had to be traced through
`Gaussian::delta` rather than assumed from the neighbouring vocabulary.
- `GameOptions::score_sigma` rejects non-positive and NaN but accepts
`+inf`, which is what the guard actually says.
README: it is the front door for a crate on a private registry, and it
opened with a link dump followed by 130 lines on drift. The first
`record_winner → converge → current_skill` block was at line 226 of 307.
It now leads with what the crate is, an install line, a quickstart, a
"which entry point?" table, and the `converge`-is-strict rationale that
was the crate's most opinionated recent decision and went unmentioned.
The two canonical examples disagreed on spelling (`History::default()`
vs `History::builder().build()`, `current_skill("a")` vs
`current_skill(&"a")`); they now agree. Five new README blocks are
doctested, taking the suite from 19 to 25.
`pub use smallvec;`. Four public items name `SmallVec` in their
signatures, and the only `Joint` example failed to compile from a
consumer crate with `unresolved import smallvec` — the dependency was in
the API but not reachable. Both worked examples now use the re-export,
so they teach the path that works downstream.
Vocabulary, from #75: "agent" was a fourth word for competitor, 200
occurrences, and it had reached public signatures before #73 un-exported
`TimeSlice`. Now zero.
Closes #77. Refs #75.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
This commit is contained in:
+68
@@ -70,8 +70,25 @@ impl DiffFactor {
|
||||
/// how much the engine trusts the observed score margin (smaller σ = more trust).
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct GameOptions {
|
||||
/// Probability the model assigns to two teams drawing, which sets the width
|
||||
/// of the truncation band around a tie. Must be in `[0.0, 1.0)`; defaults
|
||||
/// to [`P_DRAW`](crate::P_DRAW).
|
||||
///
|
||||
/// At `0.0` the band has zero width, so a ranked outcome that ties two
|
||||
/// teams has no representable likelihood and [`Game::ranked`] rejects it
|
||||
/// with `TieWithoutDrawProbability`.
|
||||
pub p_draw: f64,
|
||||
/// Standard deviation of the observation noise on an observed score margin,
|
||||
/// used only by [`Game::scored`], which rejects a non-positive or NaN value
|
||||
/// with `InvalidParameter`. Defaults to `1.0`.
|
||||
///
|
||||
/// It is in the units of the scores themselves, and says how much of a
|
||||
/// margin the model reads as skill rather than noise: a small sigma takes
|
||||
/// the margin near-literally, a large one barely moves the ratings.
|
||||
pub score_sigma: f64,
|
||||
/// Stopping rule and damping for the within-game message-passing loop:
|
||||
/// iterate until the largest message change falls below `epsilon`, or
|
||||
/// `max_iter` passes, with each update damped by `alpha`.
|
||||
pub convergence: crate::ConvergenceOptions,
|
||||
}
|
||||
|
||||
@@ -91,6 +108,9 @@ impl Default for GameOptions {
|
||||
/// History's internal state), `OwnedGame<T, D>` owns the team ratings, so it
|
||||
/// can be returned freely from public constructors. The inference inputs
|
||||
/// themselves are not retained — nothing reads them back.
|
||||
///
|
||||
/// A fitted single match, and nothing more: see [`Game`] for why that is not
|
||||
/// the same as a step of a [`History`](crate::History).
|
||||
#[derive(Debug)]
|
||||
#[must_use]
|
||||
pub struct OwnedGame<T: Time, D: Drift<T>> {
|
||||
@@ -145,6 +165,13 @@ impl<T: Time, D: Drift<T>> OwnedGame<T, D> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Updated skill belief for every competitor, as `[team][member]` in the
|
||||
/// order the teams and members were passed in.
|
||||
///
|
||||
/// Each is the competitor's own prior multiplied by the likelihood this one
|
||||
/// match produced for it — so it reflects this match and the rating handed
|
||||
/// in, and nothing else. Feeding it back as the next match's prior is the
|
||||
/// caller's job; that is what a [`History`](crate::History) automates.
|
||||
#[must_use]
|
||||
pub fn posteriors(&self) -> Vec<Vec<Gaussian>> {
|
||||
self.likelihoods
|
||||
@@ -154,12 +181,48 @@ impl<T: Time, D: Drift<T>> OwnedGame<T, D> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Natural log of how probable this outcome was under the priors, summed
|
||||
/// over the diff chain's links.
|
||||
///
|
||||
/// Higher means the result was less surprising, so it doubles as a
|
||||
/// closeness measure — two identically-rated competitors give exactly
|
||||
/// `ln(0.5)`, either of them being equally likely to win:
|
||||
///
|
||||
/// ```
|
||||
/// # use trueskill_tt::{ConstantDrift, Game, GameOptions, Gaussian, Outcome, Rating};
|
||||
/// let r = Rating::new(Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift::new(0.0));
|
||||
/// let g = Game::<i64, _>::ranked(&[&[r], &[r]], Outcome::winner(0, 2), &GameOptions::default())?;
|
||||
/// assert!((g.log_evidence() - 0.5_f64.ln()).abs() < 1e-12);
|
||||
/// # Ok::<(), trueskill_tt::InferenceError>(())
|
||||
/// ```
|
||||
///
|
||||
/// Accumulated in log space because the linear product over a long chain
|
||||
/// underflows to zero, and `ln(0.0)` is `-inf`.
|
||||
#[must_use]
|
||||
pub fn log_evidence(&self) -> f64 {
|
||||
self.log_evidence
|
||||
}
|
||||
}
|
||||
|
||||
/// One match's factor graph, fitted on its own.
|
||||
///
|
||||
/// Rate a single match against ratings you already hold and get the updated
|
||||
/// beliefs straight back. There is no history behind it: nothing is stored,
|
||||
/// nothing propagates backward, and the priors you hand in are the only
|
||||
/// evidence used. That makes it the wrong tool for the thing this crate exists
|
||||
/// for — [`History`](crate::History) is what infers skill *through time*,
|
||||
/// revising past estimates as later matches arrive, and a sequence of `Game`s
|
||||
/// chained by hand is a forward-only filter, not the same answer.
|
||||
///
|
||||
/// Reach for `Game` when a history would be overkill or unavailable: a
|
||||
/// one-off matchup, replaying a rating step from stored numbers, checking the
|
||||
/// engine against a reference, or a caller that keeps its own persistence and
|
||||
/// only wants the update rule.
|
||||
///
|
||||
/// The type is mostly a namespace. Its constructors — [`Game::ranked`],
|
||||
/// [`Game::scored`], [`Game::one_v_one`], [`Game::free_for_all`] — return an
|
||||
/// [`OwnedGame`], because `Game<'a, …>` borrows the result and weight slices
|
||||
/// that `History` keeps internally and so cannot be handed out.
|
||||
#[derive(Debug)]
|
||||
pub struct Game<'a, T: Time = i64, D: Drift<T> = crate::drift::ConstantDrift> {
|
||||
teams: Vec<Vec<Rating<T, D>>>,
|
||||
@@ -413,6 +476,9 @@ impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
||||
self.likelihoods = likelihoods;
|
||||
}
|
||||
|
||||
/// Updated skill belief for every competitor, as `[team][member]` in the
|
||||
/// order the teams and members were passed in — prior times this match's
|
||||
/// likelihood, exactly as [`OwnedGame::posteriors`].
|
||||
#[must_use]
|
||||
pub fn posteriors(&self) -> Vec<Vec<Gaussian>> {
|
||||
self.likelihoods
|
||||
@@ -427,6 +493,8 @@ impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Natural log of how probable this outcome was under the priors, summed
|
||||
/// over the diff chain's links — as [`OwnedGame::log_evidence`].
|
||||
#[must_use]
|
||||
pub fn log_evidence(&self) -> f64 {
|
||||
self.log_evidence
|
||||
|
||||
Reference in New Issue
Block a user