Merge api/param-reorder (#72)
This commit is contained in:
@@ -25,16 +25,14 @@
|
|||||||
|
|
||||||
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
|
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
use trueskill_tt::{
|
use trueskill_tt::{ConstantDrift, ConvergenceOptions, Event, History, Member, Outcome, Team};
|
||||||
ConstantDrift, ConvergenceOptions, Event, History, Member, NullObserver, Outcome, Team,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn build_history_1v1(
|
fn build_history_1v1(
|
||||||
n_events: usize,
|
n_events: usize,
|
||||||
n_competitors: usize,
|
n_competitors: usize,
|
||||||
events_per_slice: usize,
|
events_per_slice: usize,
|
||||||
seed: u64,
|
seed: u64,
|
||||||
) -> History<i64, ConstantDrift, NullObserver, String> {
|
) -> History<String> {
|
||||||
let mut rng = seed;
|
let mut rng = seed;
|
||||||
let mut next = || {
|
let mut next = || {
|
||||||
rng = rng
|
rng = rng
|
||||||
|
|||||||
+2
-4
@@ -32,8 +32,7 @@ fn bench_ingest(c: &mut Criterion) {
|
|||||||
b.iter_batched(
|
b.iter_batched(
|
||||||
|| events(n, 0),
|
|| events(n, 0),
|
||||||
|evs| {
|
|evs| {
|
||||||
let mut h: History<i64, _, _, String> =
|
let mut h: History<String> = History::builder().key_type::<String>().build();
|
||||||
History::builder().key_type::<String>().build();
|
|
||||||
for ev in evs {
|
for ev in evs {
|
||||||
h.add_events(std::iter::once(ev)).unwrap();
|
h.add_events(std::iter::once(ev)).unwrap();
|
||||||
}
|
}
|
||||||
@@ -47,8 +46,7 @@ fn bench_ingest(c: &mut Criterion) {
|
|||||||
b.iter_batched(
|
b.iter_batched(
|
||||||
|| events(n, 0),
|
|| events(n, 0),
|
||||||
|evs| {
|
|evs| {
|
||||||
let mut h: History<i64, _, _, String> =
|
let mut h: History<String> = History::builder().key_type::<String>().build();
|
||||||
History::builder().key_type::<String>().build();
|
|
||||||
h.add_events(evs).unwrap();
|
h.add_events(evs).unwrap();
|
||||||
black_box(h.time_slices_len())
|
black_box(h.time_slices_len())
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-2
@@ -10,8 +10,8 @@ use smallvec::smallvec;
|
|||||||
use trueskill_tt::{ConstantDrift, ConvergenceOptions, Event, History, Member, Outcome, Team};
|
use trueskill_tt::{ConstantDrift, ConvergenceOptions, Event, History, Member, Outcome, Team};
|
||||||
|
|
||||||
/// 30 slices of 8 duels: 480 appearances over 100 competitors.
|
/// 30 slices of 8 duels: 480 appearances over 100 competitors.
|
||||||
fn fitted() -> History<i64, ConstantDrift, trueskill_tt::NullObserver, String> {
|
fn fitted() -> History<String> {
|
||||||
let mut h: History<i64, ConstantDrift, _, String> = History::builder()
|
let mut h: History<String> = History::builder()
|
||||||
.key_type::<String>()
|
.key_type::<String>()
|
||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ use trueskill_tt::{ConstantDrift, Event, History, Member, Outcome, Team};
|
|||||||
fn bench_scored_history(c: &mut Criterion) {
|
fn bench_scored_history(c: &mut Criterion) {
|
||||||
c.bench_function("scored_history_60_events_30_iter", |bencher| {
|
c.bench_function("scored_history_60_events_30_iter", |bencher| {
|
||||||
bencher.iter(|| {
|
bencher.iter(|| {
|
||||||
let mut h: History<i64, ConstantDrift, _, String> = History::builder()
|
let mut h: History<String> = History::builder()
|
||||||
.key_type::<String>()
|
.key_type::<String>()
|
||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut hist: History<i64, _, _, String> = History::builder()
|
let mut hist: History<String> = History::builder()
|
||||||
.key_type::<String>()
|
.key_type::<String>()
|
||||||
.sigma(1.6)
|
.sigma(1.6)
|
||||||
.drift(ConstantDrift::new(0.036))
|
.drift(ConstantDrift::new(0.036))
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ where
|
|||||||
O: Observer<T>,
|
O: Observer<T>,
|
||||||
K: Eq + std::hash::Hash + Clone,
|
K: Eq + std::hash::Hash + Clone,
|
||||||
{
|
{
|
||||||
history: &'h mut History<T, D, O, K>,
|
history: &'h mut History<K, T, D, O>,
|
||||||
event: Event<T, K>,
|
event: Event<T, K>,
|
||||||
current_team_idx: Option<usize>,
|
current_team_idx: Option<usize>,
|
||||||
/// First validation failure seen while building, surfaced by `commit`.
|
/// First validation failure seen while building, surfaced by `commit`.
|
||||||
@@ -65,7 +65,7 @@ where
|
|||||||
O: Observer<T>,
|
O: Observer<T>,
|
||||||
K: Eq + std::hash::Hash + Clone,
|
K: Eq + std::hash::Hash + Clone,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(history: &'h mut History<T, D, O, K>, time: T) -> Self {
|
pub(crate) fn new(history: &'h mut History<K, T, D, O>, time: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
history,
|
history,
|
||||||
event: Event {
|
event: Event {
|
||||||
|
|||||||
+80
-60
@@ -32,6 +32,9 @@ use crate::{
|
|||||||
/// an unknown key. None of them can be changed after `build`, because they
|
/// an unknown key. None of them can be changed after `build`, because they
|
||||||
/// define the model the fit is of.
|
/// define the model the fit is of.
|
||||||
///
|
///
|
||||||
|
/// Parameterised as [`History`] is, `HistoryBuilder<K, T, D, O>`, with the
|
||||||
|
/// same defaults.
|
||||||
|
///
|
||||||
/// Two of the setters change the builder's *type* rather than a field —
|
/// Two of the setters change the builder's *type* rather than a field —
|
||||||
/// [`HistoryBuilder::drift`] and [`HistoryBuilder::observer`] — so bind the
|
/// [`HistoryBuilder::drift`] and [`HistoryBuilder::observer`] — so bind the
|
||||||
/// result rather than calling them on a `&mut`. [`HistoryBuilder::time_type`]
|
/// result rather than calling them on a `&mut`. [`HistoryBuilder::time_type`]
|
||||||
@@ -40,10 +43,10 @@ use crate::{
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[must_use = "a builder does nothing until `.build()`"]
|
#[must_use = "a builder does nothing until `.build()`"]
|
||||||
pub struct HistoryBuilder<
|
pub struct HistoryBuilder<
|
||||||
|
K: Eq + Hash + Clone = &'static str,
|
||||||
T: Time = i64,
|
T: Time = i64,
|
||||||
D: Drift<T> = ConstantDrift,
|
D: Drift<T> = ConstantDrift,
|
||||||
O: Observer<T> = NullObserver,
|
O: Observer<T> = NullObserver,
|
||||||
K: Eq + Hash + Clone = &'static str,
|
|
||||||
> {
|
> {
|
||||||
mu: f64,
|
mu: f64,
|
||||||
sigma: f64,
|
sigma: f64,
|
||||||
@@ -58,7 +61,7 @@ pub struct HistoryBuilder<
|
|||||||
_key: PhantomData<K>,
|
_key: PhantomData<K>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> HistoryBuilder<T, D, O, K> {
|
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> HistoryBuilder<K, T, D, O> {
|
||||||
/// Prior mean skill.
|
/// Prior mean skill.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
@@ -150,7 +153,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> HistoryBuilder<
|
|||||||
/// implementation. `converge` checks the variance each competitor actually
|
/// implementation. `converge` checks the variance each competitor actually
|
||||||
/// accumulates and reports `InvalidParameter` if it is negative or
|
/// accumulates and reports `InvalidParameter` if it is negative or
|
||||||
/// non-finite.
|
/// non-finite.
|
||||||
pub fn drift<D2: Drift<T>>(self, drift: D2) -> HistoryBuilder<T, D2, O, K> {
|
pub fn drift<D2: Drift<T>>(self, drift: D2) -> HistoryBuilder<K, T, D2, O> {
|
||||||
HistoryBuilder {
|
HistoryBuilder {
|
||||||
drift,
|
drift,
|
||||||
mu: self.mu,
|
mu: self.mu,
|
||||||
@@ -249,7 +252,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> HistoryBuilder<
|
|||||||
/// assert!(h.current_skill(&"alice").unwrap().mu() > 0.0);
|
/// assert!(h.current_skill(&"alice").unwrap().mu() > 0.0);
|
||||||
/// # Ok::<(), trueskill_tt::InferenceError>(())
|
/// # Ok::<(), trueskill_tt::InferenceError>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub fn time_type<T2>(self) -> HistoryBuilder<T2, D, O, K>
|
pub fn time_type<T2>(self) -> HistoryBuilder<K, T2, D, O>
|
||||||
where
|
where
|
||||||
T2: Time,
|
T2: Time,
|
||||||
D: Drift<T2>,
|
D: Drift<T2>,
|
||||||
@@ -283,7 +286,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> HistoryBuilder<
|
|||||||
/// h.record_winner(&"alice".to_string(), &"bob".to_string(), 1)?;
|
/// h.record_winner(&"alice".to_string(), &"bob".to_string(), 1)?;
|
||||||
/// # Ok::<(), trueskill_tt::InferenceError>(())
|
/// # Ok::<(), trueskill_tt::InferenceError>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub fn key_type<K2: Eq + Hash + Clone>(self) -> HistoryBuilder<T, D, O, K2> {
|
pub fn key_type<K2: Eq + Hash + Clone>(self) -> HistoryBuilder<K2, T, D, O> {
|
||||||
HistoryBuilder {
|
HistoryBuilder {
|
||||||
mu: self.mu,
|
mu: self.mu,
|
||||||
sigma: self.sigma,
|
sigma: self.sigma,
|
||||||
@@ -305,7 +308,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> HistoryBuilder<
|
|||||||
/// observer by value; to keep a handle on one that accumulates state, pass
|
/// observer by value; to keep a handle on one that accumulates state, pass
|
||||||
/// an `Arc` and keep a clone, or read it back with
|
/// an `Arc` and keep a clone, or read it back with
|
||||||
/// [`History::observer`] / [`History::into_observer`].
|
/// [`History::observer`] / [`History::into_observer`].
|
||||||
pub fn observer<O2: Observer<T>>(self, observer: O2) -> HistoryBuilder<T, D, O2, K> {
|
pub fn observer<O2: Observer<T>>(self, observer: O2) -> HistoryBuilder<K, T, D, O2> {
|
||||||
HistoryBuilder {
|
HistoryBuilder {
|
||||||
mu: self.mu,
|
mu: self.mu,
|
||||||
sigma: self.sigma,
|
sigma: self.sigma,
|
||||||
@@ -324,7 +327,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> HistoryBuilder<
|
|||||||
/// Finish configuring and produce an empty [`History`].
|
/// Finish configuring and produce an empty [`History`].
|
||||||
///
|
///
|
||||||
/// Every parameter was validated as it was set, so this cannot fail.
|
/// Every parameter was validated as it was set, so this cannot fail.
|
||||||
pub fn build(self) -> History<T, D, O, K> {
|
pub fn build(self) -> History<K, T, D, O> {
|
||||||
History {
|
History {
|
||||||
size: 0,
|
size: 0,
|
||||||
time_slices: Vec::new(),
|
time_slices: Vec::new(),
|
||||||
@@ -351,7 +354,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> HistoryBuilder<
|
|||||||
/// That, plus private fields and no `new`, meant a downstream crate could not
|
/// That, plus private fields and no `new`, meant a downstream crate could not
|
||||||
/// construct a `History` on any other time axis at all — `Untimed` and every
|
/// construct a `History` on any other time axis at all — `Untimed` and every
|
||||||
/// custom `Drift<T>` were public but unreachable.
|
/// custom `Drift<T>` were public but unreachable.
|
||||||
impl<T: Time, K: Eq + Hash + Clone> Default for HistoryBuilder<T, ConstantDrift, NullObserver, K> {
|
impl<T: Time, K: Eq + Hash + Clone> Default for HistoryBuilder<K, T, ConstantDrift, NullObserver> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
mu: MU,
|
mu: MU,
|
||||||
@@ -443,11 +446,25 @@ impl CompetitorConfig {
|
|||||||
///
|
///
|
||||||
/// `tests/reconvergence_equivalence.rs` pins the path-independence this rests
|
/// `tests/reconvergence_equivalence.rs` pins the path-independence this rests
|
||||||
/// on.
|
/// on.
|
||||||
|
/// The top-level container: ingests events, runs forward/backward message
|
||||||
|
/// passing, and answers queries about the fit.
|
||||||
|
///
|
||||||
|
/// # Type parameters
|
||||||
|
///
|
||||||
|
/// `History<K, T, D, O>` — key type, time type, drift model, observer — all
|
||||||
|
/// defaulted, so `History` alone means `&'static str` keys on an `i64` time
|
||||||
|
/// axis with [`ConstantDrift`] and no observer, and `History<String>` is the
|
||||||
|
/// whole spelling for owned keys.
|
||||||
|
///
|
||||||
|
/// `K` is first because it is the one people change. It used to be last, so
|
||||||
|
/// naming a history in a struct field meant writing all four:
|
||||||
|
/// `History<i64, ConstantDrift, NullObserver, String>` to say "keys are
|
||||||
|
/// `String`". See #72.
|
||||||
pub struct History<
|
pub struct History<
|
||||||
|
K: Eq + Hash + Clone = &'static str,
|
||||||
T: Time = i64,
|
T: Time = i64,
|
||||||
D: Drift<T> = ConstantDrift,
|
D: Drift<T> = ConstantDrift,
|
||||||
O: Observer<T> = NullObserver,
|
O: Observer<T> = NullObserver,
|
||||||
K: Eq + Hash + Clone = &'static str,
|
|
||||||
> {
|
> {
|
||||||
size: usize,
|
size: usize,
|
||||||
pub(crate) time_slices: Vec<TimeSlice<T>>,
|
pub(crate) time_slices: Vec<TimeSlice<T>>,
|
||||||
@@ -470,25 +487,25 @@ pub struct History<
|
|||||||
declared: HashMap<Index, CompetitorConfig>,
|
declared: HashMap<Index, CompetitorConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for History<i64, ConstantDrift, NullObserver, &'static str> {
|
impl Default for History {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
HistoryBuilder::default().build()
|
HistoryBuilder::default().build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl History<i64, ConstantDrift, NullObserver, &'static str> {
|
impl History {
|
||||||
/// Start configuring a history.
|
/// Start configuring a history.
|
||||||
///
|
///
|
||||||
/// The defaults are `i64` time, [`ConstantDrift`], no observer and
|
/// The defaults are `i64` time, [`ConstantDrift`], no observer and
|
||||||
/// `&'static str` keys. Any of the four can be changed — the two type
|
/// `&'static str` keys. Any of the four can be changed — the two type
|
||||||
/// parameters that no argument would pin are named with
|
/// parameters that no argument would pin are named with
|
||||||
/// [`HistoryBuilder::time_type`] and [`HistoryBuilder::key_type`].
|
/// [`HistoryBuilder::time_type`] and [`HistoryBuilder::key_type`].
|
||||||
pub fn builder() -> HistoryBuilder<i64, ConstantDrift, NullObserver, &'static str> {
|
pub fn builder() -> HistoryBuilder {
|
||||||
HistoryBuilder::default()
|
HistoryBuilder::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Time, K: Eq + Hash + Clone> HistoryBuilder<T, ConstantDrift, NullObserver, K> {
|
impl<T: Time, K: Eq + Hash + Clone> HistoryBuilder<K, T, ConstantDrift, NullObserver> {
|
||||||
/// A builder on any time axis and key type, with the default drift and no
|
/// A builder on any time axis and key type, with the default drift and no
|
||||||
/// observer.
|
/// observer.
|
||||||
///
|
///
|
||||||
@@ -499,7 +516,7 @@ impl<T: Time, K: Eq + Hash + Clone> HistoryBuilder<T, ConstantDrift, NullObserve
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use trueskill_tt::{History, HistoryBuilder, Untimed};
|
/// # use trueskill_tt::{History, HistoryBuilder, Untimed};
|
||||||
/// let mut h = HistoryBuilder::<Untimed, _, _, String>::new().build();
|
/// let mut h = HistoryBuilder::<String, Untimed>::new().build();
|
||||||
/// h.record_winner(&"alice".to_string(), &"bob".to_string(), Untimed)?;
|
/// h.record_winner(&"alice".to_string(), &"bob".to_string(), Untimed)?;
|
||||||
/// h.converge()?;
|
/// h.converge()?;
|
||||||
/// # Ok::<(), trueskill_tt::InferenceError>(())
|
/// # Ok::<(), trueskill_tt::InferenceError>(())
|
||||||
@@ -535,7 +552,7 @@ impl<T: Time, K: Eq + Hash + Clone> HistoryBuilder<T, ConstantDrift, NullObserve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O, K> {
|
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<K, T, D, O> {
|
||||||
/// Promote a key to its [`Index`], creating the entry if it is new.
|
/// Promote a key to its [`Index`], creating the entry if it is new.
|
||||||
///
|
///
|
||||||
/// Crate-internal since #73: interning reserves a storage slot and nothing
|
/// Crate-internal since #73: interning reserves a storage slot and nothing
|
||||||
@@ -550,7 +567,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O, K> {
|
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<K, T, D, O> {
|
||||||
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);
|
||||||
|
|
||||||
@@ -1577,7 +1594,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
|
|||||||
///
|
///
|
||||||
/// `JointUnavailable` if the history is empty, contains ranked events, or
|
/// `JointUnavailable` if the history is empty, contains ranked events, or
|
||||||
/// yields a precision matrix that is not positive-definite.
|
/// yields a precision matrix that is not positive-definite.
|
||||||
pub fn joint(&self) -> Result<Joint<'_, T, D, O, K>, InferenceError> {
|
pub fn joint(&self) -> Result<Joint<'_, K, T, D, O>, InferenceError> {
|
||||||
if self.time_slices.is_empty() {
|
if self.time_slices.is_empty() {
|
||||||
return Err(InferenceError::JointUnavailable {
|
return Err(InferenceError::JointUnavailable {
|
||||||
reason: "the history has no events",
|
reason: "the history has no events",
|
||||||
@@ -2048,7 +2065,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O, K> {
|
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<K, T, D, O> {
|
||||||
pub(crate) fn add_events_with_prior(
|
pub(crate) fn add_events_with_prior(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut composition: Vec<Vec<Vec<Index>>>,
|
mut composition: Vec<Vec<Vec<Index>>>,
|
||||||
@@ -2654,7 +2671,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
|
|||||||
/// It exists at all because without it a consumer cannot `#[derive(Debug)]` on
|
/// It exists at all because without it a consumer cannot `#[derive(Debug)]` on
|
||||||
/// any struct holding a `History`, which is how both known consumers store it.
|
/// any struct holding a `History`, which is how both known consumers store it.
|
||||||
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> std::fmt::Debug
|
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> std::fmt::Debug
|
||||||
for History<T, D, O, K>
|
for History<K, T, D, O>
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("History")
|
f.debug_struct("History")
|
||||||
@@ -2712,8 +2729,14 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> std::fmt::Debug
|
|||||||
/// so a competitor seen in the first and last of a hundred slices contributes
|
/// so a competitor seen in the first and last of a hundred slices contributes
|
||||||
/// two variables, not a hundred.
|
/// two variables, not a hundred.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct Joint<'h, T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> {
|
pub struct Joint<
|
||||||
history: &'h History<T, D, O, K>,
|
'h,
|
||||||
|
K: Eq + Hash + Clone = &'static str,
|
||||||
|
T: Time = i64,
|
||||||
|
D: Drift<T> = ConstantDrift,
|
||||||
|
O: Observer<T> = NullObserver,
|
||||||
|
> {
|
||||||
|
history: &'h History<K, T, D, O>,
|
||||||
cholesky: crate::joint::Cholesky,
|
cholesky: crate::joint::Cholesky,
|
||||||
/// `(row, slice)` of each competitor's latest appearance.
|
/// `(row, slice)` of each competitor's latest appearance.
|
||||||
latest: HashMap<Index, (usize, usize)>,
|
latest: HashMap<Index, (usize, usize)>,
|
||||||
@@ -2726,7 +2749,7 @@ pub struct Joint<'h, T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone>
|
|||||||
/// Deliberately does not print the factorisation, which is `n^2` floats and
|
/// Deliberately does not print the factorisation, which is `n^2` floats and
|
||||||
/// would make a `{:?}` of a large joint unreadable and slow.
|
/// would make a `{:?}` of a large joint unreadable and slow.
|
||||||
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> std::fmt::Debug
|
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> std::fmt::Debug
|
||||||
for Joint<'_, T, D, O, K>
|
for Joint<'_, K, T, D, O>
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Joint")
|
f.debug_struct("Joint")
|
||||||
@@ -2735,7 +2758,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> std::fmt::Debug
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> Joint<'_, T, D, O, K> {
|
impl<K: Eq + Hash + Clone, T: Time, D: Drift<T>, O: Observer<T>> Joint<'_, K, T, D, O> {
|
||||||
/// Number of variables in the joint: the history's appearances, after
|
/// Number of variables in the joint: the history's appearances, after
|
||||||
/// collapsing consecutive pairs a competitor does not drift between.
|
/// collapsing consecutive pairs a competitor does not drift between.
|
||||||
///
|
///
|
||||||
@@ -2933,8 +2956,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn per_slice_footprint_is_independent_of_index_magnitude() {
|
fn per_slice_footprint_is_independent_of_index_magnitude() {
|
||||||
fn total_skill_slots(high_indices: bool) -> usize {
|
fn total_skill_slots(high_indices: bool) -> usize {
|
||||||
let mut h: History<i64, ConstantDrift, NullObserver, String> =
|
let mut h: History<String> = History::builder().key_type::<String>().build();
|
||||||
History::builder().key_type::<String>().build();
|
|
||||||
|
|
||||||
for i in 0..2_000 {
|
for i in 0..2_000 {
|
||||||
h.intern(&format!("k{i:05}"));
|
h.intern(&format!("k{i:05}"));
|
||||||
@@ -3240,7 +3262,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_teams() {
|
fn test_teams() {
|
||||||
let mut h: History<i64, _, _, &'static str> = History::builder()
|
let mut h: History = History::builder()
|
||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
@@ -3346,7 +3368,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_events() {
|
fn test_add_events() {
|
||||||
let mut h: History<i64, _, _, &'static str> = History::builder()
|
let mut h: History = History::builder()
|
||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
@@ -3443,7 +3465,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_only_add_events() {
|
fn test_only_add_events() {
|
||||||
let mut h: History<i64, _, _, &'static str> = History::builder()
|
let mut h: History = History::builder()
|
||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
@@ -3542,7 +3564,7 @@ mod tests {
|
|||||||
fn test_log_evidence() {
|
fn test_log_evidence() {
|
||||||
use crate::ConvergenceOptions;
|
use crate::ConvergenceOptions;
|
||||||
|
|
||||||
let mut h: History<i64, _, _, &'static str> = History::builder().build();
|
let mut h: History = History::builder().build();
|
||||||
|
|
||||||
// empty results in the old API = team 0 wins; reproduce with Outcome::winner(0,2)
|
// empty results in the old API = team 0 wins; reproduce with Outcome::winner(0,2)
|
||||||
let events = make_events_1v1(
|
let events = make_events_1v1(
|
||||||
@@ -3598,7 +3620,7 @@ mod tests {
|
|||||||
epsilon = 1e-4
|
epsilon = 1e-4
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut h2: History<i64, _, _, &'static str> = History::builder().build();
|
let mut h2: History = History::builder().build();
|
||||||
|
|
||||||
let events = make_events_1v1(
|
let events = make_events_1v1(
|
||||||
&[("a", "b"), ("b", "a")],
|
&[("a", "b"), ("b", "a")],
|
||||||
@@ -3616,7 +3638,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_events_with_time() {
|
fn test_add_events_with_time() {
|
||||||
let mut h: History<i64, _, _, &'static str> = History::builder()
|
let mut h: History = History::builder()
|
||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
@@ -3719,7 +3741,7 @@ mod tests {
|
|||||||
|
|
||||||
// second scenario: team-0 wins (empty results in old API), different composition order
|
// second scenario: team-0 wins (empty results in old API), different composition order
|
||||||
|
|
||||||
let mut h2: History<i64, _, _, &'static str> = History::builder()
|
let mut h2: History = History::builder()
|
||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
@@ -3823,7 +3845,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_1vs1_weighted() {
|
fn test_1vs1_weighted() {
|
||||||
let mut h: History<i64, _, _, &'static str> = History::builder()
|
let mut h: History = History::builder()
|
||||||
.mu(2.0)
|
.mu(2.0)
|
||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
@@ -3890,7 +3912,7 @@ mod tests {
|
|||||||
fn test_converge_returns_report() {
|
fn test_converge_returns_report() {
|
||||||
use crate::ConvergenceOptions;
|
use crate::ConvergenceOptions;
|
||||||
|
|
||||||
let mut h: History<i64, _, _, &'static str> = History::builder()
|
let mut h: History = History::builder()
|
||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
@@ -3930,19 +3952,18 @@ mod tests {
|
|||||||
fn history_propagates_convergence_to_inner_run_chain() {
|
fn history_propagates_convergence_to_inner_run_chain() {
|
||||||
use crate::ConvergenceOptions;
|
use crate::ConvergenceOptions;
|
||||||
|
|
||||||
let events_for =
|
let events_for = |h: &mut History| {
|
||||||
|h: &mut History<i64, ConstantDrift, crate::observer::NullObserver, &'static str>| {
|
h.event(0)
|
||||||
h.event(0)
|
.team(["a"])
|
||||||
.team(["a"])
|
.team(["b"])
|
||||||
.team(["b"])
|
.team(["c"])
|
||||||
.team(["c"])
|
.team(["d"])
|
||||||
.team(["d"])
|
.ranking([0u32, 1, 2, 3])
|
||||||
.ranking([0u32, 1, 2, 3])
|
.commit()
|
||||||
.commit()
|
.unwrap();
|
||||||
.unwrap();
|
};
|
||||||
};
|
|
||||||
|
|
||||||
let mut h_capped: History<i64, _, _, &'static str> = History::builder()
|
let mut h_capped: History = History::builder()
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 1,
|
max_iter: 1,
|
||||||
..ConvergenceOptions::default()
|
..ConvergenceOptions::default()
|
||||||
@@ -3953,7 +3974,7 @@ mod tests {
|
|||||||
// result rather than an error.
|
// result rather than an error.
|
||||||
let _ = h_capped.converge_partial().unwrap();
|
let _ = h_capped.converge_partial().unwrap();
|
||||||
|
|
||||||
let mut h_full: History<i64, _, _, &'static str> = History::builder().build();
|
let mut h_full: History = History::builder().build();
|
||||||
events_for(&mut h_full);
|
events_for(&mut h_full);
|
||||||
let _ = h_full.converge().unwrap();
|
let _ = h_full.converge().unwrap();
|
||||||
|
|
||||||
@@ -3978,23 +3999,22 @@ mod tests {
|
|||||||
fn history_with_damping_reaches_same_fixed_point_as_undamped() {
|
fn history_with_damping_reaches_same_fixed_point_as_undamped() {
|
||||||
use crate::ConvergenceOptions;
|
use crate::ConvergenceOptions;
|
||||||
|
|
||||||
let events_for =
|
let events_for = |h: &mut History| {
|
||||||
|h: &mut History<i64, ConstantDrift, crate::observer::NullObserver, &'static str>| {
|
h.event(0)
|
||||||
h.event(0)
|
.team(["a"])
|
||||||
.team(["a"])
|
.team(["b"])
|
||||||
.team(["b"])
|
.team(["c"])
|
||||||
.team(["c"])
|
.team(["d"])
|
||||||
.team(["d"])
|
.ranking([0u32, 1, 2, 3])
|
||||||
.ranking([0u32, 1, 2, 3])
|
.commit()
|
||||||
.commit()
|
.unwrap();
|
||||||
.unwrap();
|
};
|
||||||
};
|
|
||||||
|
|
||||||
let mut h_undamped: History<i64, _, _, &'static str> = History::builder().build();
|
let mut h_undamped: History = History::builder().build();
|
||||||
events_for(&mut h_undamped);
|
events_for(&mut h_undamped);
|
||||||
let _ = h_undamped.converge().unwrap();
|
let _ = h_undamped.converge().unwrap();
|
||||||
|
|
||||||
let mut h_damped: History<i64, _, _, &'static str> = History::builder()
|
let mut h_damped: History = History::builder()
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
alpha: 0.5,
|
alpha: 0.5,
|
||||||
max_iter: 200,
|
max_iter: 200,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ fn additive_structure_makes_sums_wide_and_differences_tight() {
|
|||||||
let players = ["p0", "p1", "p2"];
|
let players = ["p0", "p1", "p2"];
|
||||||
let holes = ["h0", "h1"];
|
let holes = ["h0", "h1"];
|
||||||
|
|
||||||
let mut h: History<i64, _, _, &'static str> = History::builder()
|
let mut h: History = History::builder()
|
||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use trueskill_tt::{
|
|||||||
ConstantDrift, ConvergenceOptions, Event, History, InferenceError, Member, Outcome, Team,
|
ConstantDrift, ConvergenceOptions, Event, History, InferenceError, Member, Outcome, Team,
|
||||||
};
|
};
|
||||||
|
|
||||||
type H = History<i64, ConstantDrift, trueskill_tt::NullObserver, &'static str>;
|
type H = History;
|
||||||
|
|
||||||
fn duel(a: &'static str, b: &'static str, t: i64) -> Event<i64, &'static str> {
|
fn duel(a: &'static str, b: &'static str, t: i64) -> Event<i64, &'static str> {
|
||||||
Event {
|
Event {
|
||||||
@@ -108,7 +108,7 @@ fn the_two_agree_on_a_converged_fit() {
|
|||||||
/// At the old value of 30 this history stopped short and said nothing.
|
/// At the old value of 30 this history stopped short and said nothing.
|
||||||
#[test]
|
#[test]
|
||||||
fn the_default_cap_clears_an_ordinary_history() {
|
fn the_default_cap_clears_an_ordinary_history() {
|
||||||
let mut h: History<i64, ConstantDrift, _, String> = History::builder()
|
let mut h: History<String> = History::builder()
|
||||||
.key_type::<String>()
|
.key_type::<String>()
|
||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ use std::{env, process::Command};
|
|||||||
|
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
use trueskill_tt::{
|
use trueskill_tt::{
|
||||||
ConstantDrift, ConvergenceOptions, Event, History, Member, NullObserver, Outcome, Team,
|
ConstantDrift, ConvergenceOptions, Event, History, Member, Outcome, Team, UnknownKeys,
|
||||||
UnknownKeys,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Set in the child so it reports instead of re-spawning.
|
/// Set in the child so it reports instead of re-spawning.
|
||||||
@@ -24,7 +23,7 @@ const CHILD: &str = "TSTT_DETERMINISM_CHILD";
|
|||||||
|
|
||||||
const RUNS: usize = 40;
|
const RUNS: usize = 40;
|
||||||
|
|
||||||
type H = History<i64, ConstantDrift, NullObserver, String>;
|
type H = History<String>;
|
||||||
|
|
||||||
fn fitted() -> H {
|
fn fitted() -> H {
|
||||||
let mut h: H = History::builder()
|
let mut h: H = History::builder()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ mod common;
|
|||||||
use common::assert_finite;
|
use common::assert_finite;
|
||||||
use trueskill_tt::{
|
use trueskill_tt::{
|
||||||
ConstantDrift, ConvergenceOptions, Game, GameOptions, Gaussian, History, InferenceError,
|
ConstantDrift, ConvergenceOptions, Game, GameOptions, Gaussian, History, InferenceError,
|
||||||
NullObserver, Outcome, Rating,
|
Outcome, Rating,
|
||||||
};
|
};
|
||||||
|
|
||||||
type R = Rating<i64, ConstantDrift>;
|
type R = Rating<i64, ConstantDrift>;
|
||||||
@@ -126,7 +126,7 @@ fn empty_history_converges_trivially() {
|
|||||||
/// indexed out of bounds in release, so this must run in both profiles.
|
/// indexed out of bounds in release, so this must run in both profiles.
|
||||||
#[test]
|
#[test]
|
||||||
fn converge_on_an_empty_history_with_owned_keys() {
|
fn converge_on_an_empty_history_with_owned_keys() {
|
||||||
let mut history: History<i64, ConstantDrift, NullObserver, String> = History::builder()
|
let mut history: History<String> = History::builder()
|
||||||
.key_type::<String>()
|
.key_type::<String>()
|
||||||
.score_sigma(5.0)
|
.score_sigma(5.0)
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
|
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
use trueskill_tt::{
|
use trueskill_tt::{
|
||||||
ConstantDrift, ConvergenceOptions, Event, Gaussian, History, InferenceError, Member,
|
ConstantDrift, ConvergenceOptions, Event, Gaussian, History, InferenceError, Member, Outcome,
|
||||||
NullObserver, Outcome, Team,
|
Team,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Fit = History<i64, ConstantDrift, NullObserver, &'static str>;
|
type Fit = History;
|
||||||
|
|
||||||
const CONVERGENCE: ConvergenceOptions = ConvergenceOptions {
|
const CONVERGENCE: ConvergenceOptions = ConvergenceOptions {
|
||||||
max_iter: 64,
|
max_iter: 64,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use trueskill_tt::{
|
|||||||
Team,
|
Team,
|
||||||
};
|
};
|
||||||
|
|
||||||
type H = History<i64, ConstantDrift, trueskill_tt::NullObserver, &'static str>;
|
type H = History;
|
||||||
|
|
||||||
fn history() -> H {
|
fn history() -> H {
|
||||||
History::builder()
|
History::builder()
|
||||||
|
|||||||
@@ -4,11 +4,9 @@
|
|||||||
//! `filtered_log_evidence_for` was the missing corner: the one a per-competitor
|
//! `filtered_log_evidence_for` was the missing corner: the one a per-competitor
|
||||||
//! prequential score needs.
|
//! prequential score needs.
|
||||||
|
|
||||||
use trueskill_tt::{
|
use trueskill_tt::{Event, History, InferenceError, Member, Outcome, Team};
|
||||||
ConstantDrift, Event, History, InferenceError, Member, NullObserver, Outcome, Team,
|
|
||||||
};
|
|
||||||
|
|
||||||
type H = History<i64, ConstantDrift, NullObserver, &'static str>;
|
type H = History;
|
||||||
|
|
||||||
/// Two disjoint cohorts, so a key restriction is guaranteed to leave events out.
|
/// Two disjoint cohorts, so a key restriction is guaranteed to leave events out.
|
||||||
fn two_cohorts() -> H {
|
fn two_cohorts() -> H {
|
||||||
|
|||||||
@@ -4,11 +4,9 @@
|
|||||||
//! Each test carries a control: the same call on a key the history *does* know,
|
//! Each test carries a control: the same call on a key the history *does* know,
|
||||||
//! so it cannot pass merely because everything returns the same thing.
|
//! so it cannot pass merely because everything returns the same thing.
|
||||||
|
|
||||||
use trueskill_tt::{
|
use trueskill_tt::{Event, History, InferenceError, Member, Outcome, Team};
|
||||||
ConstantDrift, Event, History, InferenceError, Member, NullObserver, Outcome, Team,
|
|
||||||
};
|
|
||||||
|
|
||||||
type H = History<i64, ConstantDrift, NullObserver, &'static str>;
|
type H = History;
|
||||||
|
|
||||||
fn history() -> H {
|
fn history() -> H {
|
||||||
let mut h = H::default();
|
let mut h = H::default();
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ fn configured_event(a: &str, b: &str, time: i64, scale: f64) -> Event<i64, Strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn converged_skills(events: Vec<Event<i64, String>>, batched: bool) -> Vec<(String, Gaussian)> {
|
fn converged_skills(events: Vec<Event<i64, String>>, batched: bool) -> Vec<(String, Gaussian)> {
|
||||||
let mut h: History<i64, _, _, String> = History::builder()
|
let mut h: History<String> = History::builder()
|
||||||
.key_type::<String>()
|
.key_type::<String>()
|
||||||
.convergence(tight())
|
.convergence(tight())
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ use trueskill_tt::{Event, History, InferenceError, Member, Outcome, Team};
|
|||||||
|
|
||||||
type Ev = Event<i64, &'static str>;
|
type Ev = Event<i64, &'static str>;
|
||||||
|
|
||||||
fn history() -> History<i64, trueskill_tt::ConstantDrift, trueskill_tt::NullObserver, &'static str>
|
fn history() -> History {
|
||||||
{
|
|
||||||
History::builder().score_sigma(1.0).build()
|
History::builder().score_sigma(1.0).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use trueskill_tt::{
|
|||||||
UnknownKeys,
|
UnknownKeys,
|
||||||
};
|
};
|
||||||
|
|
||||||
type H = History<i64, ConstantDrift, trueskill_tt::NullObserver, &'static str>;
|
type H = History;
|
||||||
|
|
||||||
fn duel(a: &'static str, b: &'static str, t: i64, sa: f64, sb: f64) -> Event<i64, &'static str> {
|
fn duel(a: &'static str, b: &'static str, t: i64, sa: f64, sb: f64) -> Event<i64, &'static str> {
|
||||||
Event {
|
Event {
|
||||||
@@ -288,7 +288,7 @@ fn unseen_competitors_match_a_fresh_factorisation() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_drift_too_small_to_represent_collapses_rather_than_corrupting() {
|
fn a_drift_too_small_to_represent_collapses_rather_than_corrupting() {
|
||||||
fn variance(scale: f64) -> f64 {
|
fn variance(scale: f64) -> f64 {
|
||||||
let mut h: History<i64, ConstantDrift, _, String> = History::builder()
|
let mut h: History<String> = History::builder()
|
||||||
.key_type::<String>()
|
.key_type::<String>()
|
||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
//! Both key types are exercised in every test, because the point is that the
|
//! Both key types are exercised in every test, because the point is that the
|
||||||
//! spelling is the same.
|
//! spelling is the same.
|
||||||
|
|
||||||
use trueskill_tt::{ConstantDrift, History, NullObserver};
|
use trueskill_tt::{ConstantDrift, History};
|
||||||
|
|
||||||
type Owned = History<i64, ConstantDrift, NullObserver, String>;
|
type Owned = History<String>;
|
||||||
type Borrowed = History<i64, ConstantDrift, NullObserver, &'static str>;
|
type Borrowed = History;
|
||||||
|
|
||||||
fn owned() -> Owned {
|
fn owned() -> Owned {
|
||||||
let mut h: Owned = History::builder().key_type::<String>().build();
|
let mut h: Owned = History::builder().key_type::<String>().build();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//! produced a tiny-negative precision whose `sigma() = 1/sqrt(pi)` was NaN, which the
|
//! produced a tiny-negative precision whose `sigma() = 1/sqrt(pi)` was NaN, which the
|
||||||
//! moment-space `Sub` in the game chain propagated into every skill once the slice grew past
|
//! moment-space `Sub` in the game chain propagated into every skill once the slice grew past
|
||||||
//! ~75 competitors (e.g. a real ranking dataset with hundreds of players).
|
//! ~75 competitors (e.g. a real ranking dataset with hundreds of players).
|
||||||
use trueskill_tt::{ConstantDrift, ConvergenceOptions, EPSILON, History, ITERATIONS, NullObserver};
|
use trueskill_tt::{ConstantDrift, ConvergenceOptions, EPSILON, History, ITERATIONS};
|
||||||
|
|
||||||
/// Tiny deterministic LCG — avoids a dev-dependency on `rand`.
|
/// Tiny deterministic LCG — avoids a dev-dependency on `rand`.
|
||||||
struct Lcg(u64);
|
struct Lcg(u64);
|
||||||
@@ -24,7 +24,7 @@ impl Lcg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn nan_after_fit(players: usize) -> usize {
|
fn nan_after_fit(players: usize) -> usize {
|
||||||
let mut h: History<i64, ConstantDrift, NullObserver, String> = History::builder()
|
let mut h: History<String> = History::builder()
|
||||||
.key_type::<String>()
|
.key_type::<String>()
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
|
|||||||
@@ -132,10 +132,8 @@ fn key(i: usize) -> &'static str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns (worst mean error, worst sd ratio).
|
/// Returns (worst mean error, worst sd ratio).
|
||||||
fn fitted(
|
fn fitted(obs: &[(usize, usize, f64)]) -> History {
|
||||||
obs: &[(usize, usize, f64)],
|
let mut h: History = History::builder()
|
||||||
) -> History<i64, ConstantDrift, trueskill_tt::NullObserver, &'static str> {
|
|
||||||
let mut h: History<i64, _, _, &'static str> = History::builder()
|
|
||||||
.mu(MU0)
|
.mu(MU0)
|
||||||
.sigma(SIGMA0)
|
.sigma(SIGMA0)
|
||||||
.beta(BETA)
|
.beta(BETA)
|
||||||
@@ -324,7 +322,7 @@ fn cost_scaling() {
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
for n in [50usize, 100, 200, 400, 800] {
|
for n in [50usize, 100, 200, 400, 800] {
|
||||||
let names: Vec<String> = (0..n).map(|i| format!("c{i}")).collect();
|
let names: Vec<String> = (0..n).map(|i| format!("c{i}")).collect();
|
||||||
let mut h: History<i64, _, _, String> = History::builder()
|
let mut h: History<String> = History::builder()
|
||||||
.key_type::<String>()
|
.key_type::<String>()
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift::new(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ use trueskill_tt::{
|
|||||||
UnknownKeys,
|
UnknownKeys,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn builder(
|
fn builder(policy: UnknownKeys) -> History {
|
||||||
policy: UnknownKeys,
|
|
||||||
) -> History<i64, ConstantDrift, trueskill_tt::NullObserver, &'static str> {
|
|
||||||
History::builder()
|
History::builder()
|
||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
@@ -37,9 +35,7 @@ fn round(a: &'static str, b: &'static str, sa: f64, sb: f64) -> Event<i64, &'sta
|
|||||||
|
|
||||||
/// A history where "veteran" and "regular" are well observed and "novice"
|
/// A history where "veteran" and "regular" are well observed and "novice"
|
||||||
/// appears once.
|
/// appears once.
|
||||||
fn fitted(
|
fn fitted(policy: UnknownKeys) -> History {
|
||||||
policy: UnknownKeys,
|
|
||||||
) -> History<i64, ConstantDrift, trueskill_tt::NullObserver, &'static str> {
|
|
||||||
let mut h = builder(policy);
|
let mut h = builder(policy);
|
||||||
let mut events: Vec<_> = (0..40)
|
let mut events: Vec<_> = (0..40)
|
||||||
.map(|t| round("veteran", "regular", 10.0 + f64::from(t % 3), 5.0))
|
.map(|t| round("veteran", "regular", 10.0 + f64::from(t % 3), 5.0))
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
//! returning `Err`.
|
//! returning `Err`.
|
||||||
|
|
||||||
use trueskill_tt::{
|
use trueskill_tt::{
|
||||||
ConstantDrift, Event, Gaussian, History, InferenceError, Member, NullObserver, Outcome, Team,
|
ConstantDrift, Event, Gaussian, History, InferenceError, Member, Outcome, Team,
|
||||||
};
|
};
|
||||||
|
|
||||||
type H = History<i64, ConstantDrift, NullObserver, &'static str>;
|
type H = History;
|
||||||
|
|
||||||
fn build(beta: f64, prior: Option<Gaussian>, outcome: Outcome) -> H {
|
fn build(beta: f64, prior: Option<Gaussian>, outcome: Outcome) -> H {
|
||||||
let mut h: H = History::builder()
|
let mut h: H = History::builder()
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ fn ev(a: &str, b: &str, time: i64) -> Event<i64, String> {
|
|||||||
|
|
||||||
/// Ingest each chunk in turn, converging fully after every one.
|
/// Ingest each chunk in turn, converging fully after every one.
|
||||||
fn fit_in_chunks(chunks: Vec<Events>) -> Vec<(String, Gaussian)> {
|
fn fit_in_chunks(chunks: Vec<Events>) -> Vec<(String, Gaussian)> {
|
||||||
let mut h: History<i64, _, _, String> = History::builder()
|
let mut h: History<String> = History::builder()
|
||||||
.key_type::<String>()
|
.key_type::<String>()
|
||||||
.convergence(tight())
|
.convergence(tight())
|
||||||
.build();
|
.build();
|
||||||
@@ -152,7 +152,7 @@ fn re_converging_an_unchanged_history_costs_one_iteration() {
|
|||||||
let (early, late) = fixture();
|
let (early, late) = fixture();
|
||||||
let all: Vec<_> = early.into_iter().chain(late).collect();
|
let all: Vec<_> = early.into_iter().chain(late).collect();
|
||||||
|
|
||||||
let mut h: History<i64, _, _, String> = History::builder()
|
let mut h: History<String> = History::builder()
|
||||||
.key_type::<String>()
|
.key_type::<String>()
|
||||||
.convergence(tight())
|
.convergence(tight())
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use trueskill_tt::{
|
|||||||
Team,
|
Team,
|
||||||
};
|
};
|
||||||
|
|
||||||
type H = History<i64, ConstantDrift, trueskill_tt::NullObserver, &'static str>;
|
type H = History;
|
||||||
|
|
||||||
const PINNED: Gaussian = Gaussian::from_ms(2.0, 0.5);
|
const PINNED: Gaussian = Gaussian::from_ms(2.0, 0.5);
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -144,7 +144,7 @@ fn key_type_replaces_builder_with_key() {
|
|||||||
/// Both axes at once, via the explicit constructor rather than the setters.
|
/// Both axes at once, via the explicit constructor rather than the setters.
|
||||||
#[test]
|
#[test]
|
||||||
fn new_constructs_on_any_axis_directly() {
|
fn new_constructs_on_any_axis_directly() {
|
||||||
let mut h = HistoryBuilder::<Season, _, _, String>::new().build();
|
let mut h = HistoryBuilder::<String, Season>::new().build();
|
||||||
h.record_winner(&"a".to_string(), &"b".to_string(), Season(7))
|
h.record_winner(&"a".to_string(), &"b".to_string(), Season(7))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(h.converge().unwrap().converged);
|
assert!(h.converge().unwrap().converged);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const BETA: f64 = 1.0;
|
|||||||
const SCORE_SIGMA: f64 = 2.0;
|
const SCORE_SIGMA: f64 = 2.0;
|
||||||
const GAMMA: f64 = 0.5;
|
const GAMMA: f64 = 0.5;
|
||||||
|
|
||||||
type H = History<i64, ConstantDrift, trueskill_tt::NullObserver, &'static str>;
|
type H = History;
|
||||||
|
|
||||||
fn history(gamma: f64) -> H {
|
fn history(gamma: f64) -> H {
|
||||||
History::builder()
|
History::builder()
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ fn a_struct_holding_a_history_can_derive_debug() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn history_builder_is_debug_and_clone() {
|
fn history_builder_is_debug_and_clone() {
|
||||||
let b: HistoryBuilder<i64, ConstantDrift, _, &'static str> = History::builder();
|
let b: HistoryBuilder = History::builder();
|
||||||
let cloned = b.clone();
|
let cloned = b.clone();
|
||||||
assert!(!format!("{cloned:?}").is_empty());
|
assert!(!format!("{cloned:?}").is_empty());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use trueskill_tt::{
|
|||||||
UnknownKeys,
|
UnknownKeys,
|
||||||
};
|
};
|
||||||
|
|
||||||
type H = History<i64, ConstantDrift, trueskill_tt::NullObserver, &'static str>;
|
type H = History;
|
||||||
|
|
||||||
fn round(a: &'static str, b: &'static str, sa: f64, sb: f64) -> Event<i64, &'static str> {
|
fn round(a: &'static str, b: &'static str, sa: f64, sb: f64) -> Event<i64, &'static str> {
|
||||||
Event {
|
Event {
|
||||||
@@ -30,7 +30,7 @@ fn base() -> Vec<Event<i64, &'static str>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fit(extra: Option<Event<i64, &'static str>>, policy: UnknownKeys) -> H {
|
fn fit(extra: Option<Event<i64, &'static str>>, policy: UnknownKeys) -> H {
|
||||||
let mut h: History<i64, _, _, &'static str> = History::builder()
|
let mut h: History = History::builder()
|
||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
|
|||||||
Reference in New Issue
Block a user