feat!: Game is the type you get, and one_v_one returns one
`lib.rs` advertised `Game` in "Core types" as "one match in isolation". It had no public constructor: every `Game::*` returned `OwnedGame`, so `let g: Game = Game::ranked(..)?` did not compile. Names swapped. The public type is the owned one — `Game<T, D>`, no lifetime — and the borrowing form is `pub(crate) GameRef<'a, T, D>`, which is what it always was: an implementation detail about whether the result and weight slices are borrowed from `History`'s storage. That distinction meant nothing to someone scoring one match, and it showed the module's surface twice in rustdoc, since both types carried `posteriors()` / `log_evidence()`. `one_v_one` returned `(Gaussian, Gaussian)` while every sibling returned a game, making it the one constructor you could not ask for `log_evidence()`. It returns `Self` now; `.posteriors()` recovers the old shape, and the test that covers it now also asserts the evidence of two identical ratings is exactly `ln(0.5)`. `ranked`, `scored` and `free_for_all` had `# Errors` as their entire doc, so rustdoc's index rendered the error list as the summary. They have summary lines, and `Game` has a worked example — it was advertised as a core type with none anywhere in the crate. Closes #69. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
This commit is contained in:
+120
-81
@@ -102,24 +102,51 @@ impl Default for GameOptions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Owned variant of `Game` returned by public constructors.
|
||||
/// One match, fitted on its own.
|
||||
///
|
||||
/// Unlike `Game<'a, T, D>` (which borrows its result/weights slices from
|
||||
/// History's internal state), `OwnedGame<T, D>` owns the team ratings, so it
|
||||
/// can be returned freely from public constructors. The inference inputs
|
||||
/// themselves are not retained — nothing reads them back.
|
||||
/// Rate a single match against ratings you already hold and read the updated
|
||||
/// beliefs straight back. There is no history behind it: nothing is stored,
|
||||
/// nothing propagates backward, and the priors you hand in are the only
|
||||
/// evidence used. That makes it the wrong tool for the thing this crate exists
|
||||
/// for — [`History`](crate::History) is what infers skill *through time*,
|
||||
/// revising past estimates as later matches arrive, and a sequence of `Game`s
|
||||
/// chained by hand is a forward-only filter, not the same answer.
|
||||
///
|
||||
/// A fitted single match, and nothing more: see [`Game`] for why that is not
|
||||
/// the same as a step of a [`History`](crate::History).
|
||||
/// Reach for it when a history would be overkill or unavailable: a one-off
|
||||
/// matchup, replaying a rating step from stored numbers, checking the engine
|
||||
/// against a reference, or a caller that keeps its own persistence and only
|
||||
/// wants the update rule.
|
||||
///
|
||||
/// ```
|
||||
/// use trueskill_tt::{ConstantDrift, Game, GameOptions, Gaussian, Outcome, Rating};
|
||||
///
|
||||
/// let strong: Rating = Rating::new(Gaussian::from_ms(30.0, 3.0), 1.0, ConstantDrift::new(0.0));
|
||||
/// let weak: Rating = Rating::new(Gaussian::from_ms(20.0, 3.0), 1.0, ConstantDrift::new(0.0));
|
||||
///
|
||||
/// // The underdog wins.
|
||||
/// let game = Game::ranked(
|
||||
/// &[&[weak], &[strong]],
|
||||
/// Outcome::winner(0, 2),
|
||||
/// &GameOptions::default(),
|
||||
/// )?;
|
||||
///
|
||||
/// let posteriors = game.posteriors();
|
||||
/// assert!(posteriors[0][0].mu() > weak.prior().mu(), "the winner gained");
|
||||
/// assert!(posteriors[1][0].mu() < strong.prior().mu(), "the loser lost");
|
||||
///
|
||||
/// // An upset is improbable, and `log_evidence` says so.
|
||||
/// assert!(game.log_evidence() < 0.5_f64.ln());
|
||||
/// # Ok::<(), trueskill_tt::InferenceError>(())
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[must_use]
|
||||
pub struct OwnedGame<T: Time, D: Drift<T>> {
|
||||
pub struct Game<T: Time, D: Drift<T>> {
|
||||
teams: Vec<Vec<Rating<T, D>>>,
|
||||
pub(crate) likelihoods: Vec<Vec<Gaussian>>,
|
||||
pub(crate) log_evidence: f64,
|
||||
}
|
||||
|
||||
impl<T: Time, D: Drift<T>> OwnedGame<T, D> {
|
||||
impl<T: Time, D: Drift<T>> Game<T, D> {
|
||||
pub(crate) fn new(
|
||||
teams: Vec<Vec<Rating<T, D>>>,
|
||||
result: Vec<f64>,
|
||||
@@ -131,7 +158,8 @@ impl<T: Time, D: Drift<T>> OwnedGame<T, D> {
|
||||
|
||||
// `Game` takes the teams by value and is dropped here, so take the vec
|
||||
// back out of it rather than handing it a clone.
|
||||
let g = Game::ranked_with_arena(teams, &result, &weights, p_draw, convergence, &mut arena);
|
||||
let g =
|
||||
GameRef::ranked_with_arena(teams, &result, &weights, p_draw, convergence, &mut arena);
|
||||
|
||||
Self {
|
||||
teams: g.teams,
|
||||
@@ -149,7 +177,7 @@ impl<T: Time, D: Drift<T>> OwnedGame<T, D> {
|
||||
) -> Self {
|
||||
let mut arena = ScratchArena::new();
|
||||
|
||||
let g = Game::scored_with_arena(
|
||||
let g = GameRef::scored_with_arena(
|
||||
teams,
|
||||
&scores,
|
||||
&weights,
|
||||
@@ -204,27 +232,15 @@ impl<T: Time, D: Drift<T>> OwnedGame<T, D> {
|
||||
}
|
||||
}
|
||||
|
||||
/// One match's factor graph, fitted on its own.
|
||||
/// The borrowing form of [`Game`], used only inside the crate.
|
||||
///
|
||||
/// Rate a single match against ratings you already hold and get the updated
|
||||
/// beliefs straight back. There is no history behind it: nothing is stored,
|
||||
/// nothing propagates backward, and the priors you hand in are the only
|
||||
/// evidence used. That makes it the wrong tool for the thing this crate exists
|
||||
/// for — [`History`](crate::History) is what infers skill *through time*,
|
||||
/// revising past estimates as later matches arrive, and a sequence of `Game`s
|
||||
/// chained by hand is a forward-only filter, not the same answer.
|
||||
///
|
||||
/// Reach for `Game` when a history would be overkill or unavailable: a
|
||||
/// one-off matchup, replaying a rating step from stored numbers, checking the
|
||||
/// engine against a reference, or a caller that keeps its own persistence and
|
||||
/// only wants the update rule.
|
||||
///
|
||||
/// The type is mostly a namespace. Its constructors — [`Game::ranked`],
|
||||
/// [`Game::scored`], [`Game::one_v_one`], [`Game::free_for_all`] — return an
|
||||
/// [`OwnedGame`], because `Game<'a, …>` borrows the result and weight slices
|
||||
/// that `History` keeps internally and so cannot be handed out.
|
||||
/// `History` keeps each event's result and weight slices in its own storage
|
||||
/// and sweeps them thousands of times, so the inference core borrows them
|
||||
/// rather than copying. That borrow is the whole difference between this and
|
||||
/// [`Game`]; it is why this type cannot be handed to a caller, and why it is
|
||||
/// not part of the public API.
|
||||
#[derive(Debug)]
|
||||
pub struct Game<'a, T: Time = i64, D: Drift<T> = crate::drift::ConstantDrift> {
|
||||
pub(crate) struct GameRef<'a, T: Time = i64, D: Drift<T> = crate::drift::ConstantDrift> {
|
||||
teams: Vec<Vec<Rating<T, D>>>,
|
||||
result: &'a [f64],
|
||||
weights: &'a [Vec<f64>],
|
||||
@@ -234,7 +250,7 @@ pub struct Game<'a, T: Time = i64, D: Drift<T> = crate::drift::ConstantDrift> {
|
||||
pub(crate) log_evidence: f64,
|
||||
}
|
||||
|
||||
impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
||||
impl<'a, T: Time, D: Drift<T>> GameRef<'a, T, D> {
|
||||
pub(crate) fn ranked_with_arena(
|
||||
teams: Vec<Vec<Rating<T, D>>>,
|
||||
result: &'a [f64],
|
||||
@@ -476,11 +492,12 @@ impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
||||
self.likelihoods = likelihoods;
|
||||
}
|
||||
|
||||
/// Updated skill belief for every competitor, as `[team][member]` in the
|
||||
/// order the teams and members were passed in — prior times this match's
|
||||
/// likelihood, exactly as [`OwnedGame::posteriors`].
|
||||
#[must_use]
|
||||
pub fn posteriors(&self) -> Vec<Vec<Gaussian>> {
|
||||
/// As [`Game::posteriors`].
|
||||
///
|
||||
/// Test-only: inference reads `likelihoods` directly, and `GameRef` is not
|
||||
/// public, so the only callers are this module's own goldens.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn posteriors(&self) -> Vec<Vec<Gaussian>> {
|
||||
self.likelihoods
|
||||
.iter()
|
||||
.zip(self.teams.iter())
|
||||
@@ -492,16 +509,9 @@ impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Natural log of how probable this outcome was under the priors, summed
|
||||
/// over the diff chain's links — as [`OwnedGame::log_evidence`].
|
||||
#[must_use]
|
||||
pub fn log_evidence(&self) -> f64 {
|
||||
self.log_evidence
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Time, D: Drift<T>> Game<'_, T, D> {
|
||||
impl<T: Time, D: Drift<T>> Game<T, D> {
|
||||
/// Reject the team shapes inference cannot represent.
|
||||
///
|
||||
/// `run_chain` builds one diff link per adjacent pair of teams, so fewer
|
||||
@@ -525,6 +535,12 @@ impl<T: Time, D: Drift<T>> Game<'_, T, D> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fit one match from an ordinal result.
|
||||
///
|
||||
/// `teams` is `[team][member]`, and `outcome` ranks those teams in the
|
||||
/// same order. Read the result with [`posteriors`](Game::posteriors) and
|
||||
/// [`log_evidence`](Game::log_evidence).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - `InvalidParameter` if `options.convergence` is out of range — an
|
||||
@@ -542,7 +558,7 @@ impl<T: Time, D: Drift<T>> Game<'_, T, D> {
|
||||
teams: &[&[Rating<T, D>]],
|
||||
outcome: crate::Outcome,
|
||||
options: &GameOptions,
|
||||
) -> Result<OwnedGame<T, D>, crate::InferenceError> {
|
||||
) -> Result<Self, crate::InferenceError> {
|
||||
options.convergence.validate()?;
|
||||
Self::validate_teams(teams)?;
|
||||
if !(0.0..1.0).contains(&options.p_draw) {
|
||||
@@ -581,7 +597,7 @@ impl<T: Time, D: Drift<T>> Game<'_, T, D> {
|
||||
let teams_owned: Vec<Vec<Rating<T, D>>> = teams.iter().map(|t| t.to_vec()).collect();
|
||||
let weights: Vec<Vec<f64>> = teams.iter().map(|t| vec![1.0; t.len()]).collect();
|
||||
|
||||
Ok(OwnedGame::new(
|
||||
Ok(Self::new(
|
||||
teams_owned,
|
||||
result,
|
||||
weights,
|
||||
@@ -590,6 +606,12 @@ impl<T: Time, D: Drift<T>> Game<'_, T, D> {
|
||||
))
|
||||
}
|
||||
|
||||
/// Fit one match from continuous scores.
|
||||
///
|
||||
/// Unlike [`ranked`](Game::ranked), the *size* of each adjacent gap is
|
||||
/// evidence: beating a team by ten says more than beating them by one.
|
||||
/// How much more is set by `options.score_sigma`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - `InvalidParameter` if `options.score_sigma` is not strictly positive
|
||||
@@ -602,7 +624,7 @@ impl<T: Time, D: Drift<T>> Game<'_, T, D> {
|
||||
teams: &[&[Rating<T, D>]],
|
||||
outcome: crate::Outcome,
|
||||
options: &GameOptions,
|
||||
) -> Result<OwnedGame<T, D>, crate::InferenceError> {
|
||||
) -> Result<Self, crate::InferenceError> {
|
||||
options.convergence.validate()?;
|
||||
Self::validate_teams(teams)?;
|
||||
if options.score_sigma <= 0.0 || options.score_sigma.is_nan() {
|
||||
@@ -638,7 +660,7 @@ impl<T: Time, D: Drift<T>> Game<'_, T, D> {
|
||||
}
|
||||
let teams_owned: Vec<Vec<Rating<T, D>>> = teams.iter().map(|t| t.to_vec()).collect();
|
||||
let weights: Vec<Vec<f64>> = teams.iter().map(|t| vec![1.0; t.len()]).collect();
|
||||
Ok(OwnedGame::new_scored(
|
||||
Ok(Self::new_scored(
|
||||
teams_owned,
|
||||
scores,
|
||||
weights,
|
||||
@@ -647,7 +669,24 @@ impl<T: Time, D: Drift<T>> Game<'_, T, D> {
|
||||
))
|
||||
}
|
||||
|
||||
/// Convenience wrapper over [`Game::ranked`] for two single-competitor teams.
|
||||
/// Two single-competitor teams: the common case, without the nesting.
|
||||
///
|
||||
/// Returns a `Game` like every other constructor. It used to return
|
||||
/// `(Gaussian, Gaussian)` — the posteriors alone — which made it the one
|
||||
/// member of the family you could not ask for
|
||||
/// [`log_evidence`](Game::log_evidence). Call `.posteriors()` for the old
|
||||
/// shape:
|
||||
///
|
||||
/// ```
|
||||
/// # use trueskill_tt::{ConstantDrift, Game, GameOptions, Gaussian, Outcome, Rating};
|
||||
/// # let a: Rating = Rating::new(Gaussian::from_ms(25.0, 8.0), 4.0, ConstantDrift::new(0.0));
|
||||
/// # let b = a;
|
||||
/// let game = Game::one_v_one(&a, &b, Outcome::winner(0, 2), &GameOptions::default())?;
|
||||
/// let post = game.posteriors();
|
||||
/// let (a_post, b_post) = (post[0][0], post[1][0]);
|
||||
/// # let _ = (a_post, b_post);
|
||||
/// # Ok::<(), trueskill_tt::InferenceError>(())
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -659,12 +698,12 @@ impl<T: Time, D: Drift<T>> Game<'_, T, D> {
|
||||
b: &Rating<T, D>,
|
||||
outcome: crate::Outcome,
|
||||
options: &GameOptions,
|
||||
) -> Result<(Gaussian, Gaussian), crate::InferenceError> {
|
||||
let game = Self::ranked(&[&[*a], &[*b]], outcome, options)?;
|
||||
let post = game.posteriors();
|
||||
Ok((post[0][0], post[1][0]))
|
||||
) -> Result<Self, crate::InferenceError> {
|
||||
Self::ranked(&[&[*a], &[*b]], outcome, options)
|
||||
}
|
||||
|
||||
/// A free-for-all: every competitor is their own one-member team.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Wraps each competitor in a one-member team and delegates to
|
||||
@@ -673,7 +712,7 @@ impl<T: Time, D: Drift<T>> Game<'_, T, D> {
|
||||
competitors: &[&Rating<T, D>],
|
||||
outcome: crate::Outcome,
|
||||
options: &GameOptions,
|
||||
) -> Result<OwnedGame<T, D>, crate::InferenceError> {
|
||||
) -> Result<Self, crate::InferenceError> {
|
||||
let teams: Vec<Vec<Rating<T, D>>> = competitors.iter().map(|p| vec![**p]).collect();
|
||||
let team_refs: Vec<&[Rating<T, D>]> = teams.iter().map(|t| t.as_slice()).collect();
|
||||
Self::ranked(&team_refs, outcome, options)
|
||||
@@ -703,7 +742,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let w = [vec![1.0], vec![1.0]];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![vec![t_a], vec![t_b]],
|
||||
&[0.0, 1.0],
|
||||
&w,
|
||||
@@ -731,7 +770,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let w = [vec![1.0], vec![1.0]];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![vec![t_a], vec![t_b]],
|
||||
&[0.0, 1.0],
|
||||
&w,
|
||||
@@ -759,7 +798,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let w = [vec![1.0], vec![1.0]];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![vec![t_a], vec![t_b]],
|
||||
&[0.0, 1.0],
|
||||
&w,
|
||||
@@ -793,7 +832,7 @@ mod tests {
|
||||
];
|
||||
|
||||
let w = [vec![1.0], vec![1.0], vec![1.0]];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
teams.clone(),
|
||||
&[1.0, 2.0, 0.0],
|
||||
&w,
|
||||
@@ -810,7 +849,7 @@ mod tests {
|
||||
assert_ulps_eq!(b, Gaussian::from_ms(31.311358, 6.698818), epsilon = 1e-6);
|
||||
|
||||
let w = [vec![1.0], vec![1.0], vec![1.0]];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
teams.clone(),
|
||||
&[2.0, 1.0, 0.0],
|
||||
&w,
|
||||
@@ -827,7 +866,7 @@ mod tests {
|
||||
assert_ulps_eq!(b, Gaussian::from_ms(25.000000, 6.238469), epsilon = 1e-6);
|
||||
|
||||
let w = [vec![1.0], vec![1.0], vec![1.0]];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
teams,
|
||||
&[1.0, 2.0, 0.0],
|
||||
&w,
|
||||
@@ -867,7 +906,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let w = [vec![1.0], vec![1.0]];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![vec![t_a], vec![t_b]],
|
||||
&[0.0, 0.0],
|
||||
&w,
|
||||
@@ -899,7 +938,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let w = [vec![1.0], vec![1.0]];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![vec![t_a], vec![t_b]],
|
||||
&[0.0, 0.0],
|
||||
&w,
|
||||
@@ -935,7 +974,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let w = [vec![1.0], vec![1.0], vec![1.0]];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![vec![t_a], vec![t_b], vec![t_c]],
|
||||
&[0.0, 0.0, 0.0],
|
||||
&w,
|
||||
@@ -972,7 +1011,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let w = [vec![1.0], vec![1.0], vec![1.0]];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![vec![t_a], vec![t_b], vec![t_c]],
|
||||
&[0.0, 0.0, 0.0],
|
||||
&w,
|
||||
@@ -1024,7 +1063,7 @@ mod tests {
|
||||
];
|
||||
|
||||
let w = [vec![1.0, 1.0], vec![1.0], vec![1.0, 1.0]];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![t_a, t_b, t_c],
|
||||
&[1.0, 0.0, 0.0],
|
||||
&w,
|
||||
@@ -1058,7 +1097,7 @@ mod tests {
|
||||
)];
|
||||
|
||||
let w = [w_a, w_b];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![t_a.clone(), t_b.clone()],
|
||||
&[1.0, 0.0],
|
||||
&w,
|
||||
@@ -1083,7 +1122,7 @@ mod tests {
|
||||
let w_b = vec![0.7];
|
||||
|
||||
let w = [w_a, w_b];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![t_a.clone(), t_b.clone()],
|
||||
&[1.0, 0.0],
|
||||
&w,
|
||||
@@ -1108,7 +1147,7 @@ mod tests {
|
||||
let w_b = vec![0.7];
|
||||
|
||||
let w = [w_a, w_b];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![t_a, t_b],
|
||||
&[1.0, 0.0],
|
||||
&w,
|
||||
@@ -1144,7 +1183,7 @@ mod tests {
|
||||
)];
|
||||
|
||||
let w = [w_a, w_b];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![t_a, t_b],
|
||||
&[1.0, 0.0],
|
||||
&w,
|
||||
@@ -1180,7 +1219,7 @@ mod tests {
|
||||
)];
|
||||
|
||||
let w = [w_a, w_b];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![t_a, t_b],
|
||||
&[1.0, 0.0],
|
||||
&w,
|
||||
@@ -1226,7 +1265,7 @@ mod tests {
|
||||
let result = vec![10.0, 0.0]; // a beat b by 10
|
||||
let weights = [vec![1.0], vec![1.0]];
|
||||
let mut arena = ScratchArena::new();
|
||||
let g = Game::scored_with_arena(
|
||||
let g = GameRef::scored_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&weights,
|
||||
@@ -1246,7 +1285,7 @@ mod tests {
|
||||
|
||||
// Tighter score_sigma should produce a stronger update.
|
||||
let mut arena2 = ScratchArena::new();
|
||||
let g_tight = Game::scored_with_arena(
|
||||
let g_tight = GameRef::scored_with_arena(
|
||||
vec![vec![prior], vec![prior]],
|
||||
&result,
|
||||
&weights,
|
||||
@@ -1357,7 +1396,7 @@ mod tests {
|
||||
let w_b = vec![0.9, 0.6];
|
||||
|
||||
let w = [w_a, w_b];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![t_a.clone(), t_b.clone()],
|
||||
&[1.0, 0.0],
|
||||
&w,
|
||||
@@ -1392,7 +1431,7 @@ mod tests {
|
||||
let w_b = vec![0.7, 0.4];
|
||||
|
||||
let w = [w_a, w_b];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![t_a.clone(), t_b.clone()],
|
||||
&[1.0, 0.0],
|
||||
&w,
|
||||
@@ -1427,7 +1466,7 @@ mod tests {
|
||||
let w_b = vec![0.7, 2.4];
|
||||
|
||||
let w = [w_a, w_b];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![t_a.clone(), t_b.clone()],
|
||||
&[1.0, 0.0],
|
||||
&w,
|
||||
@@ -1459,7 +1498,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let w = [vec![1.0, 1.0], vec![1.0]];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![
|
||||
t_a.clone(),
|
||||
vec![R::new(
|
||||
@@ -1480,7 +1519,7 @@ mod tests {
|
||||
let w_b = vec![1.0, 0.0];
|
||||
|
||||
let w = [w_a, w_b];
|
||||
let g = Game::ranked_with_arena(
|
||||
let g = GameRef::ranked_with_arena(
|
||||
vec![t_a, t_b.clone()],
|
||||
&[1.0, 0.0],
|
||||
&w,
|
||||
@@ -1505,7 +1544,7 @@ mod tests {
|
||||
|
||||
// Capped at 1 iteration: cannot fully propagate down a 4-team chain.
|
||||
let mut arena = ScratchArena::new();
|
||||
let g_capped = Game::ranked_with_arena(
|
||||
let g_capped = GameRef::ranked_with_arena(
|
||||
teams.clone(),
|
||||
&result,
|
||||
&weights,
|
||||
@@ -1520,7 +1559,7 @@ mod tests {
|
||||
|
||||
// Same inputs, plenty of iterations: fully converged.
|
||||
let mut arena = ScratchArena::new();
|
||||
let g_full = Game::ranked_with_arena(
|
||||
let g_full = GameRef::ranked_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&weights,
|
||||
@@ -1552,7 +1591,7 @@ mod tests {
|
||||
let weights = vec![vec![1.0]; 4];
|
||||
|
||||
let mut arena = ScratchArena::new();
|
||||
let g_undamped = Game::ranked_with_arena(
|
||||
let g_undamped = GameRef::ranked_with_arena(
|
||||
teams.clone(),
|
||||
&result,
|
||||
&weights,
|
||||
@@ -1564,7 +1603,7 @@ mod tests {
|
||||
|
||||
// alpha=0.5 with extra iterations: should reach the same fixed point.
|
||||
let mut arena = ScratchArena::new();
|
||||
let g_damped = Game::ranked_with_arena(
|
||||
let g_damped = GameRef::ranked_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&weights,
|
||||
|
||||
+3
-3
@@ -3053,8 +3053,8 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
ConstantDrift, EPSILON, Event, Game, Gaussian, Member, Outcome, P_DRAW, Team,
|
||||
arena::ScratchArena,
|
||||
ConstantDrift, EPSILON, Event, Gaussian, Member, Outcome, P_DRAW, Team,
|
||||
arena::ScratchArena, game::GameRef,
|
||||
};
|
||||
|
||||
/// #17: a slice's footprint must be O(competitors in the slice), not
|
||||
@@ -3170,7 +3170,7 @@ mod tests {
|
||||
let observed = h.time_slices[1].skills.get(a).unwrap().posterior();
|
||||
|
||||
let w = [vec![1.0], vec![1.0]];
|
||||
let p = Game::ranked_with_arena(
|
||||
let p = GameRef::ranked_with_arena(
|
||||
h.time_slices[1].events[0].within_priors(
|
||||
false,
|
||||
&h.time_slices[1].skills,
|
||||
|
||||
+1
-1
@@ -154,7 +154,7 @@ pub use drift::{ConstantDrift, Drift};
|
||||
pub use error::{InferenceError, UnknownKeys};
|
||||
pub use event::{Event, Member, Team};
|
||||
pub use event_builder::EventBuilder;
|
||||
pub use game::{Game, GameOptions, OwnedGame};
|
||||
pub use game::{Game, GameOptions};
|
||||
pub use gaussian::Gaussian;
|
||||
pub use history::{History, HistoryBuilder, Joint};
|
||||
use matrix::Matrix;
|
||||
|
||||
+14
-9
@@ -9,7 +9,7 @@ use crate::{
|
||||
arena::ScratchArena,
|
||||
color_group::ColorGroups,
|
||||
drift::Drift,
|
||||
game::Game,
|
||||
game::GameRef,
|
||||
gaussian::Gaussian,
|
||||
rating::Rating,
|
||||
storage::{CompetitorStore, SkillStore},
|
||||
@@ -141,10 +141,15 @@ impl Event {
|
||||
let teams = self.within_priors(false, skills, competitors);
|
||||
let result = self.outputs();
|
||||
let g = match self.kind {
|
||||
EventKind::Ranked => {
|
||||
Game::ranked_with_arena(teams, &result, &self.weights, p_draw, convergence, arena)
|
||||
}
|
||||
EventKind::Scored { score_sigma } => Game::scored_with_arena(
|
||||
EventKind::Ranked => GameRef::ranked_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&self.weights,
|
||||
p_draw,
|
||||
convergence,
|
||||
arena,
|
||||
),
|
||||
EventKind::Scored { score_sigma } => GameRef::scored_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&self.weights,
|
||||
@@ -409,7 +414,7 @@ impl<T: Time> TimeSlice<T> {
|
||||
let result = event.outputs();
|
||||
|
||||
let g = match event.kind {
|
||||
EventKind::Ranked => Game::ranked_with_arena(
|
||||
EventKind::Ranked => GameRef::ranked_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&event.weights,
|
||||
@@ -417,7 +422,7 @@ impl<T: Time> TimeSlice<T> {
|
||||
self.convergence,
|
||||
&mut self.arena,
|
||||
),
|
||||
EventKind::Scored { score_sigma } => Game::scored_with_arena(
|
||||
EventKind::Scored { score_sigma } => GameRef::scored_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&event.weights,
|
||||
@@ -724,7 +729,7 @@ impl<T: Time> TimeSlice<T> {
|
||||
let result = event.outputs();
|
||||
match event.kind {
|
||||
EventKind::Ranked => {
|
||||
Game::ranked_with_arena(
|
||||
GameRef::ranked_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&event.weights,
|
||||
@@ -735,7 +740,7 @@ impl<T: Time> TimeSlice<T> {
|
||||
.log_evidence
|
||||
}
|
||||
EventKind::Scored { score_sigma } => {
|
||||
Game::scored_with_arena(
|
||||
GameRef::scored_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&event.weights,
|
||||
|
||||
Reference in New Issue
Block a user