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:
+89
-8
@@ -39,36 +39,71 @@ pub enum UnknownKeys {
|
||||
Prior,
|
||||
}
|
||||
|
||||
/// Every way ingestion, inference or prediction can refuse to answer.
|
||||
///
|
||||
/// The crate reports rather than repairs. An input it cannot represent, a fit
|
||||
/// that never reached its fixed point, a quadrature it cannot resolve — each
|
||||
/// comes back here instead of as a clamped, skipped or truncated result that
|
||||
/// would still look like a number. Several variants exist precisely because the
|
||||
/// silent version was measured and found to return a plausible wrong answer.
|
||||
///
|
||||
/// The enum and most of its variants are `#[non_exhaustive]`: new cases and new
|
||||
/// fields are additive, so match with a `_` arm and construct through the
|
||||
/// library rather than by literal.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum InferenceError {
|
||||
/// Expected and actual lengths of some array-shaped input differ.
|
||||
#[non_exhaustive]
|
||||
MismatchedShape {
|
||||
/// Which input disagreed, as a short label — `"ranks vs teams"`,
|
||||
/// `"weights"`, `"times"`.
|
||||
kind: &'static str,
|
||||
/// The length it had to have, taken from whatever it must line up with
|
||||
/// (usually the event's team count).
|
||||
expected: usize,
|
||||
/// The length actually supplied.
|
||||
got: usize,
|
||||
},
|
||||
/// An `Outcome` of the wrong variant was supplied for the requested inference.
|
||||
#[non_exhaustive]
|
||||
WrongOutcomeKind {
|
||||
/// The call that rejected the outcome, e.g. `"Game::ranked"`.
|
||||
context: &'static str,
|
||||
/// The [`Outcome`](crate::Outcome) variant that call needs, by name.
|
||||
expected: &'static str,
|
||||
/// The variant actually supplied, by name.
|
||||
got: &'static str,
|
||||
},
|
||||
/// A probability value is outside `[0, 1]`.
|
||||
#[non_exhaustive]
|
||||
InvalidProbability { value: f64 },
|
||||
InvalidProbability {
|
||||
/// The value supplied, as it fell outside `[0, 1]`. Today only
|
||||
/// `p_draw` reaches here.
|
||||
value: f64,
|
||||
},
|
||||
/// A scalar parameter is outside its valid range.
|
||||
#[non_exhaustive]
|
||||
InvalidParameter { name: &'static str, value: f64 },
|
||||
InvalidParameter {
|
||||
/// The parameter, spelled as the API spells it — `"alpha"`,
|
||||
/// `"epsilon"`, `"score_sigma"`, `"drift_scale"`, `"drift variance"`.
|
||||
name: &'static str,
|
||||
/// The value supplied for it. Out of that parameter's range, or NaN,
|
||||
/// which fails every range comparison and is rejected on that basis.
|
||||
value: f64,
|
||||
},
|
||||
/// An event contains tied teams, but the draw probability is zero.
|
||||
///
|
||||
/// A zero draw probability asserts that draws cannot occur, so a tied
|
||||
/// result has no representable likelihood. Configure a positive `p_draw`
|
||||
/// (via `HistoryBuilder::p_draw` or `GameOptions::p_draw`) to admit ties.
|
||||
#[non_exhaustive]
|
||||
TieWithoutDrawProbability { teams: (usize, usize) },
|
||||
TieWithoutDrawProbability {
|
||||
/// Positions in the event's team list of the first tied pair, lowest
|
||||
/// index first. Only one pair is reported — the event is rejected
|
||||
/// whole, so enumerating the rest would add nothing.
|
||||
teams: (usize, usize),
|
||||
},
|
||||
/// The convergence sweep hit `max_iter` with the step still above
|
||||
/// `epsilon`.
|
||||
///
|
||||
@@ -84,8 +119,15 @@ pub enum InferenceError {
|
||||
/// returns the short fit instead when that is genuinely what is wanted.
|
||||
#[non_exhaustive]
|
||||
NotConverged {
|
||||
/// Full forward+backward sweeps run before the loop gave up.
|
||||
iterations: usize,
|
||||
/// How far the last sweep still moved the fit, as
|
||||
/// `(largest change in a mean, largest change in a standard
|
||||
/// deviation)` over every competitor posterior it touched — the same
|
||||
/// quantity as
|
||||
/// [`ConvergenceReport::final_step`](crate::ConvergenceReport).
|
||||
final_step: (f64, f64),
|
||||
/// The threshold both components of `final_step` had to reach.
|
||||
epsilon: f64,
|
||||
},
|
||||
/// Inference produced a non-finite value (NaN or infinity).
|
||||
@@ -94,7 +136,12 @@ pub enum InferenceError {
|
||||
/// and must not be treated as a converged estimate.
|
||||
#[non_exhaustive]
|
||||
NonFiniteResult {
|
||||
/// Where the breakdown was caught — `"History::converge"` for a sweep,
|
||||
/// or a phrase naming the prediction that read an unusable skill.
|
||||
context: &'static str,
|
||||
/// The offending pair, at least one component of which is NaN or
|
||||
/// infinite. From `converge` it is the sweep's step; from a prediction
|
||||
/// it is the skill's own `(mu, sigma)`.
|
||||
step: (f64, f64),
|
||||
},
|
||||
/// One batch declared two different values for the same competitor's
|
||||
@@ -108,7 +155,12 @@ pub enum InferenceError {
|
||||
/// when a competitor's configuration is a property of the domain.
|
||||
#[non_exhaustive]
|
||||
ConflictingCompetitorConfig {
|
||||
/// The competitor's interned [`Index`](crate::Index) as a raw `usize`,
|
||||
/// not the user key — the batch is already flattened to indices by the
|
||||
/// time the conflict is detectable.
|
||||
competitor: usize,
|
||||
/// Which piece of configuration was declared twice: `"prior"` or
|
||||
/// `"drift_scale"`.
|
||||
field: &'static str,
|
||||
},
|
||||
/// A prediction referenced a key the history has no skill for.
|
||||
@@ -123,8 +175,16 @@ pub enum InferenceError {
|
||||
/// neutral value — turns the whole thing into a plausible constant.
|
||||
#[non_exhaustive]
|
||||
UnknownKey {
|
||||
/// Position of the offending team in the supplied matchup. `0` on the
|
||||
/// queries that take a flat list of keys rather than teams, where
|
||||
/// there is only one list to index into.
|
||||
team: usize,
|
||||
/// Position of the offending key within that team, or within the flat
|
||||
/// key list.
|
||||
member: usize,
|
||||
/// The key's `Debug` rendering, captured because `K` is only required
|
||||
/// to be `Debug` — see the variant docs for why the indices alone are
|
||||
/// not enough.
|
||||
key: String,
|
||||
},
|
||||
/// `History::register` was called for a competitor that already exists.
|
||||
@@ -138,10 +198,16 @@ pub enum InferenceError {
|
||||
/// To change an existing competitor's configuration, supply it on an event
|
||||
/// through `Member`; that refits the whole history.
|
||||
#[non_exhaustive]
|
||||
AlreadyRegistered { key: String },
|
||||
AlreadyRegistered {
|
||||
/// The already-known competitor's key, in its `Debug` rendering.
|
||||
key: String,
|
||||
},
|
||||
/// A prediction was given a team with no members.
|
||||
#[non_exhaustive]
|
||||
EmptyTeam { team: usize },
|
||||
EmptyTeam {
|
||||
/// Position of the memberless team in the supplied list.
|
||||
team: usize,
|
||||
},
|
||||
/// The prediction grid cannot resolve the narrowest feature in the matchup.
|
||||
///
|
||||
/// `predict_outcome` and `predict_ranking` integrate every team's density
|
||||
@@ -167,10 +233,19 @@ pub enum InferenceError {
|
||||
},
|
||||
/// A joint posterior was requested where one cannot be formed exactly.
|
||||
#[non_exhaustive]
|
||||
JointUnavailable { reason: &'static str },
|
||||
JointUnavailable {
|
||||
/// Why no exact joint exists here: the history has no events, it holds
|
||||
/// ranked events whose EP factors are not retained past convergence, or
|
||||
/// the assembled precision matrix is not positive-definite.
|
||||
reason: &'static str,
|
||||
},
|
||||
/// Fewer than two teams were supplied to a prediction.
|
||||
#[non_exhaustive]
|
||||
NotEnoughTeams { got: usize },
|
||||
NotEnoughTeams {
|
||||
/// How many teams the prediction was actually given. Two is the
|
||||
/// minimum: there is nothing to compare against with fewer.
|
||||
got: usize,
|
||||
},
|
||||
/// The full outcome distribution was requested for too many teams.
|
||||
///
|
||||
/// Each realisation sorts into exactly one (order, tie-pattern) event, so
|
||||
@@ -180,7 +255,13 @@ pub enum InferenceError {
|
||||
/// `predict_ranking`, or for `predict_win_probabilities`, both of which
|
||||
/// stay cheap at any team count.
|
||||
#[non_exhaustive]
|
||||
TooManyTeams { got: usize, max: usize },
|
||||
TooManyTeams {
|
||||
/// How many teams the outcome distribution was asked for.
|
||||
got: usize,
|
||||
/// The largest team count that will be enumerated,
|
||||
/// [`MAX_PREDICTED_TEAMS`](crate::MAX_PREDICTED_TEAMS).
|
||||
max: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for InferenceError {
|
||||
|
||||
Reference in New Issue
Block a user