Port from julia version instead

This commit is contained in:
2022-06-18 22:27:38 +02:00
parent 36c3366990
commit dc10504b80
13 changed files with 1141 additions and 1213 deletions

View File

@@ -1,54 +1,76 @@
use std::collections::{HashMap, HashSet};
use crate::{utils, Agent, Batch, Gaussian, Player, PlayerIndex, N_INF};
use crate::{
agent::{self, Agent},
batch::Batch,
gaussian::Gaussian,
player::Player,
sort_time, tuple_gt, tuple_max,
};
pub struct History {
size: usize,
batches: Vec<Batch>,
agents: HashMap<PlayerIndex, Agent>,
// mu: f64,
// sigma: f64,
// beta: f64,
// gamma: f64,
p_draw: f64,
agents: HashMap<String, Agent>,
time: bool,
pub epsilon: f64,
pub iterations: usize,
pub verbose: bool,
mu: f64,
sigma: f64,
beta: f64,
gamma: f64,
p_draw: f64,
online: bool,
weights: Vec<Vec<Vec<f64>>>,
epsilon: f64,
iterations: usize,
}
impl History {
pub fn new(
composition: &[Vec<Vec<PlayerIndex>>],
results: &[Vec<u16>],
times: &[f64],
priors: HashMap<PlayerIndex, Player>,
composition: Vec<Vec<Vec<&str>>>,
results: Vec<Vec<f64>>,
times: Vec<u64>,
weights: Vec<Vec<Vec<f64>>>,
priors: HashMap<String, Player>,
mu: f64,
sigma: f64,
beta: f64,
gamma: f64,
p_draw: f64,
online: bool,
) -> Self {
assert!(
results.is_empty() || results.len() == composition.len(),
"TODO: Add a comment here"
);
assert!(
times.is_empty() || times.len() == composition.len(),
"TODO: Add a comment here"
);
assert!(
weights.is_empty() || weights.len() == composition.len(),
"TODO: Add a comment here"
);
let this_agent = composition
.iter()
.flatten()
.flatten()
.cloned()
.collect::<HashSet<_>>();
let agents = this_agent
.into_iter()
.map(|a| {
.map(|agent| {
let player = priors
.get(a)
.get(agent)
.cloned()
.unwrap_or_else(|| Player::new(Gaussian::new(mu, sigma), beta, gamma, N_INF));
.unwrap_or_else(|| Player::new(Gaussian::new(mu, sigma), beta, gamma));
(
*a,
agent.to_string(),
Agent {
player,
message: N_INF,
last_time: f64::NEG_INFINITY,
..Default::default()
},
)
})
@@ -58,41 +80,45 @@ impl History {
size: composition.len(),
batches: Vec::new(),
agents,
p_draw,
time: !times.is_empty(),
epsilon: 1e-6,
iterations: 30,
verbose: true,
mu,
sigma,
beta,
gamma,
p_draw,
online,
weights: weights.clone(),
epsilon: 0.0,
iterations: 10,
};
this.trueskill(composition, results, times);
this.trueskill(composition, results, times, weights, online);
this
}
fn trueskill(
&mut self,
composition: &[Vec<Vec<PlayerIndex>>],
results: &[Vec<u16>],
times: &[f64],
composition: Vec<Vec<Vec<&str>>>,
results: Vec<Vec<f64>>,
times: Vec<u64>,
weights: Vec<Vec<Vec<f64>>>,
online: bool,
) {
let o = if self.time {
utils::sort_time(times)
sort_time(&times, false)
} else {
(0..composition.len()).collect::<Vec<_>>()
};
let mut i = 0;
let mut last = 0.0;
while i < self.size {
let mut j = i + 1;
let time = if self.time {
times[o[i]]
} else {
i as f64 + 1.0
};
let t = if self.time { times[o[i]] } else { i as u64 + 1 };
while self.time && j < self.size && times[o[j]] == time {
while self.time && j < self.size && times[o[j]] == t {
j += 1;
}
@@ -100,30 +126,67 @@ impl History {
.map(|e| composition[o[e]].clone())
.collect::<Vec<_>>();
let results = (i..j).map(|e| results[o[e]].clone()).collect::<Vec<_>>();
let results = if results.is_empty() {
Vec::new()
} else {
(i..j).map(|e| results[o[e]].clone()).collect::<Vec<_>>()
};
let b = Batch::new(composition, results, time, &mut self.agents, self.p_draw);
let weights = if weights.is_empty() {
Vec::new()
} else {
(i..j).map(|e| weights[o[e]].clone()).collect::<Vec<_>>()
};
let b = Batch::new(
composition,
results,
weights,
t,
self.p_draw,
&mut self.agents,
);
self.batches.push(b);
let idx = self.batches.len() - 1;
if online {
let new = 100.0 * (i as f64 / self.size as f64);
if new != last {
println!("{:.02}%", new);
last = new;
}
for skill in self.batches[idx].skills.values_mut() {
skill.online = skill.forward;
}
self.convergence(self.iterations, self.epsilon, false);
}
let b = &mut self.batches[idx];
for a in b.skills.keys() {
let agent = self.agents.get_mut(a).unwrap();
agent.last_time = if self.time { time } else { f64::INFINITY };
agent.last_time = if self.time { t } else { u64::MAX };
agent.message = b.forward_prior_out(a);
}
i = j;
}
if online {
println!("100.00%");
}
}
fn iteration(&mut self) -> (f64, f64) {
let mut step = (0.0, 0.0);
clean(self.agents.values_mut(), false);
agent::clean(self.agents.values_mut(), false);
for j in (0..self.batches.len() - 1).rev() {
for agent in self.batches[j + 1].skills.keys() {
@@ -142,7 +205,7 @@ impl History {
.fold(step, |step, (a, old)| tuple_max(step, old.delta(new[a])));
}
clean(self.agents.values_mut(), false);
agent::clean(self.agents.values_mut(), false);
for j in 1..self.batches.len() {
for agent in self.batches[j - 1].skills.keys() {
@@ -164,7 +227,7 @@ impl History {
if self.batches.len() == 1 {
let old = self.batches[0].posteriors();
self.batches[0].convergence(&mut self.agents);
self.batches[0].iteration(0, &mut self.agents);
let new = self.batches[0].posteriors();
@@ -176,12 +239,17 @@ impl History {
step
}
pub fn convergence(&mut self) -> ((f64, f64), usize) {
pub fn convergence(
&mut self,
iterations: usize,
epsilon: f64,
verbose: bool,
) -> ((f64, f64), usize) {
let mut step = (f64::INFINITY, f64::INFINITY);
let mut i = 0;
while (step.0 > self.epsilon || step.1 > self.epsilon) && i < self.iterations {
if self.verbose {
while tuple_gt(step, epsilon) && i < iterations {
if verbose {
print!("Iteration = {}", i);
}
@@ -189,20 +257,20 @@ impl History {
i += 1;
if self.verbose {
if verbose {
println!(", step = {:?}", step);
}
}
if self.verbose {
if verbose {
println!("End");
}
(step, i)
}
pub fn learning_curves(&self) -> HashMap<PlayerIndex, Vec<(f64, Gaussian)>> {
let mut data: HashMap<PlayerIndex, Vec<(f64, Gaussian)>> = HashMap::new();
pub fn learning_curves(&self) -> HashMap<String, Vec<(u64, Gaussian)>> {
let mut data: HashMap<String, Vec<(u64, Gaussian)>> = HashMap::new();
for b in &self.batches {
for agent in b.skills.keys() {
@@ -211,266 +279,234 @@ impl History {
if let Some(entry) = data.get_mut(agent) {
entry.push(point);
} else {
data.insert(*agent, vec![point]);
data.insert(agent.to_string(), vec![point]);
}
}
}
data
}
pub fn log_evidence(&self) -> f64 {
self.batches
.iter()
.flat_map(|batch| batch.events.iter())
.map(|event| event.evidence.ln())
.sum()
}
}
fn clean<'a, A: Iterator<Item = &'a mut Agent>>(agents: A, last_time: bool) {
for a in agents {
a.message = N_INF;
if last_time {
a.last_time = f64::NEG_INFINITY;
}
}
}
fn tuple_max(a: (f64, f64), b: (f64, f64)) -> (f64, f64) {
(
if a.0 > b.0 { a.0 } else { b.0 },
if a.1 > b.1 { a.1 } else { b.1 },
)
}
#[cfg(test)]
mod tests {
use approx::assert_ulps_eq;
use crate::{Game, BETA, GAMMA, MU, P_DRAW, SIGMA};
use crate::{Game, Player, BETA, EPSILON, GAMMA, ITERATIONS, MU, P_DRAW, SIGMA};
use super::*;
#[test]
fn test_init() {
let a = PlayerIndex::new(0);
let b = PlayerIndex::new(1);
let c = PlayerIndex::new(2);
let composition = vec![
vec![vec![a], vec![b]],
vec![vec![a], vec![c]],
vec![vec![b], vec![c]],
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 results = vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 0.0]];
let mut priors = HashMap::new();
for k in [a, b, c] {
let player = Player::new(
Gaussian::new(25.0, 25.0 / 3.0),
25.0 / 6.0,
0.15 * 25.0 / 3.0,
N_INF,
for agent in ["a", "b", "c"] {
priors.insert(
agent.to_string(),
Player::new(
Gaussian::new(25.0, 25.0 / 3.0),
25.0 / 6.0,
0.15 * 25.0 / 3.0,
),
);
priors.insert(k, player);
}
let mut h = History::new(
&composition,
&results,
&[1.0, 2.0, 3.0],
composition,
results,
vec![1, 2, 3],
vec![],
priors,
MU,
BETA,
SIGMA,
BETA,
GAMMA,
P_DRAW,
false,
);
let p0 = h.batches[0].posteriors();
assert_ulps_eq!(p0[&a].mu(), 29.205220743876975, epsilon = 0.000001);
assert_ulps_eq!(p0[&a].sigma(), 7.194481422570443, epsilon = 0.000001);
assert_ulps_eq!(p0["a"].mu, 29.205220743876975, epsilon = 0.000001);
assert_ulps_eq!(p0["a"].sigma, 7.194481422570443, epsilon = 0.000001);
let observed = h.batches[1].skills[&a].forward.sigma();
let observed = h.batches[1].skills["a"].forward.sigma;
let gamma: f64 = 0.15 * 25.0 / 3.0;
let expected = (gamma.powi(2) + h.batches[0].posterior(&a).sigma().powi(2)).sqrt();
let expected = (gamma.powi(2) + h.batches[0].posterior("a").sigma.powi(2)).sqrt();
assert_ulps_eq!(observed, expected, epsilon = 0.000001);
let observed = h.batches[1].posterior(&a);
let observed = h.batches[1].posterior("a");
let p = Game::new(
h.batches[1].within_priors(0, &mut h.agents),
vec![0, 1],
h.batches[1].within_priors(0, false, false, &mut h.agents),
vec![0.0, 1.0],
vec![],
P_DRAW,
)
.posteriors();
let expected = p[0][0];
assert_ulps_eq!(observed.mu(), expected.mu(), epsilon = 0.000001);
assert_ulps_eq!(observed.sigma(), expected.sigma(), epsilon = 0.000001);
assert_ulps_eq!(observed.mu, expected.mu, epsilon = 0.000001);
assert_ulps_eq!(observed.sigma, expected.sigma, epsilon = 0.000001);
}
#[test]
fn test_one_batch() {
let a = PlayerIndex::new(0);
let b = PlayerIndex::new(1);
let c = PlayerIndex::new(2);
let composition = vec![
vec![vec![a], vec![b]],
vec![vec![b], vec![c]],
vec![vec![c], vec![a]],
vec![vec!["a"], vec!["b"]],
vec![vec!["b"], vec!["c"]],
vec![vec!["c"], vec!["a"]],
];
let results = vec![vec![1, 0], vec![1, 0], vec![1, 0]];
let times = vec![1.0, 1.0, 1.0];
let results = vec![vec![1.0, 0.0], vec![1.0, 0.0], vec![1.0, 0.0]];
let times = vec![1, 1, 1];
let mut priors = HashMap::new();
for k in [a, b, c] {
for k in ["a", "b", "c"] {
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, player);
priors.insert(k.to_string(), player);
}
let mut h1 = History::new(
&composition,
&results,
&times,
composition,
results,
times,
vec![],
priors,
MU,
BETA,
SIGMA,
BETA,
GAMMA,
P_DRAW,
false,
);
assert_ulps_eq!(
h1.batches[0].posterior(&a).mu(),
h1.batches[0].posterior("a").mu,
22.904409330892914,
epsilon = 0.000001
);
assert_ulps_eq!(
h1.batches[0].posterior(&a).sigma(),
h1.batches[0].posterior("a").sigma,
6.0103304390431,
epsilon = 0.000001
);
assert_ulps_eq!(
h1.batches[0].posterior(&c).mu(),
h1.batches[0].posterior("c").mu,
25.110318212568806,
epsilon = 0.000001
);
assert_ulps_eq!(
h1.batches[0].posterior(&c).sigma(),
h1.batches[0].posterior("c").sigma,
5.866311348102563,
epsilon = 0.000001
);
let (_step, _i) = h1.convergence();
let (_step, _i) = h1.convergence(ITERATIONS, EPSILON, false);
assert_ulps_eq!(
h1.batches[0].posterior(&a).mu(),
h1.batches[0].posterior("a").mu,
25.00000000,
epsilon = 0.000001
);
assert_ulps_eq!(
h1.batches[0].posterior(&a).sigma(),
h1.batches[0].posterior("a").sigma,
5.41921200,
epsilon = 0.000001
);
assert_ulps_eq!(
h1.batches[0].posterior(&c).mu(),
h1.batches[0].posterior("c").mu,
25.00000000,
epsilon = 0.000001
);
assert_ulps_eq!(
h1.batches[0].posterior(&c).sigma(),
h1.batches[0].posterior("c").sigma,
5.41921200,
epsilon = 0.000001
);
let composition = vec![
vec![vec![a], vec![b]],
vec![vec![b], vec![c]],
vec![vec![c], vec![a]],
vec![vec!["a"], vec!["b"]],
vec![vec!["b"], vec!["c"]],
vec![vec!["c"], vec!["a"]],
];
let results = vec![vec![1, 0], vec![1, 0], vec![1, 0]];
let times = vec![1.0, 2.0, 3.0];
let results = vec![vec![1.0, 0.0], vec![1.0, 0.0], vec![1.0, 0.0]];
let times = vec![1, 2, 3];
let mut priors = HashMap::new();
for k in [a, b, c] {
let player = Player::new(
Gaussian::new(25.0, 25.0 / 3.0),
25.0 / 6.0,
25.0 / 300.0,
N_INF,
);
for k in ["a", "b", "c"] {
let player = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0);
priors.insert(k, player);
priors.insert(k.to_string(), player);
}
let mut h2 = History::new(
&composition,
&results,
&times,
composition,
results,
times,
vec![],
priors,
MU,
BETA,
SIGMA,
BETA,
GAMMA,
P_DRAW,
false,
);
assert_ulps_eq!(
h2.batches[2].posterior(&a).mu(),
h2.batches[2].posterior("a").mu,
22.90352227792141,
epsilon = 0.000001
);
assert_ulps_eq!(
h2.batches[2].posterior(&a).sigma(),
h2.batches[2].posterior("a").sigma,
6.011017301320632,
epsilon = 0.000001
);
assert_ulps_eq!(
h2.batches[2].posterior(&c).mu(),
h2.batches[2].posterior("c").mu,
25.110702468366718,
epsilon = 0.000001
);
assert_ulps_eq!(
h2.batches[2].posterior(&c).sigma(),
h2.batches[2].posterior("c").sigma,
5.866811597660157,
epsilon = 0.000001
);
let (_step, _i) = h2.convergence();
let (_step, _i) = h2.convergence(ITERATIONS, EPSILON, false);
assert_ulps_eq!(
h2.batches[2].posterior(&a).mu(),
h2.batches[2].posterior("a").mu,
24.99866831022851,
epsilon = 0.000001
);
assert_ulps_eq!(
h2.batches[2].posterior(&a).sigma(),
h2.batches[2].posterior("a").sigma,
5.420053708148435,
epsilon = 0.000001
);
assert_ulps_eq!(
h2.batches[2].posterior(&c).mu(),
h2.batches[2].posterior("c").mu,
25.000532179593538,
epsilon = 0.000001
);
assert_ulps_eq!(
h2.batches[2].posterior(&c).sigma(),
h2.batches[2].posterior("c").sigma,
5.419827012784138,
epsilon = 0.000001
);
@@ -478,70 +514,63 @@ mod tests {
#[test]
fn test_learning_curves() {
let a = PlayerIndex::new(0);
let b = PlayerIndex::new(1);
let c = PlayerIndex::new(2);
let composition = vec![
vec![vec![a], vec![b]],
vec![vec![b], vec![c]],
vec![vec![c], vec![a]],
vec![vec!["a"], vec!["b"]],
vec![vec!["b"], vec!["c"]],
vec![vec!["c"], vec!["a"]],
];
let results = vec![vec![1, 0], vec![1, 0], vec![1, 0]];
let times = vec![5.0, 6.0, 7.0];
let results = vec![vec![1.0, 0.0], vec![1.0, 0.0], vec![1.0, 0.0]];
let times = vec![5, 6, 7];
let mut priors = HashMap::new();
for k in [a, b, c] {
let player = Player::new(
Gaussian::new(25.0, 25.0 / 3.0),
25.0 / 6.0,
25.0 / 300.0,
N_INF,
);
for k in ["a", "b", "c"] {
let player = Player::new(Gaussian::new(25.0, 25.0 / 3.0), 25.0 / 6.0, 25.0 / 300.0);
priors.insert(k, player);
priors.insert(k.to_string(), player);
}
let mut h = History::new(
&composition,
&results,
&times,
composition,
results,
times,
vec![],
priors,
MU,
BETA,
SIGMA,
BETA,
GAMMA,
P_DRAW,
false,
);
h.convergence();
h.convergence(ITERATIONS, EPSILON, false);
let lc = h.learning_curves();
let aj_e = lc[&a].len();
let cj_e = lc[&c].len();
let aj_e = lc["a"].len();
let cj_e = lc["c"].len();
assert_eq!(lc[&a][0].0, 5.0);
assert_eq!(lc[&a][aj_e - 1].0, 7.0);
assert_eq!(lc["a"][0].0, 5);
assert_eq!(lc["a"][aj_e - 1].0, 7);
assert_ulps_eq!(
lc[&a][aj_e - 1].1.mu(),
lc["a"][aj_e - 1].1.mu,
24.99866831022851,
epsilon = 0.000001
);
assert_ulps_eq!(
lc[&a][aj_e - 1].1.sigma(),
lc["a"][aj_e - 1].1.sigma,
5.420053708148435,
epsilon = 0.000001
);
assert_ulps_eq!(
lc[&c][cj_e - 1].1.mu(),
lc["c"][cj_e - 1].1.mu,
25.000532179593538,
epsilon = 0.000001
);
assert_ulps_eq!(
lc[&c][cj_e - 1].1.sigma(),
lc["c"][cj_e - 1].1.sigma,
5.419827012784138,
epsilon = 0.000001
);
@@ -549,61 +578,59 @@ mod tests {
#[test]
fn test_env_ttt() {
let a = PlayerIndex::new(0);
let b = PlayerIndex::new(1);
let c = PlayerIndex::new(2);
let composition = vec![
vec![vec![a], vec![b]],
vec![vec![a], vec![c]],
vec![vec![b], vec![c]],
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 results = vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 0.0]];
let mut h = History::new(
&composition,
&results,
&[],
composition,
results,
vec![],
vec![],
HashMap::new(),
25.0,
25.0 / 3.0,
25.0 / 6.0,
25.0 / 300.0,
0.0,
false,
);
let (_step, _i) = h.convergence();
let (_step, _i) = h.convergence(ITERATIONS, EPSILON, false);
assert_eq!(h.batches[2].skills[&b].elapsed, 1.0);
assert_eq!(h.batches[2].skills[&c].elapsed, 1.0);
assert_eq!(h.batches[2].skills["b"].elapsed, 1);
assert_eq!(h.batches[2].skills["c"].elapsed, 1);
assert_ulps_eq!(
h.batches[0].posterior(&a).mu(),
h.batches[0].posterior("a").mu,
25.0002673,
epsilon = 0.000001
);
assert_ulps_eq!(
h.batches[0].posterior(&a).sigma(),
h.batches[0].posterior("a").sigma,
5.41938162,
epsilon = 0.000001
);
assert_ulps_eq!(
h.batches[0].posterior(&b).mu(),
h.batches[0].posterior("b").mu,
24.999465,
epsilon = 0.000001
);
assert_ulps_eq!(
h.batches[0].posterior(&b).sigma(),
h.batches[0].posterior("b").sigma,
5.419425831,
epsilon = 0.000001
);
assert_ulps_eq!(
h.batches[2].posterior(&b).mu(),
h.batches[2].posterior("b").mu,
25.00053219,
epsilon = 0.000001
);
assert_ulps_eq!(
h.batches[2].posterior(&b).sigma(),
h.batches[2].posterior("b").sigma,
5.419696790,
epsilon = 0.000001
);