Handle case where there is no time

This commit is contained in:
2022-06-13 21:55:43 +02:00
parent 82e7b22443
commit 1b6e07225b
7 changed files with 447347 additions and 59 deletions

View File

@@ -5,3 +5,7 @@ edition = "2021"
[dev-dependencies] [dev-dependencies]
approx = "0.5.1" approx = "0.5.1"
time = { version = "0.3.9", features = ["parsing"] }
[profile.release]
debug = true

447029
examples/atp.csv Normal file

File diff suppressed because it is too large Load Diff

203
examples/atp.rs Normal file
View File

@@ -0,0 +1,203 @@
use std::collections::HashMap;
use time::Date;
use trueskill_tt::{History, BETA, MU, P_DRAW};
fn main() {
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 time_format = time::format_description::parse("[year]-[month]-[day]").unwrap();
for row in csv.records() {
if &row["double"] == "t" {
composition.push(vec![
vec![row["w1_id"].to_string(), row["w2_id"].to_string()],
vec![row["l1_id"].to_string(), row["l2_id"].to_string()],
]);
} else {
composition.push(vec![
vec![row["w1_id"].to_string()],
vec![row["l1_id"].to_string()],
]);
}
results.push(vec![1, 0]);
let time = Date::parse(&row["time_start"], &time_format)
.unwrap()
.midnight()
.assume_utc()
.unix_timestamp();
times.push(time as u64);
}
let mut h = History::new(
composition,
results,
times,
HashMap::new(),
MU,
1.6,
BETA,
0.036,
P_DRAW,
);
h.convergence();
/*
composition,
results,
times,
priors,
MU,
BETA,
SIGMA,
GAMMA,
P_DRAW,
*/
/*
let mut priors = HashMap::new();
for k in ["aj", "bj", "cj"] {
let player = Player::new(
Gaussian::new(25.0, 25.0 / 3.0),
25.0 / 6.0,
0.15 * 25.0 / 3.0,
N_INF,
);
priors.insert(k.to_string(), player);
}
let mut h1 = History::new(
composition,
results,
times,
priors,
MU,
BETA,
SIGMA,
GAMMA,
P_DRAW,
);
*/
/*
let columns = data
.lines()
.skip(1)
.map(|line| {
let columns = line.split(',').collect::<Vec<_>>();
Column {
w1_id: columns[3],
w2_id: columns[3],
l1_id: columns[3],
l2_id: columns[3],
double: columns[3],
}
})
.collect::<Vec<_>>();
*/
// match_id,double,round_number,w1_id,w1_name,w2_id,w2_name,l1_id,l1_name,l2_id,l2_name,time_start,time_end,ground,tour_id,tour_name
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
import pandas as pd
#sudo pip3 install trueskillthroughtime
from trueskillthroughtime import *
import time
from datetime import datetime
# Data
df = pd.read_csv('input/history.csv', low_memory=False)
columns = zip(df.w1_id, df.w2_id, df.l1_id, df.l2_id, df.double)
composition = [[[w1,w2],[l1,l2]] if d == 't' else [[w1],[l1]] for w1, w2, l1, l2, d in columns ]
times = [ datetime.strptime(t, "%Y-%m-%d").timestamp()/(60*60*24) for t in df.time_start]
#start = time.time()
h = History(composition = composition, times = times, sigma = 1.6, gamma = 0.036)
h.convergence(epsilon=0.01, iterations=10)
#end = time.time()
#print(end-start)
*/
}
mod csv {
use std::fs::File;
use std::io::{self, BufRead, BufReader, Lines};
use std::ops;
use std::path::Path;
pub struct Reader {
header_map: Vec<String>,
lines: Lines<BufReader<File>>,
}
impl Reader {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
let mut lines = File::open(path).map(BufReader::new)?.lines();
let header_map = if let Some(header) = lines.next() {
let header = header?;
header.split(',').map(Into::into).collect::<Vec<_>>()
} else {
Vec::new()
};
Ok(Self { header_map, lines })
}
pub fn records(&mut self) -> Records<'_> {
Records {
header_map: &self.header_map,
lines: &mut self.lines,
}
}
}
pub struct Records<'a> {
header_map: &'a Vec<String>,
lines: &'a mut Lines<BufReader<File>>,
}
impl<'a> Iterator for Records<'a> {
type Item = Record<'a>;
fn next(&mut self) -> Option<Self::Item> {
let line = self.lines.next()?;
Some(Record {
header_map: self.header_map,
columns: line.unwrap().split(',').map(Into::into).collect::<Vec<_>>(),
})
}
}
pub struct Record<'a> {
header_map: &'a Vec<String>,
columns: Vec<String>,
}
impl<'a> ops::Index<&str> for Record<'a> {
type Output = str;
fn index(&self, index: &str) -> &Self::Output {
&self.columns[self
.header_map
.iter()
.position(|header| header == index)
.unwrap()]
}
}
}

