T0 + T1 + T2: engine redesign through new API surface #1
132
src/event.rs
Normal file
132
src/event.rs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
//! Typed event description for bulk ingestion.
|
||||||
|
//!
|
||||||
|
//! `Event<T, K>` is the new public event shape (spec Section 4). Replaces
|
||||||
|
//! the nested `Vec<Vec<Vec<Index>>>`, `Vec<Vec<f64>>`, `Vec<Vec<Vec<f64>>>`
|
||||||
|
//! that the old `add_events_with_prior` took.
|
||||||
|
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use crate::{gaussian::Gaussian, outcome::Outcome, time::Time};
|
||||||
|
|
||||||
|
/// A single match at time `time` involving some number of teams.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Event<T: Time, K> {
|
||||||
|
pub time: T,
|
||||||
|
pub teams: SmallVec<[Team<K>; 4]>,
|
||||||
|
pub outcome: Outcome,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A team: list of members competing together.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Team<K> {
|
||||||
|
pub members: SmallVec<[Member<K>; 4]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K> Team<K> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
members: SmallVec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_members<I: IntoIterator<Item = Member<K>>>(members: I) -> Self {
|
||||||
|
Self {
|
||||||
|
members: members.into_iter().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K> Default for Team<K> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One member of a team, identified by user key `K`.
|
||||||
|
///
|
||||||
|
/// `weight` defaults to 1.0; a per-event `prior` can override the competitor's
|
||||||
|
/// current skill estimate for this event only.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Member<K> {
|
||||||
|
pub key: K,
|
||||||
|
pub weight: f64,
|
||||||
|
pub prior: Option<Gaussian>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K> Member<K> {
|
||||||
|
pub fn new(key: K) -> Self {
|
||||||
|
Self {
|
||||||
|
key,
|
||||||
|
weight: 1.0,
|
||||||
|
prior: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_weight(mut self, weight: f64) -> Self {
|
||||||
|
self.weight = weight;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_prior(mut self, prior: Gaussian) -> Self {
|
||||||
|
self.prior = Some(prior);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience: a member is a user key with default weight 1.0 and no prior.
|
||||||
|
impl<K> From<K> for Member<K> {
|
||||||
|
fn from(key: K) -> Self {
|
||||||
|
Self::new(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::Outcome;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn member_new_has_unit_weight_no_prior() {
|
||||||
|
let m = Member::new("alice");
|
||||||
|
assert_eq!(m.key, "alice");
|
||||||
|
assert_eq!(m.weight, 1.0);
|
||||||
|
assert!(m.prior.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn member_builder_methods_chain() {
|
||||||
|
let m = Member::new("alice")
|
||||||
|
.with_weight(0.5)
|
||||||
|
.with_prior(Gaussian::from_ms(20.0, 5.0));
|
||||||
|
assert_eq!(m.weight, 0.5);
|
||||||
|
assert!(m.prior.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn member_from_key() {
|
||||||
|
let m: Member<&str> = "bob".into();
|
||||||
|
assert_eq!(m.key, "bob");
|
||||||
|
assert_eq!(m.weight, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn team_with_members_collects() {
|
||||||
|
let t: Team<&str> = Team::with_members([Member::new("a"), Member::new("b")]);
|
||||||
|
assert_eq!(t.members.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn event_construction() {
|
||||||
|
use smallvec::smallvec;
|
||||||
|
let e: Event<i64, &str> = Event {
|
||||||
|
time: 1,
|
||||||
|
teams: smallvec![
|
||||||
|
Team::with_members([Member::new("a")]),
|
||||||
|
Team::with_members([Member::new("b")]),
|
||||||
|
],
|
||||||
|
outcome: Outcome::winner(0, 2),
|
||||||
|
};
|
||||||
|
assert_eq!(e.teams.len(), 2);
|
||||||
|
assert_eq!(e.time, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ pub use time_slice::TimeSlice;
|
|||||||
mod competitor;
|
mod competitor;
|
||||||
pub mod drift;
|
pub mod drift;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod event;
|
||||||
pub(crate) mod factor;
|
pub(crate) mod factor;
|
||||||
mod game;
|
mod game;
|
||||||
pub mod gaussian;
|
pub mod gaussian;
|
||||||
@@ -26,6 +27,7 @@ pub mod storage;
|
|||||||
pub use competitor::Competitor;
|
pub use competitor::Competitor;
|
||||||
pub use drift::{ConstantDrift, Drift};
|
pub use drift::{ConstantDrift, Drift};
|
||||||
pub use error::InferenceError;
|
pub use error::InferenceError;
|
||||||
|
pub use event::{Event, Member, Team};
|
||||||
pub use game::Game;
|
pub use game::Game;
|
||||||
pub use gaussian::Gaussian;
|
pub use gaussian::Gaussian;
|
||||||
pub use history::History;
|
pub use history::History;
|
||||||
|
|||||||
Reference in New Issue
Block a user