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:
+71
@@ -85,6 +85,10 @@
|
||||
//! regardless of worker count.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
// Turned on once the surface was fully documented (80 items at the time), so
|
||||
// the next undocumented public item is a build failure rather than a warning
|
||||
// nobody reads.
|
||||
#![deny(missing_docs)]
|
||||
|
||||
/// Compiles every `rust` block in `README.md` as a doctest.
|
||||
///
|
||||
@@ -111,12 +115,25 @@ pub(crate) mod arena;
|
||||
mod color_group;
|
||||
mod competitor;
|
||||
mod convergence;
|
||||
/// Skill drift: how much a competitor's skill is allowed to move between
|
||||
/// appearances.
|
||||
///
|
||||
/// Public because [`Drift`] is a trait a caller may implement — a per-sport
|
||||
/// off-season, say, or a schedule where drift is a function of the calendar
|
||||
/// rather than of elapsed ticks. [`ConstantDrift`] is what
|
||||
/// [`HistoryBuilder`] uses by default.
|
||||
pub mod drift;
|
||||
mod error;
|
||||
mod event;
|
||||
mod event_builder;
|
||||
pub(crate) mod factor;
|
||||
mod game;
|
||||
/// The Gaussian message type and its expectation-propagation algebra.
|
||||
///
|
||||
/// Public because [`Gaussian`] appears throughout the results: a posterior
|
||||
/// skill, a learning-curve point, a predicted margin. The module carries the
|
||||
/// operator documentation — `Mul`/`Div` are the EP product and cavity, not
|
||||
/// arithmetic on random variables.
|
||||
pub mod gaussian;
|
||||
mod history;
|
||||
mod joint;
|
||||
@@ -145,13 +162,58 @@ pub use observer::{NullObserver, Observer};
|
||||
pub use outcome::Outcome;
|
||||
pub use predict::Prediction;
|
||||
pub use rating::Rating;
|
||||
/// The `smallvec` crate, re-exported.
|
||||
///
|
||||
/// Four public items name `SmallVec` in their signatures: [`Event::teams`],
|
||||
/// [`Team::members`], [`Outcome::Ranked`]'s payload and
|
||||
/// [`ConvergenceReport::per_iteration_time`]. You can *build* an `Event`
|
||||
/// without ever naming the type — `vec![..].into()` and `.collect()` both work
|
||||
/// — and iterate the timings through `Deref`. But writing a helper that
|
||||
/// *returns* a teams list, or a `match` arm that binds ranks and passes them
|
||||
/// on, requires the type by name.
|
||||
///
|
||||
/// Measured: the only `Joint` doc example failed to compile from a consumer
|
||||
/// crate with `unresolved import \`smallvec\``, because the dependency was in
|
||||
/// the signature but not reachable. Re-exported so a consumer takes this
|
||||
/// crate's version rather than pinning a matching one of their own.
|
||||
pub use smallvec;
|
||||
pub use time::{Time, Untimed};
|
||||
|
||||
/// Default performance noise: how much a single showing varies around skill.
|
||||
///
|
||||
/// Every other default is expressed as a multiple of this, so `BETA` sets the
|
||||
/// scale of the whole rating system. Doubling it and doubling `SIGMA` and
|
||||
/// `GAMMA` with it gives the same fit on a rescaled axis.
|
||||
pub const BETA: f64 = 1.0;
|
||||
/// Default prior mean skill.
|
||||
///
|
||||
/// Zero rather than a conventional 25: the scale is set by `BETA`, and a
|
||||
/// centred axis makes a negative rating mean "below the prior" instead of
|
||||
/// looking like an error.
|
||||
pub const MU: f64 = 0.0;
|
||||
/// Default prior standard deviation: how unsure the model starts out.
|
||||
///
|
||||
/// Six betas is deliberately wide — a new competitor's first result should
|
||||
/// move them a long way, and the prior should not fight the evidence.
|
||||
pub const SIGMA: f64 = BETA * 6.0;
|
||||
/// Default drift: the standard deviation of skill movement per unit of time.
|
||||
///
|
||||
/// Enters inference as a *variance* (`gamma^2` per elapsed tick), which is why
|
||||
/// [`ConstantDrift`] squares it and why a negative gamma would be
|
||||
/// indistinguishable from its absolute value — see [`ConstantDrift::new`].
|
||||
pub const GAMMA: f64 = BETA * 0.03;
|
||||
/// Default draw probability: zero, meaning ties are not modelled.
|
||||
///
|
||||
/// A history that ingests a tie needs a positive value. With `p_draw == 0.0`
|
||||
/// the truncation margin is zero and the two-sided tie update evaluates
|
||||
/// `0/0`, so ingestion rejects such events with
|
||||
/// [`InferenceError::TieWithoutDrawProbability`].
|
||||
pub const P_DRAW: f64 = 0.0;
|
||||
/// Default convergence threshold, in the same units as
|
||||
/// [`ConvergenceReport::final_step`](crate::ConvergenceReport).
|
||||
///
|
||||
/// The sweep stops once the largest change a full iteration makes to any
|
||||
/// message falls below this.
|
||||
pub const EPSILON: f64 = 1e-6;
|
||||
/// Default cap on convergence sweeps.
|
||||
///
|
||||
@@ -226,6 +288,15 @@ const ASYMPTOTIC_MILLS_ALPHA: f64 = 100.0;
|
||||
pub(crate) const N00: Gaussian = Gaussian::from_ms(0.0, 0.0);
|
||||
pub(crate) const N_INF: Gaussian = Gaussian::from_ms(0.0, f64::INFINITY);
|
||||
|
||||
/// An interned competitor handle: a dense slot number, not a user key.
|
||||
///
|
||||
/// [`History`] stores skills and messages by `Index` rather than by `K`, so
|
||||
/// the hot path never hashes a key. [`History::intern`] promotes a key to one
|
||||
/// and [`History::lookup`] resolves an existing key without creating.
|
||||
///
|
||||
/// Indices are assigned in interning order and are stable for the life of a
|
||||
/// history. They are **not** portable between histories: the same key interns
|
||||
/// to different slots depending on ingestion order.
|
||||
#[derive(Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
|
||||
pub struct Index(usize);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user