Merge docs/vocabulary (#75)
This commit is contained in:
+34
-4
@@ -72,7 +72,22 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> HistoryBuilder<
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prior standard deviation.
|
/// Prior standard deviation: how unsure the model is about a competitor's
|
||||||
|
/// **skill** before it has seen them play.
|
||||||
|
///
|
||||||
|
/// The first of the two noise knobs, and the one people reach for by
|
||||||
|
/// mistake. `sigma` is *epistemic* — it is what the model does not yet
|
||||||
|
/// know, and evidence shrinks it. [`HistoryBuilder::beta`] is *aleatoric*
|
||||||
|
/// — how much a single showing scatters around the skill, which no amount
|
||||||
|
/// of evidence removes.
|
||||||
|
///
|
||||||
|
/// So: results move ratings too slowly for your taste → raise `sigma` (or
|
||||||
|
/// `gamma`, if the problem is that skill genuinely moves). A single upset
|
||||||
|
/// swings ratings too far → raise `beta`, because you are telling the model
|
||||||
|
/// that one result is weaker evidence than it assumed.
|
||||||
|
///
|
||||||
|
/// The default is six betas, deliberately wide: a new competitor's first
|
||||||
|
/// result should move them a long way.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
@@ -91,7 +106,19 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> HistoryBuilder<
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Per-event performance noise.
|
/// Per-event performance noise: how much a single showing scatters around
|
||||||
|
/// a competitor's **skill**.
|
||||||
|
///
|
||||||
|
/// The second noise knob, and the one that sets the scale of the whole
|
||||||
|
/// system — [`SIGMA`](crate::SIGMA) and [`GAMMA`](crate::GAMMA) are both
|
||||||
|
/// defined as multiples of it. Unlike
|
||||||
|
/// [`sigma`](HistoryBuilder::sigma), this is *aleatoric*: it is the
|
||||||
|
/// irreducible day-to-day variation, so evidence never shrinks it. It is
|
||||||
|
/// also what makes an upset possible at all — with `beta == 0` the better
|
||||||
|
/// competitor always wins.
|
||||||
|
///
|
||||||
|
/// Larger `beta` means each result carries less information, so ratings
|
||||||
|
/// move less per game and the draw margin implied by `p_draw` is wider.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
@@ -2685,8 +2712,11 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
|
|||||||
kinds.push(EventKind::Ranked);
|
kinds.push(EventKind::Ranked);
|
||||||
ranks.iter().map(|&r| max_rank - r as f64).collect()
|
ranks.iter().map(|&r| max_rank - r as f64).collect()
|
||||||
}
|
}
|
||||||
crate::Outcome::Scored { scores, sigma } => {
|
crate::Outcome::Scored {
|
||||||
let resolved = sigma.unwrap_or(self.score_sigma);
|
scores,
|
||||||
|
score_sigma,
|
||||||
|
} => {
|
||||||
|
let resolved = score_sigma.unwrap_or(self.score_sigma);
|
||||||
if resolved <= 0.0 || resolved.is_nan() {
|
if resolved <= 0.0 || resolved.is_nan() {
|
||||||
return Err(InferenceError::InvalidParameter {
|
return Err(InferenceError::InvalidParameter {
|
||||||
name: "score_sigma",
|
name: "score_sigma",
|
||||||
|
|||||||
+13
-12
@@ -1,6 +1,6 @@
|
|||||||
//! Outcome of a match.
|
//! Outcome of a match.
|
||||||
//!
|
//!
|
||||||
//! `Ranked(ranks)` for ordinal results; `Scored { scores, sigma }` for
|
//! `Ranked(ranks)` for ordinal results; `Scored { scores, score_sigma }` for
|
||||||
//! continuous per-team scores (engages `MarginFactor` in the engine).
|
//! continuous per-team scores (engages `MarginFactor` in the engine).
|
||||||
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
@@ -10,7 +10,7 @@ use smallvec::SmallVec;
|
|||||||
/// `Ranked(ranks)`: lower rank = better. Equal ranks mean a tie between those
|
/// `Ranked(ranks)`: lower rank = better. Equal ranks mean a tie between those
|
||||||
/// teams. `ranks.len()` must equal the number of teams in the event.
|
/// teams. `ranks.len()` must equal the number of teams in the event.
|
||||||
///
|
///
|
||||||
/// `Scored { scores, sigma }`: higher score = better. Adjacent (sorted) pairs
|
/// `Scored { scores, score_sigma }`: higher score = better. Adjacent (sorted) pairs
|
||||||
/// feed observed margins to `MarginFactor`. `scores.len()` must equal the
|
/// feed observed margins to `MarginFactor`. `scores.len()` must equal the
|
||||||
/// number of teams in the event. `sigma` overrides `HistoryBuilder::score_sigma`
|
/// number of teams in the event. `sigma` overrides `HistoryBuilder::score_sigma`
|
||||||
/// when `Some`; `None` inherits the history default.
|
/// when `Some`; `None` inherits the history default.
|
||||||
@@ -34,7 +34,8 @@ pub enum Outcome {
|
|||||||
///
|
///
|
||||||
/// Unlike `Ranked`, the *sizes* of the differences are evidence. Teams are
|
/// Unlike `Ranked`, the *sizes* of the differences are evidence. Teams are
|
||||||
/// sorted by score and each adjacent pair's observed gap is fed to a
|
/// sorted by score and each adjacent pair's observed gap is fed to a
|
||||||
/// `MarginFactor` as a measurement with standard deviation `sigma`, so
|
/// `MarginFactor` as a measurement with standard deviation `score_sigma`,
|
||||||
|
/// so
|
||||||
/// beating a team by ten says more than beating them by one.
|
/// beating a team by ten says more than beating them by one.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
Scored {
|
Scored {
|
||||||
@@ -43,7 +44,7 @@ pub enum Outcome {
|
|||||||
scores: SmallVec<[f64; 4]>,
|
scores: SmallVec<[f64; 4]>,
|
||||||
/// Per-event noise override. `None` means inherit
|
/// Per-event noise override. `None` means inherit
|
||||||
/// `HistoryBuilder::score_sigma`. Must be `> 0.0` if `Some`.
|
/// `HistoryBuilder::score_sigma`. Must be `> 0.0` if `Some`.
|
||||||
sigma: Option<f64>,
|
score_sigma: Option<f64>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,20 +107,20 @@ impl Outcome {
|
|||||||
pub fn scores<I: IntoIterator<Item = f64>>(scores: I) -> Self {
|
pub fn scores<I: IntoIterator<Item = f64>>(scores: I) -> Self {
|
||||||
Self::Scored {
|
Self::Scored {
|
||||||
scores: scores.into_iter().collect(),
|
scores: scores.into_iter().collect(),
|
||||||
sigma: None,
|
score_sigma: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Explicit per-team continuous scores with a per-event noise override.
|
/// Explicit per-team continuous scores with a per-event noise override.
|
||||||
///
|
///
|
||||||
/// `sigma` must be `> 0.0`. Constructing an `Outcome` with a non-positive
|
/// `score_sigma` must be `> 0.0`. Constructing an `Outcome` with a
|
||||||
/// or NaN sigma is allowed; the value is rejected with
|
/// non-positive or NaN value is allowed; the value is rejected with
|
||||||
/// `InferenceError::InvalidParameter` when the event is ingested, so
|
/// `InferenceError::InvalidParameter` when the event is ingested, so
|
||||||
/// callers get an error rather than a panic.
|
/// callers get an error rather than a panic.
|
||||||
pub fn scores_with_sigma<I: IntoIterator<Item = f64>>(scores: I, sigma: f64) -> Self {
|
pub fn scores_with_sigma<I: IntoIterator<Item = f64>>(scores: I, score_sigma: f64) -> Self {
|
||||||
Self::Scored {
|
Self::Scored {
|
||||||
scores: scores.into_iter().collect(),
|
scores: scores.into_iter().collect(),
|
||||||
sigma: Some(sigma),
|
score_sigma: Some(score_sigma),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +220,7 @@ mod tests {
|
|||||||
fn scores_constructor_leaves_sigma_unset() {
|
fn scores_constructor_leaves_sigma_unset() {
|
||||||
let o = Outcome::scores([3.0, 1.0]);
|
let o = Outcome::scores([3.0, 1.0]);
|
||||||
match o {
|
match o {
|
||||||
Outcome::Scored { scores: _, sigma } => assert!(sigma.is_none()),
|
Outcome::Scored { score_sigma, .. } => assert!(score_sigma.is_none()),
|
||||||
Outcome::Ranked(_) => panic!("expected Scored variant"),
|
Outcome::Ranked(_) => panic!("expected Scored variant"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,7 +229,7 @@ mod tests {
|
|||||||
fn scores_with_sigma_sets_sigma_some() {
|
fn scores_with_sigma_sets_sigma_some() {
|
||||||
let o = Outcome::scores_with_sigma([3.0, 1.0], 2.0);
|
let o = Outcome::scores_with_sigma([3.0, 1.0], 2.0);
|
||||||
match o {
|
match o {
|
||||||
Outcome::Scored { scores: _, sigma } => assert_eq!(sigma, Some(2.0)),
|
Outcome::Scored { score_sigma, .. } => assert_eq!(score_sigma, Some(2.0)),
|
||||||
Outcome::Ranked(_) => panic!("expected Scored variant"),
|
Outcome::Ranked(_) => panic!("expected Scored variant"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,7 +241,7 @@ mod tests {
|
|||||||
fn scores_with_sigma_defers_validation_to_ingestion() {
|
fn scores_with_sigma_defers_validation_to_ingestion() {
|
||||||
let o = Outcome::scores_with_sigma([3.0, 1.0], 0.0);
|
let o = Outcome::scores_with_sigma([3.0, 1.0], 0.0);
|
||||||
match o {
|
match o {
|
||||||
Outcome::Scored { sigma, .. } => assert_eq!(sigma, Some(0.0)),
|
Outcome::Scored { score_sigma, .. } => assert_eq!(score_sigma, Some(0.0)),
|
||||||
Outcome::Ranked(_) => panic!("expected Scored variant"),
|
Outcome::Ranked(_) => panic!("expected Scored variant"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user