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:
+1
-1
@@ -203,7 +203,7 @@ fn predict_quality_two_teams() {
|
||||
h.record_winner(&"a", &"b", 1).unwrap();
|
||||
let _ = h.converge().unwrap();
|
||||
|
||||
let q = h.predict_quality(&[&[&"a"], &[&"b"]]).unwrap();
|
||||
let q = h.quality(&[&[&"a"], &[&"b"]]).unwrap();
|
||||
assert!(q > 0.0 && q <= 1.0);
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ fn every_magnitude_parameter_rejects_a_negative_value() {
|
||||
}),
|
||||
),
|
||||
(
|
||||
"Outcome::scores_with_sigma (at ingestion)",
|
||||
"Outcome::scores_with_noise (at ingestion)",
|
||||
Box::new(|v| {
|
||||
let mut h = History::builder().build();
|
||||
h.add_events(vec![trueskill_tt::Event {
|
||||
@@ -111,7 +111,7 @@ fn every_magnitude_parameter_rejects_a_negative_value() {
|
||||
trueskill_tt::Team::with_members([Member::new("a")]),
|
||||
trueskill_tt::Team::with_members([Member::new("b")]),
|
||||
],
|
||||
outcome: Outcome::scores_with_sigma([3.0, 1.0], v),
|
||||
outcome: Outcome::scores_with_noise([3.0, 1.0], v),
|
||||
}])
|
||||
.is_err()
|
||||
}),
|
||||
|
||||
@@ -222,7 +222,7 @@ fn scored_event_rejects_non_positive_sigma() {
|
||||
.event(1)
|
||||
.team(["a"])
|
||||
.team(["b"])
|
||||
.scores_with_sigma([3.0, 1.0], f64::NAN)
|
||||
.scores_with_noise([3.0, 1.0], f64::NAN)
|
||||
.commit()
|
||||
.unwrap_err();
|
||||
assert!(matches!(
|
||||
|
||||
@@ -52,7 +52,7 @@ fn every_team_shaped_query_accepts_the_same_slice() {
|
||||
let h = owned();
|
||||
let teams: &[&[&str]] = &[&["alice"], &["bob"]];
|
||||
|
||||
h.predict_quality(teams).expect("quality");
|
||||
h.quality(teams).expect("quality");
|
||||
let _ = h.predict_outcome(teams).expect("outcome");
|
||||
h.predict_ranking(teams, &[0, 1]).expect("ranking");
|
||||
h.expected_information_gain(teams)
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@ fn unknown_keys_are_reported_not_silently_dropped() {
|
||||
h.predict_win_probabilities(&[&[&"a"], &[&"ghost"]])
|
||||
.is_err()
|
||||
);
|
||||
assert!(h.predict_quality(&[&[&"a"], &[&"ghost"]]).is_err());
|
||||
assert!(h.quality(&[&[&"a"], &[&"ghost"]]).is_err());
|
||||
assert!(h.predict_ranking(&[&[&"a"], &[&"ghost"]], &[0, 1]).is_err());
|
||||
}
|
||||
|
||||
@@ -407,7 +407,7 @@ fn prior_reaches_every_prediction_entry_point() {
|
||||
let h = history_with_policy(&["a", "b"], trueskill_tt::UnknownKeys::Prior);
|
||||
let teams: &[&[&&str]] = &[&[&"a"], &[&"ghost"]];
|
||||
|
||||
assert!(h.predict_quality(teams).is_ok());
|
||||
assert!(h.quality(teams).is_ok());
|
||||
assert!(h.predict_win_probabilities(teams).is_ok());
|
||||
assert!(h.predict_outcome(teams).is_ok());
|
||||
assert!(h.predict_ranking(teams, &[0, 1]).is_ok());
|
||||
|
||||
@@ -78,7 +78,7 @@ macro_rules! all_predictions {
|
||||
($h:ident, $f:expr) => {{
|
||||
let teams: &[&[&&'static str]] = &[&[&"a"], &[&"b"]];
|
||||
let f = $f;
|
||||
f("predict_quality", $h.predict_quality(teams).map(|_| ()));
|
||||
f("quality", $h.quality(teams).map(|_| ()));
|
||||
f(
|
||||
"predict_win_probabilities",
|
||||
$h.predict_win_probabilities(teams).map(|_| ()),
|
||||
@@ -116,7 +116,7 @@ fn degenerate_performances_are_refused_rather_than_answered_wrongly() {
|
||||
assert_eq!(skill.sigma(), 0.0);
|
||||
assert!(skill.mu().is_finite());
|
||||
|
||||
// `predict_quality` previously PANICKED here, out of a method that returns
|
||||
// `quality` previously PANICKED here, out of a method that returns
|
||||
// `Result`: the contrast covariance is exactly singular when beta is zero
|
||||
// and every skill is a point mass.
|
||||
all_predictions!(h, |name: &str, r: Result<(), InferenceError>| {
|
||||
|
||||
+2
-5
@@ -110,11 +110,8 @@ fn history_predict_quality_supports_three_teams() {
|
||||
h.record_winner(&"b", &"c", 2).unwrap();
|
||||
let _ = h.converge().unwrap();
|
||||
|
||||
let q = h.predict_quality(&[&[&"a"], &[&"b"], &[&"c"]]).unwrap();
|
||||
assert!(
|
||||
q.is_finite(),
|
||||
"3-team predict_quality must be finite, got {q}"
|
||||
);
|
||||
let q = h.quality(&[&[&"a"], &[&"b"], &[&"c"]]).unwrap();
|
||||
assert!(q.is_finite(), "3-team quality must be finite, got {q}");
|
||||
assert!((0.0..=1.0).contains(&q), "out of range: {q}");
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -139,7 +139,7 @@ fn ingestion_rejects_a_tie_without_a_draw_probability() {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Outcome::scores_with_sigma` documents that a non-positive sigma is
|
||||
/// `Outcome::scores_with_noise` documents that a non-positive sigma is
|
||||
/// accepted at construction and rejected at ingestion.
|
||||
#[test]
|
||||
fn ingestion_rejects_a_non_positive_per_event_score_sigma() {
|
||||
@@ -152,7 +152,7 @@ fn ingestion_rejects_a_non_positive_per_event_score_sigma() {
|
||||
Team::with_members([Member::new("a")]),
|
||||
Team::with_members([Member::new("b")]),
|
||||
],
|
||||
outcome: Outcome::scores_with_sigma([21.0, 9.0], sigma),
|
||||
outcome: Outcome::scores_with_noise([21.0, 9.0], sigma),
|
||||
}])
|
||||
.expect_err("a non-positive per-event sigma must be rejected");
|
||||
assert!(
|
||||
|
||||
Reference in New Issue
Block a user