fix!: seal ConstantDrift's field so gamma can be validated
`gamma` enters only as `gamma * gamma`, so the sign was squared away: measured against the old public-field form, `ConstantDrift(-0.0833)` produced results bit identical to `ConstantDrift(0.0833)`. The sign was neither rejected nor honoured — it vanished. It could not be checked while the field was a public tuple position, because there was nothing to intercept. Validating inside `variance_for_elapsed` would have been worse: it runs in the sweep, so a construction-time mistake would panic mid-inference, and `Gaussian::from_ms` is a worked example of why that is the wrong place — rejecting NaN there turned the NonFiniteResult reporting path into a crash. So `ConstantDrift::new` is the only way in and it checks, with `gamma()` to read the value back. 129 call sites rewritten across src, tests, benches, examples and the README. The dated plan and spec documents under docs/superpowers are left alone: they record what was built at the time, and rewriting them would falsify that. tests/constructor_validation.rs is the more valuable half. This defect class was closed three times in one session and reopened twice, because each fix validated the layer it had just touched and inferred the rest — `HistoryBuilder`, then `Game`'s own entry points, then the constructors beneath both. A per-site fix cannot notice the site nobody thought of, so that file enumerates every public entry point taking a magnitude and asserts each refuses negative and non-finite values. It found an eleventh defect on its first run: `HistoryBuilder::score_sigma` accepted infinity, because `inf > 0.0` is true and the assert only tested positivity. Fixed, and its own `should_panic` message updated to match. `Gaussian::from_ms` is deliberately exempt from the non-finite half, for the reason above: a broken fit produces a NaN sigma legitimately and `converge` must be allowed to report it. The convergence-level drift-variance check stays and is now tested through a custom `Drift` implementation, since `ConstantDrift` can no longer reach it. That check is the only thing standing between a third-party `Drift` and a NaN fit. BREAKING CHANGE: `ConstantDrift`'s field is private. Replace `ConstantDrift(x)` with `ConstantDrift::new(x)`, and `drift().0` with `drift().gamma()`. `HistoryBuilder::score_sigma` now rejects infinity. Closes #65 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
This commit is contained in:
@@ -45,7 +45,7 @@ grows proportionally to time:
|
|||||||
variance_delta = elapsed * γ²
|
variance_delta = elapsed * γ²
|
||||||
```
|
```
|
||||||
|
|
||||||
This is the standard TrueSkill Through Time model. Pass a `ConstantDrift(gamma)`
|
This is the standard TrueSkill Through Time model. Pass a `ConstantDrift::new(gamma)`
|
||||||
when constructing a `Rating`:
|
when constructing a `Rating`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -53,9 +53,9 @@ use trueskill_tt::{ConstantDrift, Gaussian, Rating};
|
|||||||
|
|
||||||
// gamma = 0.1 means skill can shift ~0.1 per time unit.
|
// gamma = 0.1 means skill can shift ~0.1 per time unit.
|
||||||
let rating: Rating<i64, ConstantDrift> =
|
let rating: Rating<i64, ConstantDrift> =
|
||||||
Rating::new(Gaussian::from_ms(0.0, 6.0), 1.0, ConstantDrift(0.1));
|
Rating::new(Gaussian::from_ms(0.0, 6.0), 1.0, ConstantDrift::new(0.1));
|
||||||
|
|
||||||
assert_eq!(rating.drift().0, 0.1);
|
assert_eq!(rating.drift().gamma(), 0.1);
|
||||||
```
|
```
|
||||||
|
|
||||||
The type annotation is load-bearing: `ConstantDrift` implements `Drift<T>` for
|
The type annotation is load-bearing: `ConstantDrift` implements `Drift<T>` for
|
||||||
@@ -98,14 +98,14 @@ assert_eq!(history.log_evidence(), 0.0);
|
|||||||
```
|
```
|
||||||
|
|
||||||
`HistoryBuilder::drift` is the only way to set a history's drift model; there is
|
`HistoryBuilder::drift` is the only way to set a history's drift model; there is
|
||||||
no `gamma()` shorthand. The default is `ConstantDrift(GAMMA)`.
|
no `gamma()` shorthand. The default is `ConstantDrift::new(GAMMA)`.
|
||||||
|
|
||||||
### Per-competitor drift
|
### Per-competitor drift
|
||||||
|
|
||||||
A `History` has one drift model, but individual competitors can scale it.
|
A `History` has one drift model, but individual competitors can scale it.
|
||||||
`Member::with_drift_scale(s)` multiplies the drift *variance* that competitor
|
`Member::with_drift_scale(s)` multiplies the drift *variance* that competitor
|
||||||
accumulates, so `s` is in the same units as `gamma`: `ConstantDrift(g)` at
|
accumulates, so `s` is in the same units as `gamma`: `ConstantDrift::new(g)` at
|
||||||
scale `s` behaves exactly as `ConstantDrift(g * s)` would, for that competitor
|
scale `s` behaves exactly as `ConstantDrift::new(g * s)` would, for that competitor
|
||||||
alone.
|
alone.
|
||||||
|
|
||||||
`0.0` pins a competitor still. That is what makes a **fixed reference point**
|
`0.0` pins a competitor still. That is what makes a **fixed reference point**
|
||||||
@@ -115,7 +115,7 @@ strength, a rating floor, a course difficulty:
|
|||||||
```rust
|
```rust
|
||||||
use trueskill_tt::{ConstantDrift, Event, History, Member, Outcome, Team};
|
use trueskill_tt::{ConstantDrift, Event, History, Member, Outcome, Team};
|
||||||
|
|
||||||
let mut h = History::builder().drift(ConstantDrift(0.1)).build();
|
let mut h = History::builder().drift(ConstantDrift::new(0.1)).build();
|
||||||
|
|
||||||
h.add_events(vec![Event {
|
h.add_events(vec![Event {
|
||||||
time: 0,
|
time: 0,
|
||||||
|
|||||||
+5
-1
@@ -17,7 +17,11 @@ fn criterion_benchmark(criterion: &mut Criterion) {
|
|||||||
agents.insert(
|
agents.insert(
|
||||||
agent,
|
agent,
|
||||||
Competitor {
|
Competitor {
|
||||||
rating: Rating::new(Gaussian::from_ms(MU, SIGMA), BETA, ConstantDrift(GAMMA)),
|
rating: Rating::new(
|
||||||
|
Gaussian::from_ms(MU, SIGMA),
|
||||||
|
BETA,
|
||||||
|
ConstantDrift::new(GAMMA),
|
||||||
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ fn build_history_1v1(
|
|||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.drift(ConstantDrift(25.0 / 300.0))
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 30,
|
max_iter: 30,
|
||||||
epsilon: 1e-6,
|
epsilon: 1e-6,
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ fn fitted() -> History<i64, ConstantDrift, trueskill_tt::NullObserver, String> {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.05))
|
.drift(ConstantDrift::new(0.05))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 30,
|
max_iter: 30,
|
||||||
epsilon: 1e-10,
|
epsilon: 1e-10,
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ fn bench_scored_history(c: &mut Criterion) {
|
|||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.drift(ConstantDrift(0.03))
|
.drift(ConstantDrift::new(0.03))
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ fn main() {
|
|||||||
|
|
||||||
let mut hist: History<i64, _, _, String> = History::builder_with_key()
|
let mut hist: History<i64, _, _, String> = History::builder_with_key()
|
||||||
.sigma(1.6)
|
.sigma(1.6)
|
||||||
.drift(ConstantDrift(0.036))
|
.drift(ConstantDrift::new(0.036))
|
||||||
.convergence(trueskill_tt::ConvergenceOptions {
|
.convergence(trueskill_tt::ConvergenceOptions {
|
||||||
// This history needs 30 sweeps to reach the epsilon below. It was
|
// This history needs 30 sweeps to reach the epsilon below. It was
|
||||||
// capped at 10 until the `#[must_use]` on `ConvergenceReport`
|
// capped at 10 until the `#[must_use]` on `ConvergenceReport`
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ fn main() {
|
|||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.drift(ConstantDrift(0.03))
|
.drift(ConstantDrift::new(0.03))
|
||||||
.score_sigma(2.0) // tune to data; smaller = trust margins more
|
.score_sigma(2.0) // tune to data; smaller = trust margins more
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -208,7 +208,11 @@ mod tests {
|
|||||||
type R = Rating<i64, ConstantDrift>;
|
type R = Rating<i64, ConstantDrift>;
|
||||||
|
|
||||||
fn rating(mu: f64, sigma: f64) -> R {
|
fn rating(mu: f64, sigma: f64) -> R {
|
||||||
R::new(Gaussian::from_ms(mu, sigma), BETA, ConstantDrift(GAMMA))
|
R::new(
|
||||||
|
Gaussian::from_ms(mu, sigma),
|
||||||
|
BETA,
|
||||||
|
ConstantDrift::new(GAMMA),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn options(p_draw: f64) -> GameOptions {
|
fn options(p_draw: f64) -> GameOptions {
|
||||||
|
|||||||
+46
-13
@@ -22,24 +22,57 @@ pub trait Drift<T: Time>: Copy + Debug + Send + Sync {
|
|||||||
/// For `Time = i64`: variance added is `(to - from) * gamma^2`.
|
/// For `Time = i64`: variance added is `(to - from) * gamma^2`.
|
||||||
/// For `Time = Untimed`: elapsed is always 0, so drift is always 0.
|
/// For `Time = Untimed`: elapsed is always 0, so drift is always 0.
|
||||||
///
|
///
|
||||||
/// # The sign of `gamma` is not meaningful
|
/// # Why the field is private
|
||||||
///
|
///
|
||||||
/// `gamma` enters only as `gamma * gamma`, so `ConstantDrift(-0.05)` produces
|
/// `gamma` enters only as `gamma * gamma`, so a negative value is squared away:
|
||||||
/// results **bit identical** to `ConstantDrift(0.05)`. That is the same
|
/// measured against the old public-field form, `ConstantDrift(-0.0833)` produced
|
||||||
/// sign-absorption `HistoryBuilder::sigma`, `HistoryBuilder::beta`,
|
/// results **bit identical** to `ConstantDrift(0.0833)`. The sign was neither
|
||||||
/// `Gaussian::from_ms` and `Rating::new` all reject outright.
|
/// rejected nor honoured — it vanished. That is the same sign-absorption `HistoryBuilder::sigma`,
|
||||||
|
/// `HistoryBuilder::beta`, `Gaussian::from_ms` and `Rating::new` all reject.
|
||||||
///
|
///
|
||||||
/// It is not rejected here because the field is public and positional, so
|
/// It could not be checked while the field was a public tuple position, because
|
||||||
/// there is no constructor to intercept — sealing it would break every
|
/// there was no constructor to intercept. Validating inside
|
||||||
/// `ConstantDrift(x)` in existence for a case whose *resulting model* is
|
/// `variance_for_elapsed` would have been worse: it runs inside the sweep, so a
|
||||||
/// perfectly valid, just not the one a caller writing a minus sign expected.
|
/// construction-time mistake would panic mid-inference — and `Gaussian::from_ms`
|
||||||
|
/// is a worked example of why that is the wrong place for a guard, where
|
||||||
|
/// rejecting NaN turned the `NonFiniteResult` reporting path into a crash.
|
||||||
///
|
///
|
||||||
/// A non-finite `gamma` is a different matter and **is** rejected:
|
/// So [`ConstantDrift::new`] is the only way in, and it checks. Read the value
|
||||||
|
/// back with [`ConstantDrift::gamma`].
|
||||||
|
///
|
||||||
|
/// A non-finite gamma is caught a second time regardless:
|
||||||
/// `History::converge` validates the drift variance each competitor actually
|
/// `History::converge` validates the drift variance each competitor actually
|
||||||
/// accumulates, which also covers a custom [`Drift`] implementation, and
|
/// accumulates, which also covers a custom [`Drift`] implementation.
|
||||||
/// reports `InferenceError::InvalidParameter`.
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct ConstantDrift(pub f64);
|
pub struct ConstantDrift(f64);
|
||||||
|
|
||||||
|
impl ConstantDrift {
|
||||||
|
/// Drift of `gamma` standard deviations per unit time.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics unless `gamma` is finite and non-negative.
|
||||||
|
///
|
||||||
|
/// The field is private and this is the only constructor precisely so that
|
||||||
|
/// there is somewhere to check. While it was a public tuple field there was
|
||||||
|
/// nothing to intercept, and a negative gamma was silently squared away —
|
||||||
|
/// see the type docs.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(gamma: f64) -> Self {
|
||||||
|
assert!(
|
||||||
|
gamma.is_finite() && gamma >= 0.0,
|
||||||
|
"gamma must be finite and non-negative (got {gamma}); it is only ever \
|
||||||
|
squared, so a negative value would silently behave as its absolute value"
|
||||||
|
);
|
||||||
|
Self(gamma)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Standard deviations of drift accumulated per unit time.
|
||||||
|
#[must_use]
|
||||||
|
pub fn gamma(&self) -> f64 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Time> Drift<T> for ConstantDrift {
|
impl<T: Time> Drift<T> for ConstantDrift {
|
||||||
fn variance_delta(&self, from: &T, to: &T) -> f64 {
|
fn variance_delta(&self, from: &T, to: &T) -> f64 {
|
||||||
|
|||||||
+2
-2
@@ -99,8 +99,8 @@ impl<K> Member<K> {
|
|||||||
/// Scale how fast this competitor drifts, relative to the history's drift.
|
/// Scale how fast this competitor drifts, relative to the history's drift.
|
||||||
///
|
///
|
||||||
/// The scale multiplies the drift *variance*, so it is in the same units as
|
/// The scale multiplies the drift *variance*, so it is in the same units as
|
||||||
/// `gamma`: `ConstantDrift(g)` at `scale = s` behaves exactly as
|
/// `gamma`: `ConstantDrift::new(g)` at `scale = s` behaves exactly as
|
||||||
/// `ConstantDrift(g * s)` would for this competitor alone.
|
/// `ConstantDrift::new(g * s)` would for this competitor alone.
|
||||||
///
|
///
|
||||||
/// `0.0` pins the competitor still — useful for a reference point that
|
/// `0.0` pins the competitor still — useful for a reference point that
|
||||||
/// shares a scale with moving competitors but should not itself move: a bot
|
/// shares a scale with moving competitors but should not itself move: a bot
|
||||||
|
|||||||
+63
-39
@@ -623,12 +623,12 @@ mod tests {
|
|||||||
let t_a = R::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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_b = R::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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let w = [vec![1.0], vec![1.0]];
|
let w = [vec![1.0], vec![1.0]];
|
||||||
@@ -651,12 +651,12 @@ mod tests {
|
|||||||
let t_a = R::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::new(GAMMA),
|
||||||
);
|
);
|
||||||
let t_b = R::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::new(GAMMA),
|
||||||
);
|
);
|
||||||
|
|
||||||
let w = [vec![1.0], vec![1.0]];
|
let w = [vec![1.0], vec![1.0]];
|
||||||
@@ -676,8 +676,16 @@ 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 = R::new(Gaussian::from_ms(1.139, 0.531), 1.0, ConstantDrift(0.2125));
|
let t_a = R::new(
|
||||||
let t_b = R::new(Gaussian::from_ms(15.568, 0.51), 1.0, ConstantDrift(0.2125));
|
Gaussian::from_ms(1.139, 0.531),
|
||||||
|
1.0,
|
||||||
|
ConstantDrift::new(0.2125),
|
||||||
|
);
|
||||||
|
let t_b = R::new(
|
||||||
|
Gaussian::from_ms(15.568, 0.51),
|
||||||
|
1.0,
|
||||||
|
ConstantDrift::new(0.2125),
|
||||||
|
);
|
||||||
|
|
||||||
let w = [vec![1.0], vec![1.0]];
|
let w = [vec![1.0], vec![1.0]];
|
||||||
let g = Game::ranked_with_arena(
|
let g = Game::ranked_with_arena(
|
||||||
@@ -699,17 +707,17 @@ mod tests {
|
|||||||
vec![R::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::new(25.0 / 300.0),
|
||||||
)],
|
)],
|
||||||
vec![R::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::new(25.0 / 300.0),
|
||||||
)],
|
)],
|
||||||
vec![R::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::new(25.0 / 300.0),
|
||||||
)],
|
)],
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -779,12 +787,12 @@ mod tests {
|
|||||||
let t_a = R::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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_b = R::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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let w = [vec![1.0], vec![1.0]];
|
let w = [vec![1.0], vec![1.0]];
|
||||||
@@ -811,12 +819,12 @@ mod tests {
|
|||||||
let t_a = R::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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_b = R::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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let w = [vec![1.0], vec![1.0]];
|
let w = [vec![1.0], vec![1.0]];
|
||||||
@@ -842,17 +850,17 @@ mod tests {
|
|||||||
let t_a = R::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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_b = R::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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_c = R::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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let w = [vec![1.0], vec![1.0], vec![1.0]];
|
let w = [vec![1.0], vec![1.0], vec![1.0]];
|
||||||
@@ -879,17 +887,17 @@ mod tests {
|
|||||||
let t_a = R::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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_b = R::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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let t_c = R::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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let w = [vec![1.0], vec![1.0], vec![1.0]];
|
let w = [vec![1.0], vec![1.0], vec![1.0]];
|
||||||
@@ -918,29 +926,29 @@ mod tests {
|
|||||||
R::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::new(25.0 / 300.0),
|
||||||
),
|
),
|
||||||
R::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::new(25.0 / 300.0),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
let t_b = vec![R::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::new(25.0 / 300.0),
|
||||||
)];
|
)];
|
||||||
let t_c = vec![
|
let t_c = vec![
|
||||||
R::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::new(25.0 / 300.0),
|
||||||
),
|
),
|
||||||
R::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::new(25.0 / 300.0),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -970,12 +978,12 @@ mod tests {
|
|||||||
let t_a = vec![R::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::new(0.0),
|
||||||
)];
|
)];
|
||||||
let t_b = vec![R::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::new(0.0),
|
||||||
)];
|
)];
|
||||||
|
|
||||||
let w = [w_a, w_b];
|
let w = [w_a, w_b];
|
||||||
@@ -1053,8 +1061,16 @@ 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![R::new(Gaussian::from_ms(2.0, 6.0), 1.0, ConstantDrift(0.0))];
|
let t_a = vec![R::new(
|
||||||
let t_b = vec![R::new(Gaussian::from_ms(2.0, 6.0), 1.0, ConstantDrift(0.0))];
|
Gaussian::from_ms(2.0, 6.0),
|
||||||
|
1.0,
|
||||||
|
ConstantDrift::new(0.0),
|
||||||
|
)];
|
||||||
|
let t_b = vec![R::new(
|
||||||
|
Gaussian::from_ms(2.0, 6.0),
|
||||||
|
1.0,
|
||||||
|
ConstantDrift::new(0.0),
|
||||||
|
)];
|
||||||
|
|
||||||
let w = [w_a, w_b];
|
let w = [w_a, w_b];
|
||||||
let g = Game::ranked_with_arena(
|
let g = Game::ranked_with_arena(
|
||||||
@@ -1081,8 +1097,16 @@ 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![R::new(Gaussian::from_ms(2.0, 6.0), 1.0, ConstantDrift(0.0))];
|
let t_a = vec![R::new(
|
||||||
let t_b = vec![R::new(Gaussian::from_ms(2.0, 6.0), 1.0, ConstantDrift(0.0))];
|
Gaussian::from_ms(2.0, 6.0),
|
||||||
|
1.0,
|
||||||
|
ConstantDrift::new(0.0),
|
||||||
|
)];
|
||||||
|
let t_b = vec![R::new(
|
||||||
|
Gaussian::from_ms(2.0, 6.0),
|
||||||
|
1.0,
|
||||||
|
ConstantDrift::new(0.0),
|
||||||
|
)];
|
||||||
|
|
||||||
let w = [w_a, w_b];
|
let w = [w_a, w_b];
|
||||||
let g = Game::ranked_with_arena(
|
let g = Game::ranked_with_arena(
|
||||||
@@ -1125,7 +1149,7 @@ mod tests {
|
|||||||
let prior = R::new(
|
let prior = 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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let teams = vec![vec![prior], vec![prior]];
|
let teams = vec![vec![prior], vec![prior]];
|
||||||
let result = vec![10.0, 0.0]; // a beat b by 10
|
let result = vec![10.0, 0.0]; // a beat b by 10
|
||||||
@@ -1175,7 +1199,7 @@ mod tests {
|
|||||||
let prior = R::new(
|
let prior = 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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let opts = GameOptions {
|
let opts = GameOptions {
|
||||||
score_sigma: 1.0,
|
score_sigma: 1.0,
|
||||||
@@ -1191,7 +1215,7 @@ mod tests {
|
|||||||
let prior = R::new(
|
let prior = 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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let err = Game::scored(
|
let err = Game::scored(
|
||||||
&[&[prior], &[prior]],
|
&[&[prior], &[prior]],
|
||||||
@@ -1210,7 +1234,7 @@ mod tests {
|
|||||||
let prior = R::new(
|
let prior = 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::new(25.0 / 300.0),
|
||||||
);
|
);
|
||||||
let opts = GameOptions {
|
let opts = GameOptions {
|
||||||
score_sigma: 0.0,
|
score_sigma: 0.0,
|
||||||
@@ -1237,12 +1261,12 @@ mod tests {
|
|||||||
R::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::new(0.0),
|
||||||
),
|
),
|
||||||
R::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::new(0.0),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
let w_a = vec![0.4, 0.8];
|
let w_a = vec![0.4, 0.8];
|
||||||
@@ -1251,12 +1275,12 @@ mod tests {
|
|||||||
R::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::new(0.0),
|
||||||
),
|
),
|
||||||
R::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::new(0.0),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
let w_b = vec![0.9, 0.6];
|
let w_b = vec![0.9, 0.6];
|
||||||
@@ -1370,7 +1394,7 @@ mod tests {
|
|||||||
vec![R::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::new(0.0),
|
||||||
)],
|
)],
|
||||||
],
|
],
|
||||||
&[1.0, 0.0],
|
&[1.0, 0.0],
|
||||||
|
|||||||
+18
-18
@@ -139,8 +139,8 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> HistoryBuilder<
|
|||||||
/// Panics if `score_sigma` is not strictly positive.
|
/// Panics if `score_sigma` is not strictly positive.
|
||||||
pub fn score_sigma(mut self, score_sigma: f64) -> Self {
|
pub fn score_sigma(mut self, score_sigma: f64) -> Self {
|
||||||
assert!(
|
assert!(
|
||||||
score_sigma > 0.0,
|
score_sigma.is_finite() && score_sigma > 0.0,
|
||||||
"score_sigma must be positive (got {score_sigma})"
|
"score_sigma must be finite and positive (got {score_sigma})"
|
||||||
);
|
);
|
||||||
self.score_sigma = score_sigma;
|
self.score_sigma = score_sigma;
|
||||||
self
|
self
|
||||||
@@ -224,7 +224,7 @@ impl Default for HistoryBuilder<i64, ConstantDrift, NullObserver, &'static str>
|
|||||||
mu: MU,
|
mu: MU,
|
||||||
sigma: SIGMA,
|
sigma: SIGMA,
|
||||||
beta: BETA,
|
beta: BETA,
|
||||||
drift: ConstantDrift(GAMMA),
|
drift: ConstantDrift::new(GAMMA),
|
||||||
p_draw: P_DRAW,
|
p_draw: P_DRAW,
|
||||||
score_sigma: 1.0,
|
score_sigma: 1.0,
|
||||||
convergence: ConvergenceOptions::default(),
|
convergence: ConvergenceOptions::default(),
|
||||||
@@ -358,7 +358,7 @@ impl<K: Eq + Hash + Clone> History<i64, ConstantDrift, NullObserver, K> {
|
|||||||
mu: MU,
|
mu: MU,
|
||||||
sigma: SIGMA,
|
sigma: SIGMA,
|
||||||
beta: BETA,
|
beta: BETA,
|
||||||
drift: ConstantDrift(GAMMA),
|
drift: ConstantDrift::new(GAMMA),
|
||||||
p_draw: P_DRAW,
|
p_draw: P_DRAW,
|
||||||
score_sigma: 1.0,
|
score_sigma: 1.0,
|
||||||
convergence: ConvergenceOptions::default(),
|
convergence: ConvergenceOptions::default(),
|
||||||
@@ -1663,7 +1663,7 @@ impl<T: Time, D: Drift<T>, O: Observer<T>, K: Eq + Hash + Clone> History<T, D, O
|
|||||||
// instead, which also covers a custom impl.
|
// instead, which also covers a custom impl.
|
||||||
//
|
//
|
||||||
// `ConstantDrift` returns `elapsed * gamma * gamma`, so a negative
|
// `ConstantDrift` returns `elapsed * gamma * gamma`, so a negative
|
||||||
// gamma is squared away: measured, `ConstantDrift(-0.0833)` gave
|
// gamma is squared away: measured, `ConstantDrift::new(-0.0833)` gave
|
||||||
// results **bit identical** to `+0.0833`, the same sign-absorption
|
// results **bit identical** to `+0.0833`, the same sign-absorption
|
||||||
// defect already rejected for `sigma` and `beta`. A non-finite gamma
|
// defect already rejected for `sigma` and `beta`. A non-finite gamma
|
||||||
// poisons every posterior derived from it.
|
// poisons every posterior derived from it.
|
||||||
@@ -2606,7 +2606,7 @@ mod tests {
|
|||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.drift(ConstantDrift(0.15 * 25.0 / 3.0))
|
.drift(ConstantDrift::new(0.15 * 25.0 / 3.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let events = make_events_1v1(
|
let events = make_events_1v1(
|
||||||
@@ -2671,7 +2671,7 @@ mod tests {
|
|||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.drift(ConstantDrift(0.15 * 25.0 / 3.0))
|
.drift(ConstantDrift::new(0.15 * 25.0 / 3.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let events = make_events_1v1(
|
let events = make_events_1v1(
|
||||||
@@ -2716,7 +2716,7 @@ mod tests {
|
|||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.drift(ConstantDrift(25.0 / 300.0))
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let events = make_events_1v1(
|
let events = make_events_1v1(
|
||||||
@@ -2764,7 +2764,7 @@ mod tests {
|
|||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.drift(ConstantDrift(25.0 / 300.0))
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let events = make_events_1v1(
|
let events = make_events_1v1(
|
||||||
@@ -2806,7 +2806,7 @@ mod tests {
|
|||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.drift(ConstantDrift(25.0 / 300.0))
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let events = make_events_1v1(
|
let events = make_events_1v1(
|
||||||
@@ -2851,7 +2851,7 @@ mod tests {
|
|||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let events: Vec<Event<i64, &'static str>> = vec![
|
let events: Vec<Event<i64, &'static str>> = vec![
|
||||||
@@ -2957,7 +2957,7 @@ mod tests {
|
|||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let events = make_events_1v1(
|
let events = make_events_1v1(
|
||||||
@@ -3054,7 +3054,7 @@ mod tests {
|
|||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let events = make_events_1v1(
|
let events = make_events_1v1(
|
||||||
@@ -3227,7 +3227,7 @@ mod tests {
|
|||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let events = make_events_1v1(
|
let events = make_events_1v1(
|
||||||
@@ -3330,7 +3330,7 @@ mod tests {
|
|||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let events = make_events_1v1(
|
let events = make_events_1v1(
|
||||||
@@ -3434,7 +3434,7 @@ mod tests {
|
|||||||
.mu(2.0)
|
.mu(2.0)
|
||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// empty results in old API = team 0 wins: a wins event 1, b wins event 2
|
// empty results in old API = team 0 wins: a wins event 1, b wins event 2
|
||||||
@@ -3501,7 +3501,7 @@ mod tests {
|
|||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 30,
|
max_iter: 30,
|
||||||
epsilon: 1e-6,
|
epsilon: 1e-6,
|
||||||
@@ -3528,7 +3528,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "score_sigma must be positive")]
|
#[should_panic(expected = "score_sigma must be finite and positive")]
|
||||||
fn history_builder_rejects_zero_score_sigma() {
|
fn history_builder_rejects_zero_score_sigma() {
|
||||||
let _ = History::builder().score_sigma(0.0).build();
|
let _ = History::builder().score_sigma(0.0).build();
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -110,7 +110,7 @@ impl Default for Rating<i64, ConstantDrift> {
|
|||||||
Self {
|
Self {
|
||||||
prior: Gaussian::default(),
|
prior: Gaussian::default(),
|
||||||
beta: BETA,
|
beta: BETA,
|
||||||
drift: ConstantDrift(GAMMA),
|
drift: ConstantDrift::new(GAMMA),
|
||||||
drift_scale: 1.0,
|
drift_scale: 1.0,
|
||||||
_time: PhantomData,
|
_time: PhantomData,
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -911,7 +911,7 @@ mod tests {
|
|||||||
rating: Rating::new(
|
rating: Rating::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::new(25.0 / 300.0),
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -988,7 +988,7 @@ mod tests {
|
|||||||
rating: Rating::new(
|
rating: Rating::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::new(25.0 / 300.0),
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -1068,7 +1068,7 @@ mod tests {
|
|||||||
rating: Rating::new(
|
rating: Rating::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::new(25.0 / 300.0),
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -1171,7 +1171,7 @@ mod tests {
|
|||||||
rating: Rating::new(
|
rating: Rating::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::new(25.0 / 300.0),
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ fn additive_structure_makes_sums_wide_and_differences_tight() {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 20_000,
|
max_iter: 20_000,
|
||||||
epsilon: 1e-12,
|
epsilon: 1e-12,
|
||||||
|
|||||||
+4
-4
@@ -11,7 +11,7 @@ fn add_events_bulk_via_iter() {
|
|||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.p_draw(0.0)
|
.p_draw(0.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 30,
|
max_iter: 30,
|
||||||
epsilon: 1e-6,
|
epsilon: 1e-6,
|
||||||
@@ -53,7 +53,7 @@ fn add_events_draw() {
|
|||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.p_draw(0.25)
|
.p_draw(0.25)
|
||||||
.drift(ConstantDrift(25.0 / 300.0))
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let events: Vec<Event<i64, &'static str>> = vec![Event {
|
let events: Vec<Event<i64, &'static str>> = vec![Event {
|
||||||
@@ -181,7 +181,7 @@ fn log_evidence_total_vs_subset() {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.p_draw(0.0)
|
.p_draw(0.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.build();
|
.build();
|
||||||
h.record_winner(&"a", &"b", 1).unwrap();
|
h.record_winner(&"a", &"b", 1).unwrap();
|
||||||
h.record_winner(&"b", &"a", 2).unwrap();
|
h.record_winner(&"b", &"a", 2).unwrap();
|
||||||
@@ -236,7 +236,7 @@ fn fluent_event_builder_scores() {
|
|||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
h.event(1)
|
h.event(1)
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
//! Every public entry point that takes a magnitude, in one place.
|
||||||
|
//!
|
||||||
|
//! This defect class was closed three times in one session and reopened twice,
|
||||||
|
//! because each fix validated the layer it had just touched and inferred the
|
||||||
|
//! rest: `HistoryBuilder` first, then `Game`'s own entry points, then the
|
||||||
|
//! constructors beneath both. A per-site fix cannot notice the site nobody
|
||||||
|
//! thought of.
|
||||||
|
//!
|
||||||
|
//! So this enumerates them. `sigma`, `beta` and `gamma` all enter inference
|
||||||
|
//! only as squares, which means a negative value does not fail — it behaves as
|
||||||
|
//! its absolute value, bit for bit, and the sign vanishes with no diagnostic.
|
||||||
|
//! Non-finite values poison every posterior derived from them.
|
||||||
|
//!
|
||||||
|
//! Adding a public constructor that takes one of these and not adding it here
|
||||||
|
//! is the failure this file exists to make harder.
|
||||||
|
|
||||||
|
use std::panic::{AssertUnwindSafe, catch_unwind};
|
||||||
|
|
||||||
|
use trueskill_tt::{ConstantDrift, Gaussian, History, Member, Outcome, Rating};
|
||||||
|
|
||||||
|
/// Did the entry point refuse the value, by panic or by `Err`?
|
||||||
|
fn refuses(f: impl FnOnce() -> bool) -> bool {
|
||||||
|
catch_unwind(AssertUnwindSafe(f)).unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One entry point, as a name and a closure that applies a value to it.
|
||||||
|
type Case = (&'static str, Box<dyn Fn(f64) -> bool>);
|
||||||
|
|
||||||
|
/// Entry points that must reject a negative magnitude.
|
||||||
|
///
|
||||||
|
/// Each closure returns `true` if it refused by returning an error; a panic is
|
||||||
|
/// also a refusal and is caught.
|
||||||
|
#[test]
|
||||||
|
fn every_magnitude_parameter_rejects_a_negative_value() {
|
||||||
|
let cases: Vec<Case> = vec![
|
||||||
|
(
|
||||||
|
"Gaussian::from_ms(sigma)",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = Gaussian::from_ms(25.0, v);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Rating::new(beta)",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = Rating::<i64, ConstantDrift>::new(
|
||||||
|
Gaussian::default(),
|
||||||
|
v,
|
||||||
|
ConstantDrift::new(0.0),
|
||||||
|
);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ConstantDrift::new(gamma)",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = ConstantDrift::new(v);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"HistoryBuilder::sigma",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = History::builder().sigma(v);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"HistoryBuilder::beta",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = History::builder().beta(v);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"HistoryBuilder::score_sigma",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = History::builder().score_sigma(v);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"HistoryBuilder::p_draw",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = History::builder().p_draw(v);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Member::with_drift_scale (at ingestion)",
|
||||||
|
Box::new(|v| {
|
||||||
|
let mut h = History::builder().build();
|
||||||
|
h.add_events(vec![trueskill_tt::Event {
|
||||||
|
time: 1i64,
|
||||||
|
teams: smallvec::smallvec![
|
||||||
|
trueskill_tt::Team::with_members([Member::new("a").with_drift_scale(v)]),
|
||||||
|
trueskill_tt::Team::with_members([Member::new("b")]),
|
||||||
|
],
|
||||||
|
outcome: Outcome::winner(0, 2),
|
||||||
|
}])
|
||||||
|
.is_err()
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Outcome::scores_with_sigma (at ingestion)",
|
||||||
|
Box::new(|v| {
|
||||||
|
let mut h = History::builder().build();
|
||||||
|
h.add_events(vec![trueskill_tt::Event {
|
||||||
|
time: 1i64,
|
||||||
|
teams: smallvec::smallvec![
|
||||||
|
trueskill_tt::Team::with_members([Member::new("a")]),
|
||||||
|
trueskill_tt::Team::with_members([Member::new("b")]),
|
||||||
|
],
|
||||||
|
outcome: Outcome::scores_with_sigma([3.0, 1.0], v),
|
||||||
|
}])
|
||||||
|
.is_err()
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut accepted = Vec::new();
|
||||||
|
for (name, f) in &cases {
|
||||||
|
if !refuses(|| f(-1.0)) {
|
||||||
|
accepted.push(*name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
accepted.is_empty(),
|
||||||
|
"these accepted a negative magnitude, which is squared away silently \
|
||||||
|
rather than honoured or refused:\n {}",
|
||||||
|
accepted.join("\n ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same set, for NaN and infinity.
|
||||||
|
///
|
||||||
|
/// `Gaussian::from_ms` is deliberately absent: a broken fit produces a NaN
|
||||||
|
/// sigma legitimately and `converge` reports it as `NonFiniteResult`. Rejecting
|
||||||
|
/// it in the constructor turned that reporting path into a panic inside
|
||||||
|
/// inference — see the comment on `from_ms`.
|
||||||
|
#[test]
|
||||||
|
fn every_magnitude_parameter_rejects_a_non_finite_value() {
|
||||||
|
let cases: Vec<Case> = vec![
|
||||||
|
(
|
||||||
|
"Rating::new(beta)",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = Rating::<i64, ConstantDrift>::new(
|
||||||
|
Gaussian::default(),
|
||||||
|
v,
|
||||||
|
ConstantDrift::new(0.0),
|
||||||
|
);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ConstantDrift::new(gamma)",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = ConstantDrift::new(v);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"HistoryBuilder::sigma",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = History::builder().sigma(v);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"HistoryBuilder::beta",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = History::builder().beta(v);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"HistoryBuilder::mu",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = History::builder().mu(v);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"HistoryBuilder::score_sigma",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = History::builder().score_sigma(v);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"HistoryBuilder::p_draw",
|
||||||
|
Box::new(|v| {
|
||||||
|
let _ = History::builder().p_draw(v);
|
||||||
|
false
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut accepted = Vec::new();
|
||||||
|
for (name, f) in &cases {
|
||||||
|
for bad in [f64::NAN, f64::INFINITY] {
|
||||||
|
if !refuses(|| f(bad)) {
|
||||||
|
accepted.push(format!("{name} accepted {bad}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
accepted.is_empty(),
|
||||||
|
"these accepted a non-finite magnitude:\n {}",
|
||||||
|
accepted.join("\n ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The suite must not pass by refusing everything.
|
||||||
|
#[test]
|
||||||
|
fn ordinary_values_are_still_accepted() {
|
||||||
|
let _ = Gaussian::from_ms(25.0, 8.33);
|
||||||
|
let _ = Rating::<i64, ConstantDrift>::new(Gaussian::default(), 4.17, ConstantDrift::new(0.05));
|
||||||
|
let _ = ConstantDrift::new(0.0833);
|
||||||
|
let _ = History::builder()
|
||||||
|
.mu(25.0)
|
||||||
|
.sigma(8.33)
|
||||||
|
.beta(4.17)
|
||||||
|
.score_sigma(1.0)
|
||||||
|
.p_draw(0.1);
|
||||||
|
|
||||||
|
// Zero beta and zero gamma are legitimate, not degenerate.
|
||||||
|
let _ = ConstantDrift::new(0.0);
|
||||||
|
let _ = Rating::<i64, ConstantDrift>::new(Gaussian::default(), 0.0, ConstantDrift::new(0.0));
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ fn capped(max_iter: usize) -> H {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.5))
|
.drift(ConstantDrift::new(0.5))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter,
|
max_iter,
|
||||||
epsilon: 1e-13,
|
epsilon: 1e-13,
|
||||||
@@ -112,7 +112,7 @@ fn the_default_cap_clears_an_ordinary_history() {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.05))
|
.drift(ConstantDrift::new(0.05))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let mut events = Vec::new();
|
let mut events = Vec::new();
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ fn fitted() -> H {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.05))
|
.drift(ConstantDrift::new(0.05))
|
||||||
.unknown_keys(UnknownKeys::Prior)
|
.unknown_keys(UnknownKeys::Prior)
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 20_000,
|
max_iter: 20_000,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ fn rating() -> R {
|
|||||||
R::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(25.0 / 300.0),
|
ConstantDrift::new(25.0 / 300.0),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,8 +281,16 @@ fn log_evidence_survives_a_long_diff_chain() {
|
|||||||
/// `erfc` approximation; the evidence floor keeps `ln` finite.
|
/// `erfc` approximation; the evidence floor keeps `ln` finite.
|
||||||
#[test]
|
#[test]
|
||||||
fn log_evidence_finite_for_near_certain_outcome() {
|
fn log_evidence_finite_for_near_certain_outcome() {
|
||||||
let overwhelming = R::new(Gaussian::from_ms(5_000.0, 0.5), 1.0, ConstantDrift(0.0));
|
let overwhelming = R::new(
|
||||||
let hopeless = R::new(Gaussian::from_ms(-5_000.0, 0.5), 1.0, ConstantDrift(0.0));
|
Gaussian::from_ms(5_000.0, 0.5),
|
||||||
|
1.0,
|
||||||
|
ConstantDrift::new(0.0),
|
||||||
|
);
|
||||||
|
let hopeless = R::new(
|
||||||
|
Gaussian::from_ms(-5_000.0, 0.5),
|
||||||
|
1.0,
|
||||||
|
ConstantDrift::new(0.0),
|
||||||
|
);
|
||||||
let a = [overwhelming];
|
let a = [overwhelming];
|
||||||
let b = [hopeless];
|
let b = [hopeless];
|
||||||
let teams: Vec<&[R]> = vec![&a, &b];
|
let teams: Vec<&[R]> = vec![&a, &b];
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ fn build_and_converge() -> Fingerprint {
|
|||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.drift(ConstantDrift(25.0 / 300.0))
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 20_000,
|
max_iter: 20_000,
|
||||||
epsilon: 1e-9,
|
epsilon: 1e-9,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! The scale multiplies the *variance* the history's `Drift` contributes for
|
//! The scale multiplies the *variance* the history's `Drift` contributes for
|
||||||
//! that competitor, so `scale` is in the same units as `gamma`:
|
//! that competitor, so `scale` is in the same units as `gamma`:
|
||||||
//! `ConstantDrift(g)` at `scale = s` behaves as `ConstantDrift(g * s)` would.
|
//! `ConstantDrift::new(g)` at `scale = s` behaves as `ConstantDrift::new(g * s)` would.
|
||||||
//! `scale = 0.0` pins a competitor still — an anchor, a rating floor, a course
|
//! `scale = 0.0` pins a competitor still — an anchor, a rating floor, a course
|
||||||
//! difficulty — while everyone around them keeps drifting.
|
//! difficulty — while everyone around them keeps drifting.
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ fn fit(events: Vec<Event<i64, &'static str>>, gamma: f64) -> Fit {
|
|||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.p_draw(0.0)
|
.p_draw(0.0)
|
||||||
.drift(ConstantDrift(gamma))
|
.drift(ConstantDrift::new(gamma))
|
||||||
.convergence(CONVERGENCE)
|
.convergence(CONVERGENCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ fn scale_is_equivalent_to_scaling_gamma() {
|
|||||||
assert_eq!(t_l, t_r);
|
assert_eq!(t_l, t_r);
|
||||||
assert!(
|
assert!(
|
||||||
(g_l.mu() - g_r.mu()).abs() < 1e-9 && (g_l.sigma() - g_r.sigma()).abs() < 1e-9,
|
(g_l.mu() - g_r.mu()).abs() < 1e-9 && (g_l.sigma() - g_r.sigma()).abs() < 1e-9,
|
||||||
"ConstantDrift(0.3) at scale 0.5 must equal ConstantDrift(0.15) for {key} at \
|
"ConstantDrift::new(0.3) at scale 0.5 must equal ConstantDrift::new(0.15) for {key} at \
|
||||||
t={t_l}: ({}, {}) vs ({}, {})",
|
t={t_l}: ({}, {}) vs ({}, {})",
|
||||||
g_l.mu(),
|
g_l.mu(),
|
||||||
g_l.sigma(),
|
g_l.sigma(),
|
||||||
@@ -218,7 +218,7 @@ fn mixed_static_and_drifting_graph_converges() {
|
|||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.p_draw(0.0)
|
.p_draw(0.0)
|
||||||
.drift(ConstantDrift(25.0 / 300.0))
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
||||||
.convergence(CONVERGENCE)
|
.convergence(CONVERGENCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ fn mixed_static_and_drifting_graph_converges() {
|
|||||||
|
|
||||||
fn reject(scale: f64) -> InferenceError {
|
fn reject(scale: f64) -> InferenceError {
|
||||||
let mut h = History::builder()
|
let mut h = History::builder()
|
||||||
.drift(ConstantDrift(25.0 / 300.0))
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let events: Vec<Event<i64, &'static str>> = vec![Event {
|
let events: Vec<Event<i64, &'static str>> = vec![Event {
|
||||||
@@ -360,7 +360,7 @@ fn drift_scale_applies_when_set_after_first_appearance() {
|
|||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.p_draw(0.0)
|
.p_draw(0.0)
|
||||||
.drift(ConstantDrift(25.0 / 300.0))
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
||||||
.convergence(CONVERGENCE)
|
.convergence(CONVERGENCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ use trueskill_tt::{ConstantDrift, Game, GameOptions, Gaussian, Outcome, Rating};
|
|||||||
type R = Rating<i64, ConstantDrift>;
|
type R = Rating<i64, ConstantDrift>;
|
||||||
|
|
||||||
fn ts_rating(mu: f64, sigma: f64, beta: f64, gamma: f64) -> R {
|
fn ts_rating(mu: f64, sigma: f64, beta: f64, gamma: f64) -> R {
|
||||||
R::new(Gaussian::from_ms(mu, sigma), beta, ConstantDrift(gamma))
|
R::new(
|
||||||
|
Gaussian::from_ms(mu, sigma),
|
||||||
|
beta,
|
||||||
|
ConstantDrift::new(gamma),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ fn history() -> H {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.5))
|
.drift(ConstantDrift::new(0.5))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 20_000,
|
max_iter: 20_000,
|
||||||
epsilon: 1e-13,
|
epsilon: 1e-13,
|
||||||
|
|||||||
+3
-3
@@ -8,7 +8,7 @@ fn default_rating() -> R {
|
|||||||
R::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(25.0 / 300.0),
|
ConstantDrift::new(25.0 / 300.0),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ fn game_one_v_one_shortcut() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn game_ranked_rejects_bad_p_draw() {
|
fn game_ranked_rejects_bad_p_draw() {
|
||||||
let a = R::new(Gaussian::default(), 1.0, ConstantDrift(0.0));
|
let a = R::new(Gaussian::default(), 1.0, ConstantDrift::new(0.0));
|
||||||
let err = Game::<i64, _>::ranked(
|
let err = Game::<i64, _>::ranked(
|
||||||
&[&[a], &[a]],
|
&[&[a], &[a]],
|
||||||
Outcome::winner(0, 2),
|
Outcome::winner(0, 2),
|
||||||
@@ -56,7 +56,7 @@ fn game_ranked_rejects_bad_p_draw() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn game_ranked_rejects_mismatched_ranks() {
|
fn game_ranked_rejects_mismatched_ranks() {
|
||||||
let a = R::new(Gaussian::default(), 1.0, ConstantDrift(0.0));
|
let a = R::new(Gaussian::default(), 1.0, ConstantDrift::new(0.0));
|
||||||
let err = Game::<i64, _>::ranked(
|
let err = Game::<i64, _>::ranked(
|
||||||
&[&[a], &[a]],
|
&[&[a], &[a]],
|
||||||
Outcome::ranking([0, 1, 2]),
|
Outcome::ranking([0, 1, 2]),
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ fn history(unknown: UnknownKeys) -> H {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.5))
|
.drift(ConstantDrift::new(0.5))
|
||||||
.unknown_keys(unknown)
|
.unknown_keys(unknown)
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 20_000,
|
max_iter: 20_000,
|
||||||
@@ -158,7 +158,7 @@ fn drift_free_competitors_shrink_the_joint_by_the_slice_count() {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(gamma))
|
.drift(ConstantDrift::new(gamma))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 20_000,
|
max_iter: 20_000,
|
||||||
epsilon: 1e-13,
|
epsilon: 1e-13,
|
||||||
@@ -197,7 +197,7 @@ fn pinned_competitors_collapse_consecutive_appearances() {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 20_000,
|
max_iter: 20_000,
|
||||||
epsilon: 1e-13,
|
epsilon: 1e-13,
|
||||||
@@ -284,7 +284,7 @@ fn a_drift_too_small_to_represent_collapses_rather_than_corrupting() {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.5))
|
.drift(ConstantDrift::new(0.5))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 20_000,
|
max_iter: 20_000,
|
||||||
epsilon: 1e-13,
|
epsilon: 1e-13,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ fn nan_after_fit(players: usize) -> usize {
|
|||||||
let mut h: History<i64, ConstantDrift, NullObserver, String> = History::builder_with_key()
|
let mut h: History<i64, ConstantDrift, NullObserver, String> = History::builder_with_key()
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.drift(ConstantDrift(0.1))
|
.drift(ConstantDrift::new(0.1))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: ITERATIONS,
|
max_iter: ITERATIONS,
|
||||||
epsilon: EPSILON,
|
epsilon: EPSILON,
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ fn fitted(
|
|||||||
.sigma(SIGMA0)
|
.sigma(SIGMA0)
|
||||||
.beta(BETA)
|
.beta(BETA)
|
||||||
.score_sigma(SCORE_SIGMA)
|
.score_sigma(SCORE_SIGMA)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 20_000,
|
max_iter: 20_000,
|
||||||
epsilon: 1e-13,
|
epsilon: 1e-13,
|
||||||
@@ -324,7 +324,7 @@ fn cost_scaling() {
|
|||||||
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_with_key()
|
let mut h: History<i64, _, _, String> = History::builder_with_key()
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 200,
|
max_iter: 200,
|
||||||
epsilon: 1e-8,
|
epsilon: 1e-8,
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ fn a_narrow_draw_margin_far_into_the_tail_still_fits() {
|
|||||||
.sigma(sd)
|
.sigma(sd)
|
||||||
.beta(beta)
|
.beta(beta)
|
||||||
.p_draw(p_draw)
|
.p_draw(p_draw)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.build();
|
.build();
|
||||||
h.add_events(vec![Event {
|
h.add_events(vec![Event {
|
||||||
time: 1i64,
|
time: 1i64,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ fn builder(
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.unknown_keys(policy)
|
.unknown_keys(policy)
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 5_000,
|
max_iter: 5_000,
|
||||||
|
|||||||
@@ -74,8 +74,16 @@ fn information_gain_never_exceeds_the_entropy_of_the_outcome() {
|
|||||||
let sigma_b = rng.log_uniform(1e-4, 1e2);
|
let sigma_b = rng.log_uniform(1e-4, 1e2);
|
||||||
let beta = rng.log_uniform(1e-4, 1e1);
|
let beta = rng.log_uniform(1e-4, 1e1);
|
||||||
|
|
||||||
let a = R::new(Gaussian::from_ms(mu_a, sigma_a), beta, ConstantDrift(0.0));
|
let a = R::new(
|
||||||
let b = R::new(Gaussian::from_ms(mu_b, sigma_b), beta, ConstantDrift(0.0));
|
Gaussian::from_ms(mu_a, sigma_a),
|
||||||
|
beta,
|
||||||
|
ConstantDrift::new(0.0),
|
||||||
|
);
|
||||||
|
let b = R::new(
|
||||||
|
Gaussian::from_ms(mu_b, sigma_b),
|
||||||
|
beta,
|
||||||
|
ConstantDrift::new(0.0),
|
||||||
|
);
|
||||||
let options = GameOptions {
|
let options = GameOptions {
|
||||||
p_draw: 0.0,
|
p_draw: 0.0,
|
||||||
..GameOptions::default()
|
..GameOptions::default()
|
||||||
@@ -129,12 +137,12 @@ fn the_known_ceiling_violation_no_longer_answers_wrongly() {
|
|||||||
let a = R::new(
|
let a = R::new(
|
||||||
Gaussian::from_ms(9.577_887_112_129_012, 0.000_132_507_526_585_134_38),
|
Gaussian::from_ms(9.577_887_112_129_012, 0.000_132_507_526_585_134_38),
|
||||||
0.000_307_235_559_013_096_2,
|
0.000_307_235_559_013_096_2,
|
||||||
ConstantDrift(0.0),
|
ConstantDrift::new(0.0),
|
||||||
);
|
);
|
||||||
let b = R::new(
|
let b = R::new(
|
||||||
Gaussian::from_ms(-14.114_932_828_525_696, 91.586_690_140_921_16),
|
Gaussian::from_ms(-14.114_932_828_525_696, 91.586_690_140_921_16),
|
||||||
0.000_307_235_559_013_096_2,
|
0.000_307_235_559_013_096_2,
|
||||||
ConstantDrift(0.0),
|
ConstantDrift::new(0.0),
|
||||||
);
|
);
|
||||||
let options = GameOptions {
|
let options = GameOptions {
|
||||||
p_draw: 0.0,
|
p_draw: 0.0,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ fn record_winner_builds_history() {
|
|||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.drift(ConstantDrift(25.0 / 300.0))
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 30,
|
max_iter: 30,
|
||||||
epsilon: 1e-6,
|
epsilon: 1e-6,
|
||||||
@@ -43,7 +43,7 @@ fn record_draw_with_p_draw_set() {
|
|||||||
.mu(25.0)
|
.mu(25.0)
|
||||||
.sigma(25.0 / 3.0)
|
.sigma(25.0 / 3.0)
|
||||||
.beta(25.0 / 6.0)
|
.beta(25.0 / 6.0)
|
||||||
.drift(ConstantDrift(25.0 / 300.0))
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
||||||
.p_draw(0.25)
|
.p_draw(0.25)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ fn history() -> H {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.5))
|
.drift(ConstantDrift::new(0.5))
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 20_000,
|
max_iter: 20_000,
|
||||||
epsilon: 1e-13,
|
epsilon: 1e-13,
|
||||||
|
|||||||
+3
-3
@@ -9,7 +9,7 @@ fn scored_two_team_one_event_pulls_winner_up() {
|
|||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.score_sigma(1.0)
|
.score_sigma(1.0)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ fn scored_zero_margin_treats_as_tie() {
|
|||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.score_sigma(1.0)
|
.score_sigma(1.0)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ fn scored_three_team_partial_order() {
|
|||||||
.mu(0.0)
|
.mu(0.0)
|
||||||
.sigma(2.0)
|
.sigma(2.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.score_sigma(1.0)
|
.score_sigma(1.0)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ fn history(gamma: f64) -> H {
|
|||||||
.sigma(SIGMA0)
|
.sigma(SIGMA0)
|
||||||
.beta(BETA)
|
.beta(BETA)
|
||||||
.score_sigma(SCORE_SIGMA)
|
.score_sigma(SCORE_SIGMA)
|
||||||
.drift(ConstantDrift(gamma))
|
.drift(ConstantDrift::new(gamma))
|
||||||
.unknown_keys(UnknownKeys::Reject)
|
.unknown_keys(UnknownKeys::Reject)
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 20_000,
|
max_iter: 20_000,
|
||||||
|
|||||||
+50
-17
@@ -22,7 +22,7 @@ fn rating() -> R {
|
|||||||
R::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::new(0.0),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,33 +286,66 @@ mod constructor_parameters {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "beta must be finite and non-negative")]
|
#[should_panic(expected = "beta must be finite and non-negative")]
|
||||||
fn a_negative_beta_is_rejected_by_rating_new() {
|
fn a_negative_beta_is_rejected_by_rating_new() {
|
||||||
let _ = Rating::<i64, ConstantDrift>::new(Gaussian::default(), -4.17, ConstantDrift(0.0));
|
let _ =
|
||||||
|
Rating::<i64, ConstantDrift>::new(Gaussian::default(), -4.17, ConstantDrift::new(0.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "beta must be finite and non-negative")]
|
#[should_panic(expected = "beta must be finite and non-negative")]
|
||||||
fn a_nan_beta_is_rejected_by_rating_new() {
|
fn a_nan_beta_is_rejected_by_rating_new() {
|
||||||
let _ =
|
let _ = Rating::<i64, ConstantDrift>::new(
|
||||||
Rating::<i64, ConstantDrift>::new(Gaussian::default(), f64::NAN, ConstantDrift(0.0));
|
Gaussian::default(),
|
||||||
|
f64::NAN,
|
||||||
|
ConstantDrift::new(0.0),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_zero_beta_is_accepted_by_rating_new() {
|
fn a_zero_beta_is_accepted_by_rating_new() {
|
||||||
let _ = Rating::<i64, ConstantDrift>::new(Gaussian::default(), 0.0, ConstantDrift(0.0));
|
let _ =
|
||||||
|
Rating::<i64, ConstantDrift>::new(Gaussian::default(), 0.0, ConstantDrift::new(0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `ConstantDrift` rejects at construction now that its field is private.
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "gamma must be finite and non-negative")]
|
||||||
|
fn a_negative_gamma_is_rejected_by_constant_drift_new() {
|
||||||
|
let _ = ConstantDrift::new(-0.0833);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "gamma must be finite and non-negative")]
|
||||||
|
fn a_non_finite_gamma_is_rejected_by_constant_drift_new() {
|
||||||
|
let _ = ConstantDrift::new(f64::NAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gamma_reads_back_what_was_given() {
|
||||||
|
assert_eq!(ConstantDrift::new(0.25).gamma(), 0.25);
|
||||||
|
assert_eq!(ConstantDrift::new(0.0).gamma(), 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `HistoryBuilder::drift` is generic and cannot inspect an arbitrary
|
/// `HistoryBuilder::drift` is generic and cannot inspect an arbitrary
|
||||||
/// `Drift`, so the check is on the variance each competitor actually
|
/// `Drift`, so the check on the variance each competitor accumulates is
|
||||||
/// accumulates. That also covers a custom implementation.
|
/// still needed — it is the only thing standing between a custom
|
||||||
|
/// implementation and a NaN fit. `ConstantDrift` can no longer reach it,
|
||||||
|
/// so this uses an implementation that can.
|
||||||
#[test]
|
#[test]
|
||||||
fn a_non_finite_drift_is_rejected_at_convergence() {
|
fn a_custom_drift_returning_a_bad_variance_is_rejected_at_convergence() {
|
||||||
for gamma in [f64::NAN, f64::INFINITY] {
|
#[derive(Clone, Copy, Debug)]
|
||||||
let mut h = History::builder()
|
struct BadDrift(f64);
|
||||||
.mu(25.0)
|
|
||||||
.sigma(25.0 / 3.0)
|
impl trueskill_tt::Drift<i64> for BadDrift {
|
||||||
.beta(25.0 / 6.0)
|
fn variance_delta(&self, _from: &i64, _to: &i64) -> f64 {
|
||||||
.drift(ConstantDrift(gamma))
|
self.0
|
||||||
.build();
|
}
|
||||||
|
fn variance_for_elapsed(&self, _elapsed: i64) -> f64 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for bad in [f64::NAN, f64::INFINITY, -1.0] {
|
||||||
|
let mut h = History::builder().drift(BadDrift(bad)).build();
|
||||||
h.record_winner(&"a", &"b", 1).unwrap();
|
h.record_winner(&"a", &"b", 1).unwrap();
|
||||||
h.record_winner(&"a", &"b", 5).unwrap();
|
h.record_winner(&"a", &"b", 5).unwrap();
|
||||||
let err = h.converge().unwrap_err();
|
let err = h.converge().unwrap_err();
|
||||||
@@ -324,7 +357,7 @@ mod constructor_parameters {
|
|||||||
..
|
..
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
"gamma {gamma}: {err:?}"
|
"drift {bad}: {err:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,7 +366,7 @@ mod constructor_parameters {
|
|||||||
#[test]
|
#[test]
|
||||||
fn an_ordinary_drift_still_converges() {
|
fn an_ordinary_drift_still_converges() {
|
||||||
let mut h = History::builder()
|
let mut h = History::builder()
|
||||||
.drift(ConstantDrift(25.0 / 300.0))
|
.drift(ConstantDrift::new(25.0 / 300.0))
|
||||||
.build();
|
.build();
|
||||||
h.record_winner(&"a", &"b", 1).unwrap();
|
h.record_winner(&"a", &"b", 1).unwrap();
|
||||||
h.record_winner(&"a", &"b", 5).unwrap();
|
h.record_winner(&"a", &"b", 5).unwrap();
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ fn fit(extra: Option<Event<i64, &'static str>>, policy: UnknownKeys) -> H {
|
|||||||
.sigma(6.0)
|
.sigma(6.0)
|
||||||
.beta(1.0)
|
.beta(1.0)
|
||||||
.score_sigma(2.0)
|
.score_sigma(2.0)
|
||||||
.drift(ConstantDrift(0.0))
|
.drift(ConstantDrift::new(0.0))
|
||||||
.unknown_keys(policy)
|
.unknown_keys(policy)
|
||||||
.convergence(ConvergenceOptions {
|
.convergence(ConvergenceOptions {
|
||||||
max_iter: 20_000,
|
max_iter: 20_000,
|
||||||
|
|||||||
Reference in New Issue
Block a user