T0 + T1 + T2: engine redesign through new API surface #1
102
examples/atp.rs
102
examples/atp.rs
@@ -1,53 +1,61 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use plotters::prelude::*;
|
use plotters::prelude::*;
|
||||||
|
use smallvec::smallvec;
|
||||||
use time::{Date, Month};
|
use time::{Date, Month};
|
||||||
use trueskill_tt::{History, KeyTable};
|
use trueskill_tt::{Event, History, Member, Outcome, Team, drift::ConstantDrift};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut csv = csv::Reader::open("examples/atp.csv").unwrap();
|
let mut csv = csv::Reader::open("examples/atp.csv").unwrap();
|
||||||
|
|
||||||
let mut composition = Vec::new();
|
|
||||||
let mut results = Vec::new();
|
|
||||||
let mut times = Vec::new();
|
|
||||||
|
|
||||||
let from = Date::from_calendar_date(1900, Month::January, 1).unwrap();
|
let from = Date::from_calendar_date(1900, Month::January, 1).unwrap();
|
||||||
let time_format = time::format_description::parse("[year]-[month]-[day]").unwrap();
|
let time_format = time::format_description::parse("[year]-[month]-[day]").unwrap();
|
||||||
|
|
||||||
let mut index_map = KeyTable::new();
|
let mut events: Vec<Event<i64, String>> = Vec::new();
|
||||||
|
|
||||||
for row in csv.records() {
|
for row in csv.records() {
|
||||||
if &row["double"] == "t" {
|
|
||||||
let w1_id = index_map.get_or_create(&row["w1_id"]);
|
|
||||||
let w2_id = index_map.get_or_create(&row["w2_id"]);
|
|
||||||
|
|
||||||
let l1_id = index_map.get_or_create(&row["l1_id"]);
|
|
||||||
let l2_id = index_map.get_or_create(&row["l2_id"]);
|
|
||||||
|
|
||||||
composition.push(vec![vec![w1_id, w2_id], vec![l1_id, l2_id]]);
|
|
||||||
} else {
|
|
||||||
let w1_id = index_map.get_or_create(&row["w1_id"]);
|
|
||||||
|
|
||||||
let l1_id = index_map.get_or_create(&row["l1_id"]);
|
|
||||||
|
|
||||||
composition.push(vec![vec![w1_id], vec![l1_id]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push(vec![1.0, 0.0]);
|
|
||||||
|
|
||||||
let date = Date::parse(&row["time_start"], &time_format).unwrap();
|
let date = Date::parse(&row["time_start"], &time_format).unwrap();
|
||||||
|
let time = (date - from).whole_days();
|
||||||
|
|
||||||
times.push((date - from).whole_days());
|
if &row["double"] == "t" {
|
||||||
|
events.push(Event {
|
||||||
|
time,
|
||||||
|
teams: smallvec![
|
||||||
|
Team::with_members([
|
||||||
|
Member::new(row["w1_id"].to_owned()),
|
||||||
|
Member::new(row["w2_id"].to_owned()),
|
||||||
|
]),
|
||||||
|
Team::with_members([
|
||||||
|
Member::new(row["l1_id"].to_owned()),
|
||||||
|
Member::new(row["l2_id"].to_owned()),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
outcome: Outcome::winner(0, 2),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
events.push(Event {
|
||||||
|
time,
|
||||||
|
teams: smallvec![
|
||||||
|
Team::with_members([Member::new(row["w1_id"].to_owned())]),
|
||||||
|
Team::with_members([Member::new(row["l1_id"].to_owned())]),
|
||||||
|
],
|
||||||
|
outcome: Outcome::winner(0, 2),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut hist = History::builder().sigma(1.6).gamma(0.036).build();
|
let mut hist: History<i64, _, _, String> = History::builder_with_key()
|
||||||
|
.sigma(1.6)
|
||||||
|
.drift(ConstantDrift(0.036))
|
||||||
|
.convergence(trueskill_tt::ConvergenceOptions {
|
||||||
|
max_iter: 10,
|
||||||
|
epsilon: 0.01,
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
hist.add_events_with_prior(composition, results, times, vec![], HashMap::new())
|
hist.add_events(events).unwrap();
|
||||||
.unwrap();
|
hist.converge().unwrap();
|
||||||
hist.convergence(10, 0.01, true);
|
|
||||||
|
|
||||||
let players = [
|
let players = [
|
||||||
("aggasi", "a092", 38800),
|
("aggasi", "a092", 38800i64),
|
||||||
("borg", "b058", 30300),
|
("borg", "b058", 30300),
|
||||||
("connors", "c044", 31250),
|
("connors", "c044", 31250),
|
||||||
("courier", "c243", 35750),
|
("courier", "c243", 35750),
|
||||||
@@ -64,21 +72,16 @@ fn main() {
|
|||||||
("wilander", "w023", 32600),
|
("wilander", "w023", 32600),
|
||||||
];
|
];
|
||||||
|
|
||||||
let curves = hist.learning_curves_by_index();
|
|
||||||
|
|
||||||
let mut x_spec = (f64::MAX, f64::MIN);
|
let mut x_spec = (f64::MAX, f64::MIN);
|
||||||
let mut y_spec = (f64::MAX, f64::MIN);
|
let mut y_spec = (f64::MAX, f64::MIN);
|
||||||
|
|
||||||
for (id, cutoff) in players
|
for &(_, id, cutoff) in &players {
|
||||||
.iter()
|
for (ts, gs) in hist.learning_curve(id) {
|
||||||
.map(|&(_, id, cutoff)| (index_map.get_or_create(id), cutoff))
|
if ts >= cutoff {
|
||||||
{
|
|
||||||
for (ts, gs) in &curves[&id] {
|
|
||||||
if *ts >= cutoff {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ts = *ts as f64;
|
let ts = ts as f64;
|
||||||
|
|
||||||
if ts < x_spec.0 {
|
if ts < x_spec.0 {
|
||||||
x_spec.0 = ts;
|
x_spec.0 = ts;
|
||||||
@@ -114,24 +117,19 @@ fn main() {
|
|||||||
|
|
||||||
chart.configure_mesh().draw().unwrap();
|
chart.configure_mesh().draw().unwrap();
|
||||||
|
|
||||||
for (idx, (player, id, cutoff)) in players
|
for (idx, &(player, id, cutoff)) in players.iter().enumerate() {
|
||||||
.iter()
|
|
||||||
.map(|&(player, id, cutoff)| (player, index_map.get_or_create(id), cutoff))
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
let mut upper = Vec::new();
|
let mut upper = Vec::new();
|
||||||
let mut lower = Vec::new();
|
let mut lower = Vec::new();
|
||||||
|
|
||||||
for (ts, gs) in curves[&id].iter() {
|
for (ts, gs) in hist.learning_curve(id) {
|
||||||
if *ts >= cutoff {
|
if ts >= cutoff {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.push((*ts as f64, gs.mu()));
|
data.push((ts as f64, gs.mu()));
|
||||||
|
upper.push((ts as f64, gs.mu() + gs.sigma()));
|
||||||
upper.push((*ts as f64, gs.mu() + gs.sigma()));
|
lower.push((ts as f64, gs.mu() - gs.sigma()));
|
||||||
lower.push((*ts as f64, gs.mu() - gs.sigma()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let color = Palette99::pick(idx);
|
let color = Palette99::pick(idx);
|
||||||
|
|||||||
790
src/history.rs
790
src/history.rs
File diff suppressed because it is too large
Load Diff
61
tests/equivalence.rs
Normal file
61
tests/equivalence.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
//! Equivalence tests: every historical golden from the pre-T2 tests is
|
||||||
|
//! reproduced here at the integration level via the new public API.
|
||||||
|
//!
|
||||||
|
//! The in-crate tests in `src/history.rs::tests` and
|
||||||
|
//! `src/time_slice.rs::tests` are the primary regression net for numerical
|
||||||
|
//! behavior. This file provides Game-level goldens that stand alone and are
|
||||||
|
//! more naturally expressed as integration tests.
|
||||||
|
|
||||||
|
use approx::assert_ulps_eq;
|
||||||
|
use trueskill_tt::{ConstantDrift, Game, GameOptions, Gaussian, Outcome, Rating};
|
||||||
|
|
||||||
|
type R = Rating<i64, ConstantDrift>;
|
||||||
|
|
||||||
|
fn ts_rating(mu: f64, sigma: f64, beta: f64, gamma: f64) -> R {
|
||||||
|
R::new(Gaussian::from_ms(mu, sigma), beta, ConstantDrift(gamma))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn game_1v1_golden_matches_historical() {
|
||||||
|
let a = ts_rating(25.0, 25.0 / 3.0, 25.0 / 6.0, 25.0 / 300.0);
|
||||||
|
let b = ts_rating(25.0, 25.0 / 3.0, 25.0 / 6.0, 25.0 / 300.0);
|
||||||
|
let (a_post, b_post) = Game::<i64, _>::one_v_one(&a, &b, Outcome::winner(0, 2)).unwrap();
|
||||||
|
// Historical golden from pre-T2 test_1vs1 (team 0 wins):
|
||||||
|
assert_ulps_eq!(
|
||||||
|
a_post,
|
||||||
|
Gaussian::from_ms(29.205220, 7.194481),
|
||||||
|
epsilon = 1e-6
|
||||||
|
);
|
||||||
|
assert_ulps_eq!(
|
||||||
|
b_post,
|
||||||
|
Gaussian::from_ms(20.794779, 7.194481),
|
||||||
|
epsilon = 1e-6
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn game_1v1_draw_golden() {
|
||||||
|
let a = ts_rating(25.0, 25.0 / 3.0, 25.0 / 6.0, 25.0 / 300.0);
|
||||||
|
let b = ts_rating(25.0, 25.0 / 3.0, 25.0 / 6.0, 25.0 / 300.0);
|
||||||
|
let g = Game::<i64, _>::ranked(
|
||||||
|
&[&[a], &[b]],
|
||||||
|
Outcome::draw(2),
|
||||||
|
&GameOptions {
|
||||||
|
p_draw: 0.25,
|
||||||
|
convergence: Default::default(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let p = g.posteriors();
|
||||||
|
// Historical golden from pre-T2 test_1vs1_draw:
|
||||||
|
assert_ulps_eq!(
|
||||||
|
p[0][0],
|
||||||
|
Gaussian::from_ms(24.999999, 6.469480),
|
||||||
|
epsilon = 1e-6
|
||||||
|
);
|
||||||
|
assert_ulps_eq!(
|
||||||
|
p[1][0],
|
||||||
|
Gaussian::from_ms(24.999999, 6.469480),
|
||||||
|
epsilon = 1e-6
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user