refactor!: scores_with_noise, and History::quality
Two names that described the wrong thing.
`scores_with_sigma(scores, sigma)` reads as "these scores have prior
sigma 2.0". The quantity is observation noise on the score *margin*, in
the units of the scores, and it is spelled `score_sigma` at every config
site — `HistoryBuilder::score_sigma`, `GameOptions::score_sigma`,
`EventKind::Scored { score_sigma }` — so this was the one place the
crate used a third meaning of "sigma" for it. Its own doc had to
disambiguate itself: "`sigma` overrides `HistoryBuilder::score_sigma`".
`scores_with_noise(scores, score_sigma)` on both `Outcome` and
`EventBuilder`.
`predict_quality` predicts nothing. Its own doc says it answers "is this
matchup *fair*", not "what will happen", and the `predict_*` family is
otherwise exactly the methods returning a probability or a distribution
over outcomes. `History::quality` also makes the free/method pair
consistent: free `quality` pairs with `History::quality` the way free
`expected_information_gain` already pairs with
`History::expected_information_gain`. The rule that was already being
followed and never stated — a free function scores a hypothetical from
explicit parameters, the same-named method asks it against the fit — is
now written on the method.
Closes #75. Refs #78 (part 4).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
This commit is contained in:
+9
-4
@@ -113,11 +113,16 @@ impl Outcome {
|
||||
|
||||
/// Explicit per-team continuous scores with a per-event noise override.
|
||||
///
|
||||
/// The noise is on the *observed score margin*, in the units of the scores
|
||||
/// themselves — it is not a skill sigma, which is what the old name
|
||||
/// `scores_with_sigma` read as. It overrides `HistoryBuilder::score_sigma`
|
||||
/// for this event only.
|
||||
///
|
||||
/// `score_sigma` must be `> 0.0`. Constructing an `Outcome` with a
|
||||
/// non-positive or NaN value is allowed; the value is rejected with
|
||||
/// `InferenceError::InvalidParameter` when the event is ingested, so
|
||||
/// callers get an error rather than a panic.
|
||||
pub fn scores_with_sigma<I: IntoIterator<Item = f64>>(scores: I, score_sigma: f64) -> Self {
|
||||
pub fn scores_with_noise<I: IntoIterator<Item = f64>>(scores: I, score_sigma: f64) -> Self {
|
||||
Self::Scored {
|
||||
scores: scores.into_iter().collect(),
|
||||
score_sigma: Some(score_sigma),
|
||||
@@ -211,7 +216,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn scores_with_sigma_round_trips() {
|
||||
let o = Outcome::scores_with_sigma([10.0, 4.0], 0.5);
|
||||
let o = Outcome::scores_with_noise([10.0, 4.0], 0.5);
|
||||
assert_eq!(o.team_count(), 2);
|
||||
assert_eq!(o.as_scores(), Some(&[10.0, 4.0][..]));
|
||||
}
|
||||
@@ -227,7 +232,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn scores_with_sigma_sets_sigma_some() {
|
||||
let o = Outcome::scores_with_sigma([3.0, 1.0], 2.0);
|
||||
let o = Outcome::scores_with_noise([3.0, 1.0], 2.0);
|
||||
match o {
|
||||
Outcome::Scored { score_sigma, .. } => assert_eq!(score_sigma, Some(2.0)),
|
||||
Outcome::Ranked(_) => panic!("expected Scored variant"),
|
||||
@@ -239,7 +244,7 @@ mod tests {
|
||||
/// `tests/degenerate_inputs.rs::scored_event_rejects_non_positive_sigma`.
|
||||
#[test]
|
||||
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_noise([3.0, 1.0], 0.0);
|
||||
match o {
|
||||
Outcome::Scored { score_sigma, .. } => assert_eq!(score_sigma, Some(0.0)),
|
||||
Outcome::Ranked(_) => panic!("expected Scored variant"),
|
||||
|
||||
Reference in New Issue
Block a user