T0 + T1 + T2: engine redesign through new API surface #1
@@ -11,7 +11,7 @@ fn criterion_benchmark(criterion: &mut Criterion) {
|
|||||||
let b = index_map.get_or_create("b");
|
let b = index_map.get_or_create("b");
|
||||||
let c = index_map.get_or_create("c");
|
let c = index_map.get_or_create("c");
|
||||||
|
|
||||||
let mut agents: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
let mut agents: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
||||||
|
|
||||||
for agent in [a, b, c] {
|
for agent in [a, b, c] {
|
||||||
agents.insert(
|
agents.insert(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::{
|
|||||||
drift::{ConstantDrift, Drift},
|
drift::{ConstantDrift, Drift},
|
||||||
gaussian::Gaussian,
|
gaussian::Gaussian,
|
||||||
rating::Rating,
|
rating::Rating,
|
||||||
|
time::Time,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Per-history, temporal state for someone competing.
|
/// Per-history, temporal state for someone competing.
|
||||||
@@ -10,41 +11,61 @@ use crate::{
|
|||||||
/// Renamed from `Agent` in T2; the former `.player` field is now
|
/// Renamed from `Agent` in T2; the former `.player` field is now
|
||||||
/// `.rating` to match the `Player → Rating` rename.
|
/// `.rating` to match the `Player → Rating` rename.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Competitor<D: Drift = ConstantDrift> {
|
pub struct Competitor<T: Time = i64, D: Drift<T> = ConstantDrift> {
|
||||||
pub rating: Rating<D>,
|
pub rating: Rating<T, D>,
|
||||||
pub message: Gaussian,
|
pub message: Gaussian,
|
||||||
pub last_time: i64,
|
pub last_time: Option<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Drift> Competitor<D> {
|
impl<T: Time, D: Drift<T>> Competitor<T, D> {
|
||||||
pub(crate) fn receive(&self, elapsed: i64) -> Gaussian {
|
/// Compute the message received at time `now`, with drift accumulated
|
||||||
|
/// from `self.last_time` (if any) to `now`.
|
||||||
|
pub(crate) fn receive(&self, now: &T) -> Gaussian {
|
||||||
|
if self.message != N_INF {
|
||||||
|
let elapsed_variance = match &self.last_time {
|
||||||
|
Some(last) => self.rating.drift.variance_delta(last, now),
|
||||||
|
None => 0.0,
|
||||||
|
};
|
||||||
|
self.message.forget(elapsed_variance)
|
||||||
|
} else {
|
||||||
|
self.rating.prior
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the message using a pre-cached elapsed count (in `Time::elapsed_to` units).
|
||||||
|
///
|
||||||
|
/// Used in convergence sweeps where the elapsed was cached at slice-construction time
|
||||||
|
/// and should not be recomputed from `last_time` (which may have shifted).
|
||||||
|
pub(crate) fn receive_for_elapsed(&self, elapsed: i64) -> Gaussian {
|
||||||
if self.message != N_INF {
|
if self.message != N_INF {
|
||||||
self.message
|
self.message
|
||||||
.forget(self.rating.drift.variance_delta(elapsed))
|
.forget(self.rating.drift.variance_for_elapsed(elapsed))
|
||||||
} else {
|
} else {
|
||||||
self.rating.prior
|
self.rating.prior
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Competitor<ConstantDrift> {
|
impl Default for Competitor<i64, ConstantDrift> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
rating: Rating::default(),
|
rating: Rating::default(),
|
||||||
message: N_INF,
|
message: N_INF,
|
||||||
last_time: i64::MIN,
|
last_time: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn clean<'a, D: Drift + 'a, C: Iterator<Item = &'a mut Competitor<D>>>(
|
pub(crate) fn clean<'a, T, D, C>(competitors: C, last_time: bool)
|
||||||
competitors: C,
|
where
|
||||||
last_time: bool,
|
T: Time + 'a,
|
||||||
) {
|
D: Drift<T> + 'a,
|
||||||
|
C: Iterator<Item = &'a mut Competitor<T, D>>,
|
||||||
|
{
|
||||||
for c in competitors {
|
for c in competitors {
|
||||||
c.message = N_INF;
|
c.message = N_INF;
|
||||||
if last_time {
|
if last_time {
|
||||||
c.last_time = i64::MIN;
|
c.last_time = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/drift.rs
32
src/drift.rs
@@ -1,14 +1,36 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
pub trait Drift: Copy + Debug {
|
use crate::time::Time;
|
||||||
fn variance_delta(&self, elapsed: i64) -> f64;
|
|
||||||
|
/// Governs how much a competitor's skill can drift between two time points.
|
||||||
|
///
|
||||||
|
/// Generic over `T: Time` so seasonal or calendar-aware drift is expressible
|
||||||
|
/// without going through `i64`.
|
||||||
|
pub trait Drift<T: Time>: Copy + Debug {
|
||||||
|
/// Variance added to the skill prior for elapsed time `from -> to`.
|
||||||
|
///
|
||||||
|
/// Called with `from <= to`; returning zero means no drift accumulates.
|
||||||
|
fn variance_delta(&self, from: &T, to: &T) -> f64;
|
||||||
|
|
||||||
|
/// Variance added for a pre-computed elapsed count (in the same units as
|
||||||
|
/// `T::elapsed_to`). Used where the elapsed is already cached as `i64`.
|
||||||
|
fn variance_for_elapsed(&self, elapsed: i64) -> f64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simple constant-per-unit-time drift.
|
||||||
|
///
|
||||||
|
/// For `Time = i64`: variance added is `(to - from) * gamma^2`.
|
||||||
|
/// For `Time = Untimed`: elapsed is always 0, so drift is always 0.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct ConstantDrift(pub f64);
|
pub struct ConstantDrift(pub f64);
|
||||||
|
|
||||||
impl Drift for ConstantDrift {
|
impl<T: Time> Drift<T> for ConstantDrift {
|
||||||
fn variance_delta(&self, elapsed: i64) -> f64 {
|
fn variance_delta(&self, from: &T, to: &T) -> f64 {
|
||||||
elapsed as f64 * self.0 * self.0
|
let elapsed = from.elapsed_to(to).max(0) as f64;
|
||||||
|
elapsed * self.0 * self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn variance_for_elapsed(&self, elapsed: i64) -> f64 {
|
||||||
|
elapsed.max(0) as f64 * self.0 * self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
97
src/game.rs
97
src/game.rs
@@ -8,12 +8,13 @@ use crate::{
|
|||||||
factor::{Factor, trunc::TruncFactor},
|
factor::{Factor, trunc::TruncFactor},
|
||||||
gaussian::Gaussian,
|
gaussian::Gaussian,
|
||||||
rating::Rating,
|
rating::Rating,
|
||||||
|
time::Time,
|
||||||
tuple_gt, tuple_max,
|
tuple_gt, tuple_max,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Game<'a, D: Drift> {
|
pub struct Game<'a, T: Time = i64, D: Drift<T> = crate::drift::ConstantDrift> {
|
||||||
teams: Vec<Vec<Rating<D>>>,
|
teams: Vec<Vec<Rating<T, D>>>,
|
||||||
result: &'a [f64],
|
result: &'a [f64],
|
||||||
weights: &'a [Vec<f64>],
|
weights: &'a [Vec<f64>],
|
||||||
p_draw: f64,
|
p_draw: f64,
|
||||||
@@ -21,9 +22,9 @@ pub struct Game<'a, D: Drift> {
|
|||||||
pub(crate) evidence: f64,
|
pub(crate) evidence: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, D: Drift> Game<'a, D> {
|
impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
teams: Vec<Vec<Rating<D>>>,
|
teams: Vec<Vec<Rating<T, D>>>,
|
||||||
result: &'a [f64],
|
result: &'a [f64],
|
||||||
weights: &'a [Vec<f64>],
|
weights: &'a [Vec<f64>],
|
||||||
p_draw: f64,
|
p_draw: f64,
|
||||||
@@ -227,14 +228,16 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{ConstantDrift, GAMMA, Gaussian, N_INF, Rating, arena::ScratchArena};
|
use crate::{ConstantDrift, GAMMA, Gaussian, N_INF, Rating, arena::ScratchArena};
|
||||||
|
|
||||||
|
type R = Rating<i64, ConstantDrift>;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_1vs1() {
|
fn test_1vs1() {
|
||||||
let t_a = Rating::new(
|
let t_a = R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_b = Rating::new(
|
let t_b = R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
@@ -256,12 +259,12 @@ mod tests {
|
|||||||
assert_ulps_eq!(a, Gaussian::from_ms(20.794779, 7.194481), epsilon = 1e-6);
|
assert_ulps_eq!(a, Gaussian::from_ms(20.794779, 7.194481), epsilon = 1e-6);
|
||||||
assert_ulps_eq!(b, Gaussian::from_ms(29.205220, 7.194481), epsilon = 1e-6);
|
assert_ulps_eq!(b, Gaussian::from_ms(29.205220, 7.194481), epsilon = 1e-6);
|
||||||
|
|
||||||
let t_a = Rating::new(
|
let t_a = R::new(
|
||||||
Gaussian::from_ms(29.0, 1.0),
|
Gaussian::from_ms(29.0, 1.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(GAMMA),
|
ConstantDrift(GAMMA),
|
||||||
);
|
);
|
||||||
let t_b = Rating::new(
|
let t_b = R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(GAMMA),
|
ConstantDrift(GAMMA),
|
||||||
@@ -283,8 +286,8 @@ mod tests {
|
|||||||
assert_ulps_eq!(a, Gaussian::from_ms(28.896475, 0.996604), epsilon = 1e-6);
|
assert_ulps_eq!(a, Gaussian::from_ms(28.896475, 0.996604), epsilon = 1e-6);
|
||||||
assert_ulps_eq!(b, Gaussian::from_ms(32.189211, 6.062063), epsilon = 1e-6);
|
assert_ulps_eq!(b, Gaussian::from_ms(32.189211, 6.062063), epsilon = 1e-6);
|
||||||
|
|
||||||
let t_a = Rating::new(Gaussian::from_ms(1.139, 0.531), 1.0, ConstantDrift(0.2125));
|
let t_a = R::new(Gaussian::from_ms(1.139, 0.531), 1.0, ConstantDrift(0.2125));
|
||||||
let t_b = Rating::new(Gaussian::from_ms(15.568, 0.51), 1.0, ConstantDrift(0.2125));
|
let t_b = R::new(Gaussian::from_ms(15.568, 0.51), 1.0, ConstantDrift(0.2125));
|
||||||
|
|
||||||
let w = [vec![1.0], vec![1.0]];
|
let w = [vec![1.0], vec![1.0]];
|
||||||
let g = Game::new(
|
let g = Game::new(
|
||||||
@@ -302,17 +305,17 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_1vs1vs1() {
|
fn test_1vs1vs1() {
|
||||||
let teams = vec![
|
let teams = vec![
|
||||||
vec![Rating::new(
|
vec![R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
)],
|
)],
|
||||||
vec![Rating::new(
|
vec![R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
)],
|
)],
|
||||||
vec![Rating::new(
|
vec![R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
@@ -367,12 +370,12 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_1vs1_draw() {
|
fn test_1vs1_draw() {
|
||||||
let t_a = Rating::new(
|
let t_a = R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_b = Rating::new(
|
let t_b = R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
@@ -394,12 +397,12 @@ mod tests {
|
|||||||
assert_ulps_eq!(a, Gaussian::from_ms(24.999999, 6.469480), epsilon = 1e-6);
|
assert_ulps_eq!(a, Gaussian::from_ms(24.999999, 6.469480), epsilon = 1e-6);
|
||||||
assert_ulps_eq!(b, Gaussian::from_ms(24.999999, 6.469480), epsilon = 1e-6);
|
assert_ulps_eq!(b, Gaussian::from_ms(24.999999, 6.469480), epsilon = 1e-6);
|
||||||
|
|
||||||
let t_a = Rating::new(
|
let t_a = R::new(
|
||||||
Gaussian::from_ms(25.0, 3.0),
|
Gaussian::from_ms(25.0, 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_b = Rating::new(
|
let t_b = R::new(
|
||||||
Gaussian::from_ms(29.0, 2.0),
|
Gaussian::from_ms(29.0, 2.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
@@ -424,17 +427,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_1vs1vs1_draw() {
|
fn test_1vs1vs1_draw() {
|
||||||
let t_a = Rating::new(
|
let t_a = R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_b = Rating::new(
|
let t_b = R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_c = Rating::new(
|
let t_c = R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
@@ -460,17 +463,17 @@ mod tests {
|
|||||||
assert_ulps_eq!(b, Gaussian::from_ms(25.0, 5.707424), epsilon = 1e-6);
|
assert_ulps_eq!(b, Gaussian::from_ms(25.0, 5.707424), epsilon = 1e-6);
|
||||||
assert_ulps_eq!(c, Gaussian::from_ms(25.0, 5.729069), epsilon = 1e-6);
|
assert_ulps_eq!(c, Gaussian::from_ms(25.0, 5.729069), epsilon = 1e-6);
|
||||||
|
|
||||||
let t_a = Rating::new(
|
let t_a = R::new(
|
||||||
Gaussian::from_ms(25.0, 3.0),
|
Gaussian::from_ms(25.0, 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_b = Rating::new(
|
let t_b = R::new(
|
||||||
Gaussian::from_ms(25.0, 3.0),
|
Gaussian::from_ms(25.0, 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_c = Rating::new(
|
let t_c = R::new(
|
||||||
Gaussian::from_ms(29.0, 2.0),
|
Gaussian::from_ms(29.0, 2.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
@@ -498,29 +501,29 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_2vs1vs2_mixed() {
|
fn test_2vs1vs2_mixed() {
|
||||||
let t_a = vec![
|
let t_a = vec![
|
||||||
Rating::new(
|
R::new(
|
||||||
Gaussian::from_ms(12.0, 3.0),
|
Gaussian::from_ms(12.0, 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
),
|
),
|
||||||
Rating::new(
|
R::new(
|
||||||
Gaussian::from_ms(18.0, 3.0),
|
Gaussian::from_ms(18.0, 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
let t_b = vec![Rating::new(
|
let t_b = vec![R::new(
|
||||||
Gaussian::from_ms(30.0, 3.0),
|
Gaussian::from_ms(30.0, 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
)];
|
)];
|
||||||
let t_c = vec![
|
let t_c = vec![
|
||||||
Rating::new(
|
R::new(
|
||||||
Gaussian::from_ms(14.0, 3.0),
|
Gaussian::from_ms(14.0, 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
),
|
),
|
||||||
Rating::new(
|
R::new(
|
||||||
Gaussian::from_ms(16., 3.0),
|
Gaussian::from_ms(16., 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(25.0 / 300.0),
|
ConstantDrift(25.0 / 300.0),
|
||||||
@@ -549,12 +552,12 @@ mod tests {
|
|||||||
let w_a = vec![1.0];
|
let w_a = vec![1.0];
|
||||||
let w_b = vec![2.0];
|
let w_b = vec![2.0];
|
||||||
|
|
||||||
let t_a = vec![Rating::new(
|
let t_a = vec![R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(0.0),
|
ConstantDrift(0.0),
|
||||||
)];
|
)];
|
||||||
let t_b = vec![Rating::new(
|
let t_b = vec![R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(0.0),
|
ConstantDrift(0.0),
|
||||||
@@ -632,16 +635,8 @@ mod tests {
|
|||||||
let w_a = vec![1.0];
|
let w_a = vec![1.0];
|
||||||
let w_b = vec![0.0];
|
let w_b = vec![0.0];
|
||||||
|
|
||||||
let t_a = vec![Rating::new(
|
let t_a = vec![R::new(Gaussian::from_ms(2.0, 6.0), 1.0, ConstantDrift(0.0))];
|
||||||
Gaussian::from_ms(2.0, 6.0),
|
let t_b = vec![R::new(Gaussian::from_ms(2.0, 6.0), 1.0, ConstantDrift(0.0))];
|
||||||
1.0,
|
|
||||||
ConstantDrift(0.0),
|
|
||||||
)];
|
|
||||||
let t_b = vec![Rating::new(
|
|
||||||
Gaussian::from_ms(2.0, 6.0),
|
|
||||||
1.0,
|
|
||||||
ConstantDrift(0.0),
|
|
||||||
)];
|
|
||||||
|
|
||||||
let w = [w_a, w_b];
|
let w = [w_a, w_b];
|
||||||
let g = Game::new(
|
let g = Game::new(
|
||||||
@@ -667,16 +662,8 @@ mod tests {
|
|||||||
let w_a = vec![1.0];
|
let w_a = vec![1.0];
|
||||||
let w_b = vec![-1.0];
|
let w_b = vec![-1.0];
|
||||||
|
|
||||||
let t_a = vec![Rating::new(
|
let t_a = vec![R::new(Gaussian::from_ms(2.0, 6.0), 1.0, ConstantDrift(0.0))];
|
||||||
Gaussian::from_ms(2.0, 6.0),
|
let t_b = vec![R::new(Gaussian::from_ms(2.0, 6.0), 1.0, ConstantDrift(0.0))];
|
||||||
1.0,
|
|
||||||
ConstantDrift(0.0),
|
|
||||||
)];
|
|
||||||
let t_b = vec![Rating::new(
|
|
||||||
Gaussian::from_ms(2.0, 6.0),
|
|
||||||
1.0,
|
|
||||||
ConstantDrift(0.0),
|
|
||||||
)];
|
|
||||||
|
|
||||||
let w = [w_a, w_b];
|
let w = [w_a, w_b];
|
||||||
let g = Game::new(
|
let g = Game::new(
|
||||||
@@ -694,12 +681,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_2vs2_weighted() {
|
fn test_2vs2_weighted() {
|
||||||
let t_a = vec![
|
let t_a = vec![
|
||||||
Rating::new(
|
R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(0.0),
|
ConstantDrift(0.0),
|
||||||
),
|
),
|
||||||
Rating::new(
|
R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(0.0),
|
ConstantDrift(0.0),
|
||||||
@@ -708,12 +695,12 @@ mod tests {
|
|||||||
let w_a = vec![0.4, 0.8];
|
let w_a = vec![0.4, 0.8];
|
||||||
|
|
||||||
let t_b = vec![
|
let t_b = vec![
|
||||||
Rating::new(
|
R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(0.0),
|
ConstantDrift(0.0),
|
||||||
),
|
),
|
||||||
Rating::new(
|
R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(0.0),
|
ConstantDrift(0.0),
|
||||||
@@ -824,7 +811,7 @@ mod tests {
|
|||||||
let g = Game::new(
|
let g = Game::new(
|
||||||
vec![
|
vec![
|
||||||
t_a.clone(),
|
t_a.clone(),
|
||||||
vec![Rating::new(
|
vec![R::new(
|
||||||
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
Gaussian::from_ms(25.0, 25.0 / 3.0),
|
||||||
25.0 / 6.0,
|
25.0 / 6.0,
|
||||||
ConstantDrift(0.0),
|
ConstantDrift(0.0),
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ use crate::{
|
|||||||
rating::Rating,
|
rating::Rating,
|
||||||
sort_time,
|
sort_time,
|
||||||
storage::CompetitorStore,
|
storage::CompetitorStore,
|
||||||
|
time::Time,
|
||||||
time_slice::{self, TimeSlice},
|
time_slice::{self, TimeSlice},
|
||||||
tuple_gt, tuple_max,
|
tuple_gt, tuple_max,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HistoryBuilder<D: Drift = ConstantDrift> {
|
pub struct HistoryBuilder<T: Time = i64, D: Drift<T> = ConstantDrift> {
|
||||||
time: bool,
|
time: bool,
|
||||||
mu: f64,
|
mu: f64,
|
||||||
sigma: f64,
|
sigma: f64,
|
||||||
@@ -21,9 +22,10 @@ pub struct HistoryBuilder<D: Drift = ConstantDrift> {
|
|||||||
drift: D,
|
drift: D,
|
||||||
p_draw: f64,
|
p_draw: f64,
|
||||||
online: bool,
|
online: bool,
|
||||||
|
_time: std::marker::PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Drift> HistoryBuilder<D> {
|
impl<T: Time, D: Drift<T>> HistoryBuilder<T, D> {
|
||||||
pub fn time(mut self, time: bool) -> Self {
|
pub fn time(mut self, time: bool) -> Self {
|
||||||
self.time = time;
|
self.time = time;
|
||||||
self
|
self
|
||||||
@@ -44,7 +46,7 @@ impl<D: Drift> HistoryBuilder<D> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drift<D2: Drift>(self, drift: D2) -> HistoryBuilder<D2> {
|
pub fn drift<D2: Drift<T>>(self, drift: D2) -> HistoryBuilder<T, D2> {
|
||||||
HistoryBuilder {
|
HistoryBuilder {
|
||||||
drift,
|
drift,
|
||||||
time: self.time,
|
time: self.time,
|
||||||
@@ -53,6 +55,7 @@ impl<D: Drift> HistoryBuilder<D> {
|
|||||||
beta: self.beta,
|
beta: self.beta,
|
||||||
p_draw: self.p_draw,
|
p_draw: self.p_draw,
|
||||||
online: self.online,
|
online: self.online,
|
||||||
|
_time: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +69,7 @@ impl<D: Drift> HistoryBuilder<D> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> History<D> {
|
pub fn build(self) -> History<T, D> {
|
||||||
History {
|
History {
|
||||||
size: 0,
|
size: 0,
|
||||||
time_slices: Vec::new(),
|
time_slices: Vec::new(),
|
||||||
@@ -82,14 +85,14 @@ impl<D: Drift> HistoryBuilder<D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HistoryBuilder<ConstantDrift> {
|
impl HistoryBuilder<i64, ConstantDrift> {
|
||||||
pub fn gamma(mut self, gamma: f64) -> Self {
|
pub fn gamma(mut self, gamma: f64) -> Self {
|
||||||
self.drift = ConstantDrift(gamma);
|
self.drift = ConstantDrift(gamma);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HistoryBuilder<ConstantDrift> {
|
impl Default for HistoryBuilder<i64, ConstantDrift> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
time: true,
|
time: true,
|
||||||
@@ -99,14 +102,15 @@ impl Default for HistoryBuilder<ConstantDrift> {
|
|||||||
drift: ConstantDrift(GAMMA),
|
drift: ConstantDrift(GAMMA),
|
||||||
p_draw: P_DRAW,
|
p_draw: P_DRAW,
|
||||||
online: false,
|
online: false,
|
||||||
|
_time: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct History<D: Drift = ConstantDrift> {
|
pub struct History<T: Time = i64, D: Drift<T> = ConstantDrift> {
|
||||||
size: usize,
|
size: usize,
|
||||||
pub(crate) time_slices: Vec<TimeSlice>,
|
pub(crate) time_slices: Vec<TimeSlice<T>>,
|
||||||
agents: CompetitorStore<D>,
|
pub(crate) agents: CompetitorStore<T, D>,
|
||||||
time: bool,
|
time: bool,
|
||||||
mu: f64,
|
mu: f64,
|
||||||
sigma: f64,
|
sigma: f64,
|
||||||
@@ -116,7 +120,7 @@ pub struct History<D: Drift = ConstantDrift> {
|
|||||||
online: bool,
|
online: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for History<ConstantDrift> {
|
impl Default for History<i64, ConstantDrift> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
size: 0,
|
size: 0,
|
||||||
@@ -133,13 +137,13 @@ impl Default for History<ConstantDrift> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl History<ConstantDrift> {
|
impl History<i64, ConstantDrift> {
|
||||||
pub fn builder() -> HistoryBuilder<ConstantDrift> {
|
pub fn builder() -> HistoryBuilder<i64, ConstantDrift> {
|
||||||
HistoryBuilder::default()
|
HistoryBuilder::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Drift> History<D> {
|
impl<T: Time, D: Drift<T>> History<T, D> {
|
||||||
fn iteration(&mut self) -> (f64, f64) {
|
fn iteration(&mut self) -> (f64, f64) {
|
||||||
let mut step = (0.0, 0.0);
|
let mut step = (0.0, 0.0);
|
||||||
|
|
||||||
@@ -226,8 +230,8 @@ impl<D: Drift> History<D> {
|
|||||||
(step, i)
|
(step, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn learning_curves(&self) -> HashMap<Index, Vec<(i64, Gaussian)>> {
|
pub fn learning_curves(&self) -> HashMap<Index, Vec<(T, Gaussian)>> {
|
||||||
let mut data: HashMap<Index, Vec<(i64, Gaussian)>> = HashMap::new();
|
let mut data: HashMap<Index, Vec<(T, Gaussian)>> = HashMap::new();
|
||||||
|
|
||||||
for b in &self.time_slices {
|
for b in &self.time_slices {
|
||||||
for (agent, skill) in b.skills.iter() {
|
for (agent, skill) in b.skills.iter() {
|
||||||
@@ -250,7 +254,9 @@ impl<D: Drift> History<D> {
|
|||||||
.map(|ts| ts.log_evidence(self.online, targets, forward, &self.agents))
|
.map(|ts| ts.log_evidence(self.online, targets, forward, &self.agents))
|
||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Drift<i64>> History<i64, D> {
|
||||||
pub fn add_events(
|
pub fn add_events(
|
||||||
&mut self,
|
&mut self,
|
||||||
composition: Vec<Vec<Vec<Index>>>,
|
composition: Vec<Vec<Vec<Index>>>,
|
||||||
@@ -267,7 +273,7 @@ impl<D: Drift> History<D> {
|
|||||||
results: Vec<Vec<f64>>,
|
results: Vec<Vec<f64>>,
|
||||||
times: Vec<i64>,
|
times: Vec<i64>,
|
||||||
weights: Vec<Vec<Vec<f64>>>,
|
weights: Vec<Vec<Vec<f64>>>,
|
||||||
mut priors: HashMap<Index, Rating<D>>,
|
mut priors: HashMap<Index, Rating<i64, D>>,
|
||||||
) {
|
) {
|
||||||
assert!(times.is_empty() || self.time, "length(times)>0 but !h.time");
|
assert!(times.is_empty() || self.time, "length(times)>0 but !h.time");
|
||||||
assert!(
|
assert!(
|
||||||
@@ -310,7 +316,7 @@ impl<D: Drift> History<D> {
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
message: N_INF,
|
message: N_INF,
|
||||||
last_time: i64::MIN,
|
last_time: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -343,21 +349,30 @@ impl<D: Drift> History<D> {
|
|||||||
time_slice.new_forward_info(&self.agents);
|
time_slice.new_forward_info(&self.agents);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Is it faster to iterate over agents in batch instead?
|
|
||||||
for agent_idx in &this_agent {
|
for agent_idx in &this_agent {
|
||||||
if let Some(skill) = time_slice.skills.get_mut(*agent_idx) {
|
if let Some(skill) = time_slice.skills.get_mut(*agent_idx) {
|
||||||
skill.elapsed = time_slice::compute_elapsed(
|
skill.elapsed = time_slice::compute_elapsed(
|
||||||
self.agents[*agent_idx].last_time,
|
self.agents[*agent_idx].last_time.as_ref(),
|
||||||
time_slice.time,
|
&time_slice.time,
|
||||||
);
|
);
|
||||||
|
|
||||||
let agent = self.agents.get_mut(*agent_idx).unwrap();
|
let agent = self.agents.get_mut(*agent_idx).unwrap();
|
||||||
|
|
||||||
agent.last_time = if self.time { time_slice.time } else { i64::MAX };
|
agent.last_time = Some(time_slice.time);
|
||||||
agent.message = time_slice.forward_prior_out(agent_idx);
|
agent.message = time_slice.forward_prior_out(agent_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.time {
|
||||||
|
let slice_time = time_slice.time;
|
||||||
|
for agent_idx in &this_agent {
|
||||||
|
let c = self.agents.get_mut(*agent_idx).unwrap();
|
||||||
|
if c.last_time.is_some() {
|
||||||
|
c.last_time = Some(slice_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
k += 1;
|
k += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,11 +399,11 @@ impl<D: Drift> History<D> {
|
|||||||
for agent_idx in time_slice.skills.keys() {
|
for agent_idx in time_slice.skills.keys() {
|
||||||
let agent = self.agents.get_mut(agent_idx).unwrap();
|
let agent = self.agents.get_mut(agent_idx).unwrap();
|
||||||
|
|
||||||
agent.last_time = if self.time { t } else { i64::MAX };
|
agent.last_time = Some(t);
|
||||||
agent.message = time_slice.forward_prior_out(&agent_idx);
|
agent.message = time_slice.forward_prior_out(&agent_idx);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut time_slice: TimeSlice = TimeSlice::new(t, self.p_draw);
|
let mut time_slice = TimeSlice::new(t, self.p_draw);
|
||||||
time_slice.add_events(composition, results, weights, &self.agents);
|
time_slice.add_events(composition, results, weights, &self.agents);
|
||||||
|
|
||||||
self.time_slices.insert(k, time_slice);
|
self.time_slices.insert(k, time_slice);
|
||||||
@@ -398,10 +413,19 @@ impl<D: Drift> History<D> {
|
|||||||
for agent_idx in time_slice.skills.keys() {
|
for agent_idx in time_slice.skills.keys() {
|
||||||
let agent = self.agents.get_mut(agent_idx).unwrap();
|
let agent = self.agents.get_mut(agent_idx).unwrap();
|
||||||
|
|
||||||
agent.last_time = if self.time { t } else { i64::MAX };
|
agent.last_time = Some(t);
|
||||||
agent.message = time_slice.forward_prior_out(&agent_idx);
|
agent.message = time_slice.forward_prior_out(&agent_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.time {
|
||||||
|
for agent_idx in &this_agent {
|
||||||
|
let c = self.agents.get_mut(*agent_idx).unwrap();
|
||||||
|
if c.last_time.is_some() {
|
||||||
|
c.last_time = Some(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
k += 1;
|
k += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,17 +437,16 @@ impl<D: Drift> History<D> {
|
|||||||
|
|
||||||
time_slice.new_forward_info(&self.agents);
|
time_slice.new_forward_info(&self.agents);
|
||||||
|
|
||||||
// TODO: Is it faster to iterate over agents in batch instead?
|
|
||||||
for agent_idx in &this_agent {
|
for agent_idx in &this_agent {
|
||||||
if let Some(skill) = time_slice.skills.get_mut(*agent_idx) {
|
if let Some(skill) = time_slice.skills.get_mut(*agent_idx) {
|
||||||
skill.elapsed = time_slice::compute_elapsed(
|
skill.elapsed = time_slice::compute_elapsed(
|
||||||
self.agents[*agent_idx].last_time,
|
self.agents[*agent_idx].last_time.as_ref(),
|
||||||
time_slice.time,
|
&time_slice.time,
|
||||||
);
|
);
|
||||||
|
|
||||||
let agent = self.agents.get_mut(*agent_idx).unwrap();
|
let agent = self.agents.get_mut(*agent_idx).unwrap();
|
||||||
|
|
||||||
agent.last_time = if self.time { time_slice.time } else { i64::MAX };
|
agent.last_time = Some(time_slice.time);
|
||||||
agent.message = time_slice.forward_prior_out(agent_idx);
|
agent.message = time_slice.forward_prior_out(agent_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BETA, GAMMA,
|
BETA, GAMMA,
|
||||||
drift::{ConstantDrift, Drift},
|
drift::{ConstantDrift, Drift},
|
||||||
gaussian::Gaussian,
|
gaussian::Gaussian,
|
||||||
|
time::Time,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Static rating configuration: prior skill, performance noise `beta`, drift.
|
/// Static rating configuration: prior skill, performance noise `beta`, drift.
|
||||||
@@ -9,15 +12,21 @@ use crate::{
|
|||||||
/// Renamed from `Player` in T2; `Rating` better describes the data
|
/// Renamed from `Player` in T2; `Rating` better describes the data
|
||||||
/// (a configuration) vs. a person (who's a `Competitor` with state).
|
/// (a configuration) vs. a person (who's a `Competitor` with state).
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Rating<D: Drift = ConstantDrift> {
|
pub struct Rating<T: Time = i64, D: Drift<T> = ConstantDrift> {
|
||||||
pub(crate) prior: Gaussian,
|
pub(crate) prior: Gaussian,
|
||||||
pub(crate) beta: f64,
|
pub(crate) beta: f64,
|
||||||
pub(crate) drift: D,
|
pub(crate) drift: D,
|
||||||
|
pub(crate) _time: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Drift> Rating<D> {
|
impl<T: Time, D: Drift<T>> Rating<T, D> {
|
||||||
pub fn new(prior: Gaussian, beta: f64, drift: D) -> Self {
|
pub fn new(prior: Gaussian, beta: f64, drift: D) -> Self {
|
||||||
Self { prior, beta, drift }
|
Self {
|
||||||
|
prior,
|
||||||
|
beta,
|
||||||
|
drift,
|
||||||
|
_time: PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn performance(&self) -> Gaussian {
|
pub(crate) fn performance(&self) -> Gaussian {
|
||||||
@@ -25,12 +34,13 @@ impl<D: Drift> Rating<D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Rating<ConstantDrift> {
|
impl Default for Rating<i64, ConstantDrift> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
prior: Gaussian::default(),
|
prior: Gaussian::default(),
|
||||||
beta: BETA,
|
beta: BETA,
|
||||||
drift: ConstantDrift(GAMMA),
|
drift: ConstantDrift(GAMMA),
|
||||||
|
_time: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
use crate::{Index, competitor::Competitor, drift::Drift};
|
use crate::{Index, competitor::Competitor, drift::Drift, time::Time};
|
||||||
|
|
||||||
/// Dense Vec-backed store for competitor state in History.
|
/// Dense Vec-backed store for competitor state in History.
|
||||||
///
|
///
|
||||||
/// Indexed directly by Index.0, eliminating HashMap hashing in the
|
/// Indexed directly by Index.0, eliminating HashMap hashing in the
|
||||||
/// forward/backward sweep. Uses `Vec<Option<Competitor<D>>>` so slots can be
|
/// forward/backward sweep. Uses `Vec<Option<Competitor<T, D>>>` so slots can be
|
||||||
/// absent without an explicit present mask.
|
/// absent without an explicit present mask.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CompetitorStore<D: Drift> {
|
pub struct CompetitorStore<T: Time = i64, D: Drift<T> = crate::drift::ConstantDrift> {
|
||||||
competitors: Vec<Option<Competitor<D>>>,
|
competitors: Vec<Option<Competitor<T, D>>>,
|
||||||
n_present: usize,
|
n_present: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Drift> Default for CompetitorStore<D> {
|
impl<T: Time, D: Drift<T>> Default for CompetitorStore<T, D> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
competitors: Vec::new(),
|
competitors: Vec::new(),
|
||||||
@@ -20,7 +20,7 @@ impl<D: Drift> Default for CompetitorStore<D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Drift> CompetitorStore<D> {
|
impl<T: Time, D: Drift<T>> CompetitorStore<T, D> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ impl<D: Drift> CompetitorStore<D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, idx: Index, competitor: Competitor<D>) {
|
pub fn insert(&mut self, idx: Index, competitor: Competitor<T, D>) {
|
||||||
self.ensure_capacity(idx.0);
|
self.ensure_capacity(idx.0);
|
||||||
if self.competitors[idx.0].is_none() {
|
if self.competitors[idx.0].is_none() {
|
||||||
self.n_present += 1;
|
self.n_present += 1;
|
||||||
@@ -39,11 +39,11 @@ impl<D: Drift> CompetitorStore<D> {
|
|||||||
self.competitors[idx.0] = Some(competitor);
|
self.competitors[idx.0] = Some(competitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, idx: Index) -> Option<&Competitor<D>> {
|
pub fn get(&self, idx: Index) -> Option<&Competitor<T, D>> {
|
||||||
self.competitors.get(idx.0).and_then(|slot| slot.as_ref())
|
self.competitors.get(idx.0).and_then(|slot| slot.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, idx: Index) -> Option<&mut Competitor<D>> {
|
pub fn get_mut(&mut self, idx: Index) -> Option<&mut Competitor<T, D>> {
|
||||||
self.competitors
|
self.competitors
|
||||||
.get_mut(idx.0)
|
.get_mut(idx.0)
|
||||||
.and_then(|slot| slot.as_mut())
|
.and_then(|slot| slot.as_mut())
|
||||||
@@ -61,34 +61,34 @@ impl<D: Drift> CompetitorStore<D> {
|
|||||||
self.n_present == 0
|
self.n_present == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (Index, &Competitor<D>)> {
|
pub fn iter(&self) -> impl Iterator<Item = (Index, &Competitor<T, D>)> {
|
||||||
self.competitors
|
self.competitors
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(i, slot)| slot.as_ref().map(|a| (Index(i), a)))
|
.filter_map(|(i, slot)| slot.as_ref().map(|a| (Index(i), a)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Index, &mut Competitor<D>)> {
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Index, &mut Competitor<T, D>)> {
|
||||||
self.competitors
|
self.competitors
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(i, slot)| slot.as_mut().map(|a| (Index(i), a)))
|
.filter_map(|(i, slot)| slot.as_mut().map(|a| (Index(i), a)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Competitor<D>> {
|
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Competitor<T, D>> {
|
||||||
self.competitors.iter_mut().filter_map(|s| s.as_mut())
|
self.competitors.iter_mut().filter_map(|s| s.as_mut())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Drift> std::ops::Index<Index> for CompetitorStore<D> {
|
impl<T: Time, D: Drift<T>> std::ops::Index<Index> for CompetitorStore<T, D> {
|
||||||
type Output = Competitor<D>;
|
type Output = Competitor<T, D>;
|
||||||
fn index(&self, idx: Index) -> &Competitor<D> {
|
fn index(&self, idx: Index) -> &Competitor<T, D> {
|
||||||
self.get(idx).expect("competitor not found at index")
|
self.get(idx).expect("competitor not found at index")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Drift> std::ops::IndexMut<Index> for CompetitorStore<D> {
|
impl<T: Time, D: Drift<T>> std::ops::IndexMut<Index> for CompetitorStore<T, D> {
|
||||||
fn index_mut(&mut self, idx: Index) -> &mut Competitor<D> {
|
fn index_mut(&mut self, idx: Index) -> &mut Competitor<T, D> {
|
||||||
self.get_mut(idx).expect("competitor not found at index")
|
self.get_mut(idx).expect("competitor not found at index")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_then_get() {
|
fn insert_then_get() {
|
||||||
let mut store: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
let mut store: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
||||||
let idx = Index(7);
|
let idx = Index(7);
|
||||||
store.insert(idx, Competitor::default());
|
store.insert(idx, Competitor::default());
|
||||||
assert!(store.contains(idx));
|
assert!(store.contains(idx));
|
||||||
@@ -110,7 +110,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn iter_in_index_order() {
|
fn iter_in_index_order() {
|
||||||
let mut store: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
let mut store: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
||||||
store.insert(Index(2), Competitor::default());
|
store.insert(Index(2), Competitor::default());
|
||||||
store.insert(Index(0), Competitor::default());
|
store.insert(Index(0), Competitor::default());
|
||||||
store.insert(Index(5), Competitor::default());
|
store.insert(Index(5), Competitor::default());
|
||||||
@@ -120,7 +120,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn index_operator_works() {
|
fn index_operator_works() {
|
||||||
let mut store: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
let mut store: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
||||||
store.insert(Index(3), Competitor::default());
|
store.insert(Index(3), Competitor::default());
|
||||||
let _ = &store[Index(3)];
|
let _ = &store[Index(3)];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::{
|
|||||||
gaussian::Gaussian,
|
gaussian::Gaussian,
|
||||||
rating::Rating,
|
rating::Rating,
|
||||||
storage::{CompetitorStore, SkillStore},
|
storage::{CompetitorStore, SkillStore},
|
||||||
|
time::Time,
|
||||||
tuple_gt, tuple_max,
|
tuple_gt, tuple_max,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,13 +50,13 @@ struct Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
fn within_prior<D: Drift>(
|
fn within_prior<T: Time, D: Drift<T>>(
|
||||||
&self,
|
&self,
|
||||||
online: bool,
|
online: bool,
|
||||||
forward: bool,
|
forward: bool,
|
||||||
skills: &SkillStore,
|
skills: &SkillStore,
|
||||||
agents: &CompetitorStore<D>,
|
agents: &CompetitorStore<T, D>,
|
||||||
) -> Rating<D> {
|
) -> Rating<T, D> {
|
||||||
let r = &agents[self.agent].rating;
|
let r = &agents[self.agent].rating;
|
||||||
let skill = skills.get(self.agent).unwrap();
|
let skill = skills.get(self.agent).unwrap();
|
||||||
|
|
||||||
@@ -90,13 +91,13 @@ impl Event {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn within_priors<D: Drift>(
|
pub(crate) fn within_priors<T: Time, D: Drift<T>>(
|
||||||
&self,
|
&self,
|
||||||
online: bool,
|
online: bool,
|
||||||
forward: bool,
|
forward: bool,
|
||||||
skills: &SkillStore,
|
skills: &SkillStore,
|
||||||
agents: &CompetitorStore<D>,
|
agents: &CompetitorStore<T, D>,
|
||||||
) -> Vec<Vec<Rating<D>>> {
|
) -> Vec<Vec<Rating<T, D>>> {
|
||||||
self.teams
|
self.teams
|
||||||
.iter()
|
.iter()
|
||||||
.map(|team| {
|
.map(|team| {
|
||||||
@@ -110,16 +111,16 @@ impl Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TimeSlice {
|
pub struct TimeSlice<T: Time = i64> {
|
||||||
pub(crate) events: Vec<Event>,
|
pub(crate) events: Vec<Event>,
|
||||||
pub(crate) skills: SkillStore,
|
pub(crate) skills: SkillStore,
|
||||||
pub(crate) time: i64,
|
pub(crate) time: T,
|
||||||
p_draw: f64,
|
p_draw: f64,
|
||||||
arena: ScratchArena,
|
arena: ScratchArena,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TimeSlice {
|
impl<T: Time> TimeSlice<T> {
|
||||||
pub fn new(time: i64, p_draw: f64) -> Self {
|
pub fn new(time: T, p_draw: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
events: Vec::new(),
|
events: Vec::new(),
|
||||||
skills: SkillStore::new(),
|
skills: SkillStore::new(),
|
||||||
@@ -129,12 +130,12 @@ impl TimeSlice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_events<D: Drift>(
|
pub fn add_events<D: Drift<T>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
composition: Vec<Vec<Vec<Index>>>,
|
composition: Vec<Vec<Vec<Index>>>,
|
||||||
results: Vec<Vec<f64>>,
|
results: Vec<Vec<f64>>,
|
||||||
weights: Vec<Vec<Vec<f64>>>,
|
weights: Vec<Vec<Vec<f64>>>,
|
||||||
agents: &CompetitorStore<D>,
|
agents: &CompetitorStore<T, D>,
|
||||||
) {
|
) {
|
||||||
let mut unique = Vec::with_capacity(10);
|
let mut unique = Vec::with_capacity(10);
|
||||||
|
|
||||||
@@ -149,16 +150,16 @@ impl TimeSlice {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for idx in this_agent {
|
for idx in this_agent {
|
||||||
let elapsed = compute_elapsed(agents[*idx].last_time, self.time);
|
let elapsed = compute_elapsed(agents[*idx].last_time.as_ref(), &self.time);
|
||||||
|
|
||||||
if let Some(skill) = self.skills.get_mut(*idx) {
|
if let Some(skill) = self.skills.get_mut(*idx) {
|
||||||
skill.elapsed = elapsed;
|
skill.elapsed = elapsed;
|
||||||
skill.forward = agents[*idx].receive(elapsed);
|
skill.forward = agents[*idx].receive(&self.time);
|
||||||
} else {
|
} else {
|
||||||
self.skills.insert(
|
self.skills.insert(
|
||||||
*idx,
|
*idx,
|
||||||
Skill {
|
Skill {
|
||||||
forward: agents[*idx].receive(elapsed),
|
forward: agents[*idx].receive(&self.time),
|
||||||
elapsed,
|
elapsed,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -220,7 +221,7 @@ impl TimeSlice {
|
|||||||
.collect::<HashMap<_, _>>()
|
.collect::<HashMap<_, _>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iteration<D: Drift>(&mut self, from: usize, agents: &CompetitorStore<D>) {
|
pub fn iteration<D: Drift<T>>(&mut self, from: usize, agents: &CompetitorStore<T, D>) {
|
||||||
for event in self.events.iter_mut().skip(from) {
|
for event in self.events.iter_mut().skip(from) {
|
||||||
let teams = event.within_priors(false, false, &self.skills, agents);
|
let teams = event.within_priors(false, false, &self.skills, agents);
|
||||||
let result = event.outputs();
|
let result = event.outputs();
|
||||||
@@ -241,7 +242,7 @@ impl TimeSlice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn convergence<D: Drift>(&mut self, agents: &CompetitorStore<D>) -> usize {
|
pub(crate) fn convergence<D: Drift<T>>(&mut self, agents: &CompetitorStore<T, D>) -> usize {
|
||||||
let epsilon = 1e-6;
|
let epsilon = 1e-6;
|
||||||
let iterations = 20;
|
let iterations = 20;
|
||||||
|
|
||||||
@@ -270,36 +271,41 @@ impl TimeSlice {
|
|||||||
skill.forward * skill.likelihood
|
skill.forward * skill.likelihood
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn backward_prior_out<D: Drift>(
|
pub(crate) fn backward_prior_out<D: Drift<T>>(
|
||||||
&self,
|
&self,
|
||||||
agent: &Index,
|
agent: &Index,
|
||||||
agents: &CompetitorStore<D>,
|
agents: &CompetitorStore<T, D>,
|
||||||
) -> Gaussian {
|
) -> Gaussian {
|
||||||
let skill = self.skills.get(*agent).unwrap();
|
let skill = self.skills.get(*agent).unwrap();
|
||||||
let n = skill.likelihood * skill.backward;
|
let n = skill.likelihood * skill.backward;
|
||||||
n.forget(agents[*agent].rating.drift.variance_delta(skill.elapsed))
|
n.forget(
|
||||||
|
agents[*agent]
|
||||||
|
.rating
|
||||||
|
.drift
|
||||||
|
.variance_for_elapsed(skill.elapsed),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_backward_info<D: Drift>(&mut self, agents: &CompetitorStore<D>) {
|
pub(crate) fn new_backward_info<D: Drift<T>>(&mut self, agents: &CompetitorStore<T, D>) {
|
||||||
for (agent, skill) in self.skills.iter_mut() {
|
for (agent, skill) in self.skills.iter_mut() {
|
||||||
skill.backward = agents[agent].message;
|
skill.backward = agents[agent].message;
|
||||||
}
|
}
|
||||||
self.iteration(0, agents);
|
self.iteration(0, agents);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_forward_info<D: Drift>(&mut self, agents: &CompetitorStore<D>) {
|
pub(crate) fn new_forward_info<D: Drift<T>>(&mut self, agents: &CompetitorStore<T, D>) {
|
||||||
for (agent, skill) in self.skills.iter_mut() {
|
for (agent, skill) in self.skills.iter_mut() {
|
||||||
skill.forward = agents[agent].receive(skill.elapsed);
|
skill.forward = agents[agent].receive_for_elapsed(skill.elapsed);
|
||||||
}
|
}
|
||||||
self.iteration(0, agents);
|
self.iteration(0, agents);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn log_evidence<D: Drift>(
|
pub(crate) fn log_evidence<D: Drift<T>>(
|
||||||
&self,
|
&self,
|
||||||
online: bool,
|
online: bool,
|
||||||
targets: &[Index],
|
targets: &[Index],
|
||||||
forward: bool,
|
forward: bool,
|
||||||
agents: &CompetitorStore<D>,
|
agents: &CompetitorStore<T, D>,
|
||||||
) -> f64 {
|
) -> f64 {
|
||||||
// log_evidence is infrequent; a local arena avoids needing &mut self.
|
// log_evidence is infrequent; a local arena avoids needing &mut self.
|
||||||
let mut arena = ScratchArena::new();
|
let mut arena = ScratchArena::new();
|
||||||
@@ -388,14 +394,8 @@ impl TimeSlice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compute_elapsed(last_time: i64, actual_time: i64) -> i64 {
|
pub(crate) fn compute_elapsed<T: Time>(last: Option<&T>, current: &T) -> i64 {
|
||||||
if last_time == i64::MIN {
|
last.map(|l| l.elapsed_to(current).max(0)).unwrap_or(0)
|
||||||
0
|
|
||||||
} else if last_time == i64::MAX {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
actual_time - last_time
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -419,7 +419,7 @@ mod tests {
|
|||||||
let e = index_map.get_or_create("e");
|
let e = index_map.get_or_create("e");
|
||||||
let f = index_map.get_or_create("f");
|
let f = index_map.get_or_create("f");
|
||||||
|
|
||||||
let mut agents: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
let mut agents: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
||||||
|
|
||||||
for agent in [a, b, c, d, e, f] {
|
for agent in [a, b, c, d, e, f] {
|
||||||
agents.insert(
|
agents.insert(
|
||||||
@@ -435,7 +435,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut time_slice = TimeSlice::new(0, 0.0);
|
let mut time_slice = TimeSlice::new(0i64, 0.0);
|
||||||
|
|
||||||
time_slice.add_events(
|
time_slice.add_events(
|
||||||
vec![
|
vec![
|
||||||
@@ -495,7 +495,7 @@ mod tests {
|
|||||||
let e = index_map.get_or_create("e");
|
let e = index_map.get_or_create("e");
|
||||||
let f = index_map.get_or_create("f");
|
let f = index_map.get_or_create("f");
|
||||||
|
|
||||||
let mut agents: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
let mut agents: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
||||||
|
|
||||||
for agent in [a, b, c, d, e, f] {
|
for agent in [a, b, c, d, e, f] {
|
||||||
agents.insert(
|
agents.insert(
|
||||||
@@ -511,7 +511,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut time_slice = TimeSlice::new(0, 0.0);
|
let mut time_slice = TimeSlice::new(0i64, 0.0);
|
||||||
|
|
||||||
time_slice.add_events(
|
time_slice.add_events(
|
||||||
vec![
|
vec![
|
||||||
@@ -574,7 +574,7 @@ mod tests {
|
|||||||
let e = index_map.get_or_create("e");
|
let e = index_map.get_or_create("e");
|
||||||
let f = index_map.get_or_create("f");
|
let f = index_map.get_or_create("f");
|
||||||
|
|
||||||
let mut agents: CompetitorStore<ConstantDrift> = CompetitorStore::new();
|
let mut agents: CompetitorStore<i64, ConstantDrift> = CompetitorStore::new();
|
||||||
|
|
||||||
for agent in [a, b, c, d, e, f] {
|
for agent in [a, b, c, d, e, f] {
|
||||||
agents.insert(
|
agents.insert(
|
||||||
@@ -590,7 +590,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut time_slice = TimeSlice::new(0, 0.0);
|
let mut time_slice = TimeSlice::new(0i64, 0.0);
|
||||||
|
|
||||||
time_slice.add_events(
|
time_slice.add_events(
|
||||||
vec![
|
vec![
|
||||||
|
|||||||
Reference in New Issue
Block a user