View File

@@ -46,19 +46,19 @@ impl Agent {
} }
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct Item { pub struct Item {
name: String, name: String,
likelihood: Gaussian, likelihood: Gaussian,
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct Team { pub struct Team {
items: Vec<Item>, items: Vec<Item>,
output: u16, output: u16,
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct Event { pub struct Event {
teams: Vec<Team>, teams: Vec<Team>,
pub evidence: f64, pub evidence: f64,
@@ -91,7 +91,7 @@ fn compute_elapsed(last_time: f64, actual_time: f64) -> f64 {
} }
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct Batch { pub struct Batch {
pub skills: HashMap<String, Skill>, pub skills: HashMap<String, Skill>,
pub events: Vec<Event>, pub events: Vec<Event>,
@@ -100,8 +100,8 @@ pub struct Batch {
} }
impl Batch { impl Batch {
pub fn new( pub fn new<C: AsRef<str> + Clone>(
composition: Vec<Vec<Vec<&str>>>, composition: Vec<Vec<Vec<C>>>,
results: Vec<Vec<u16>>, results: Vec<Vec<u16>>,
time: f64, time: f64,
agents: &mut HashMap<String, Agent>, agents: &mut HashMap<String, Agent>,
@@ -119,9 +119,9 @@ impl Batch {
this this
} }
pub fn add_events( pub fn add_events<C: AsRef<str> + Clone>(
&mut self, &mut self,
composition: Vec<Vec<Vec<&str>>>, composition: Vec<Vec<Vec<C>>>,
results: Vec<Vec<u16>>, results: Vec<Vec<u16>>,
agents: &mut HashMap<String, Agent>, agents: &mut HashMap<String, Agent>,
) { ) {
@@ -129,20 +129,21 @@ impl Batch {
.iter() .iter()
.flatten() .flatten()
.flatten() .flatten()
.cloned() .map(AsRef::as_ref)
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
for a in this_agent { for a in this_agent {
let elapsed = compute_elapsed(agents[a].last_time, self.time); let elapsed = compute_elapsed(agents[a].last_time, self.time);
if let Some(skill) = self.skills.get_mut(a) { if let Some(skill) = self.skills.get_mut(a) {
skill.elapsed = elapsed;
skill.forward = agents[a].receive(elapsed); skill.forward = agents[a].receive(elapsed);
skill.elapsed = elapsed;
} else { } else {
self.skills.insert( self.skills.insert(
a.to_string(), a.to_string(),
Skill { Skill {
forward: agents[a].receive(elapsed), forward: agents[a].receive(elapsed),
elapsed,
..Default::default() ..Default::default()
}, },
); );
@@ -156,7 +157,7 @@ impl Batch {
.map(|t| { .map(|t| {
let items = (0..composition[e][t].len()) let items = (0..composition[e][t].len())
.map(|a| Item { .map(|a| Item {
name: composition[e][t][a].to_string(), name: composition[e][t][a].as_ref().to_string(),
likelihood: N_INF, likelihood: N_INF,
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@@ -4,8 +4,8 @@ use crate::{utils, Agent, Batch, Gaussian, Player, N_INF};
pub struct History { pub struct History {
size: usize, size: usize,
batches: Vec<Batch>, pub batches: Vec<Batch>,
agents: HashMap<String, Agent>, pub agents: HashMap<String, Agent>,
mu: f64, mu: f64,
sigma: f64, sigma: f64,
gamma: f64, gamma: f64,
@@ -14,14 +14,14 @@ pub struct History {
} }
impl History { impl History {
pub fn new( pub fn new<C: AsRef<str> + Clone>(
composition: Vec<Vec<Vec<&str>>>, composition: Vec<Vec<Vec<C>>>,
results: Vec<Vec<u16>>, results: Vec<Vec<u16>>,
times: Vec<u64>, times: Vec<u64>,
priors: HashMap<String, Player>, priors: HashMap<String, Player>,
mu: f64, mu: f64,
beta: f64,
sigma: f64, sigma: f64,
beta: f64,
gamma: f64, gamma: f64,
p_draw: f64, p_draw: f64,
) -> Self { ) -> Self {
@@ -29,7 +29,7 @@ impl History {
.iter() .iter()
.flatten() .flatten()
.flatten() .flatten()
.cloned() .map(AsRef::as_ref)
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
let agents = this_agent let agents = this_agent
@@ -67,20 +67,25 @@ impl History {
this this
} }
fn trueskill( fn trueskill<C: AsRef<str> + Clone>(
&mut self, &mut self,
composition: Vec<Vec<Vec<&str>>>, composition: Vec<Vec<Vec<C>>>,
results: Vec<Vec<u16>>, results: Vec<Vec<u16>>,
times: Vec<u64>, times: Vec<u64>,
) { ) {
let o = utils::sortperm(&times, false); let o = if self.time {
utils::sortperm(&times, false)
} else {
(0..composition.len()).collect::<Vec<_>>()
};
let mut i = 0; let mut i = 0;
while i < self.size { while i < self.size {
let mut j = i + 1; let mut j = i + 1;
let t = times[o[i]]; let t = if self.time { times[o[i]] } else { i as u64 + 1 };
while j < self.size && times[o[j]] == t { while self.time && j < self.size && times[o[j]] == t {
j += 1; j += 1;
} }
@@ -103,7 +108,7 @@ impl History {
for a in b.skills.keys() { for a in b.skills.keys() {
let agent = self.agents.get_mut(a).unwrap(); let agent = self.agents.get_mut(a).unwrap();
agent.last_time = t as f64; agent.last_time = if self.time { t as f64 } else { f64::INFINITY };
agent.message = b.forward_prior_out(a); agent.message = b.forward_prior_out(a);
} }
@@ -443,22 +448,22 @@ mod tests {
assert_ulps_eq!( assert_ulps_eq!(
h2.batches[2].posterior("aj").mu(), h2.batches[2].posterior("aj").mu(),
24.99999999, 24.99866831022851,
epsilon = 0.000001 epsilon = 0.000001
); );
assert_ulps_eq!( assert_ulps_eq!(
h2.batches[2].posterior("aj").sigma(), h2.batches[2].posterior("aj").sigma(),
5.419212002, 5.420053708148435,
epsilon = 0.000001 epsilon = 0.000001
); );
assert_ulps_eq!( assert_ulps_eq!(
h2.batches[2].posterior("cj").mu(), h2.batches[2].posterior("cj").mu(),
24.99999999, 25.000532179593538,
epsilon = 0.000001 epsilon = 0.000001
); );
assert_ulps_eq!( assert_ulps_eq!(
h2.batches[2].posterior("cj").sigma(), h2.batches[2].posterior("cj").sigma(),
5.419212002, 5.419827012784138,
epsilon = 0.000001 epsilon = 0.000001
); );
} }
@@ -510,22 +515,80 @@ mod tests {
assert_ulps_eq!( assert_ulps_eq!(
lc["aj"][aj_e - 1].1.mu(), lc["aj"][aj_e - 1].1.mu(),
24.99999999569006, 24.99866831022851,
epsilon = 0.000001 epsilon = 0.000001
); );
assert_ulps_eq!( assert_ulps_eq!(
lc["aj"][aj_e - 1].1.sigma(), lc["aj"][aj_e - 1].1.sigma(),
5.419212002171145, 5.420053708148435,
epsilon = 0.000001 epsilon = 0.000001
); );
assert_ulps_eq!( assert_ulps_eq!(
lc["cj"][cj_e - 1].1.mu(), lc["cj"][cj_e - 1].1.mu(),
24.999999998686533, 25.000532179593538,
epsilon = 0.000001 epsilon = 0.000001
); );
assert_ulps_eq!( assert_ulps_eq!(
lc["cj"][cj_e - 1].1.sigma(), lc["cj"][cj_e - 1].1.sigma(),
5.419212002245715, 5.419827012784138,
epsilon = 0.000001
);
}
#[test]
fn test_env_ttt() {
let composition = vec![
vec![vec!["a"], vec!["b"]],
vec![vec!["a"], vec!["c"]],
vec![vec!["b"], vec!["c"]],
];
let results = vec![vec![1, 0], vec![0, 1], vec![1, 0]];
let mut h = History::new(
composition,
results,
Vec::new(),
HashMap::new(),
25.0,
25.0 / 3.0,
25.0 / 6.0,
25.0 / 300.0,
0.0,
);
let (_step, _i) = h.convergence();
assert_eq!(h.batches[2].skills["b"].elapsed, 1.0);
assert_eq!(h.batches[2].skills["c"].elapsed, 1.0);
assert_ulps_eq!(
h.batches[0].posterior("a").mu(),
25.0002673,
epsilon = 0.000001
);
assert_ulps_eq!(
h.batches[0].posterior("a").sigma(),
5.41938162,
epsilon = 0.000001
);
assert_ulps_eq!(
h.batches[0].posterior("b").mu(),
24.999465,
epsilon = 0.000001
);
assert_ulps_eq!(
h.batches[0].posterior("b").sigma(),
5.419425831,
epsilon = 0.000001
);
assert_ulps_eq!(
h.batches[2].posterior("b").mu(),
25.00053219,
epsilon = 0.000001
);
assert_ulps_eq!(
h.batches[2].posterior("b").sigma(),
5.419696790,
epsilon = 0.000001 epsilon = 0.000001
); );
} }

View File

@@ -4,37 +4,25 @@ use trueskill_tt::*;
fn main() { fn main() {
let composition = vec![ let composition = vec![
vec![vec!["aj"], vec!["bj"]], vec![vec!["a"], vec!["b"]],
vec![vec!["bj"], vec!["cj"]], vec![vec!["a"], vec!["c"]],
vec![vec!["cj"], vec!["aj"]], vec![vec!["b"], vec!["c"]],
]; ];
let results = vec![vec![1, 0], vec![1, 0], vec![1, 0]]; let results = vec![vec![1, 0], vec![0, 1], vec![1, 0]];
let times = vec![1, 2, 3];
let mut priors = HashMap::new(); let mut h = History::new(
for k in ["aj", "bj", "cj"] {
let player = Player::new(
Gaussian::new(25.0, 25.0 / 3.0),
25.0 / 6.0,
25.0 / 300.0,
N_INF,
);
priors.insert(k.to_string(), player);
}
let mut h2 = History::new(
composition, composition,
results, results,
times, vec![],
priors, HashMap::new(),
MU, 25.0,
BETA, 25.0 / 3.0,
SIGMA, 25.0 / 6.0,
GAMMA, 25.0 / 300.0,
P_DRAW, 0.0,
); );
let (_step, _i) = h2.convergence(); let (_step, _i) = h.convergence();
println!("{:#?}", h.batches);
} }

View File

@@ -69,7 +69,7 @@ pub(crate) fn mu_sigma(tau: f64, pi: f64) -> (f64, f64) {
} }
if pi + 1e-5 < 0.0 { if pi + 1e-5 < 0.0 {
panic!("sigma should be greater than 0"); panic!("pi should be greater than 0, got: {}", pi + 1e-5);
} }
(0.0, f64::INFINITY) (0.0, f64::INFINITY)