feat(api): add current_skill / learning_curve / log_evidence / predict_*
New public query methods on History: - current_skill(&K) -> Option<Gaussian>: latest posterior for a key - learning_curve(&K) -> Vec<(T, Gaussian)>: single-key history - learning_curves() -> HashMap<K, Vec<(T, Gaussian)>>: all-keys history - log_evidence() -> f64: total log-evidence (was log_evidence(false,&[])) - log_evidence_for(&[&K]) -> f64: subset log-evidence - predict_quality(&[&[&K]]) -> f64: draw-probability match quality - predict_outcome(&[&[&K]]) -> Vec<f64>: 2-team win probabilities learning_curves() changed from returning HashMap<Index, Vec<(i64, Gaussian)>> to HashMap<K, Vec<(T, Gaussian)>>. A new learning_curves_by_index() helper preserves the old Index-keyed shape for callers that ingest via the pub(crate) Index path. log_evidence(false, &[]) was renamed to log_evidence_internal and made pub(crate); the new zero-arg log_evidence() wraps it. predict_outcome is T2 2-team-only; N-team deferred to T4. KeyTable::get no longer requires ToOwned<Owned = K> (only needed for get_or_create), allowing query methods to use simpler bounds. Part of T2 of docs/superpowers/specs/2026-04-23-trueskill-engine-redesign-design.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -142,3 +142,84 @@ fn fluent_event_builder_draw() {
|
||||
.unwrap();
|
||||
h.converge().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_skill_and_learning_curve() {
|
||||
use trueskill_tt::History;
|
||||
let mut h = History::builder()
|
||||
.mu(25.0)
|
||||
.sigma(25.0 / 3.0)
|
||||
.beta(25.0 / 6.0)
|
||||
.p_draw(0.0)
|
||||
.build();
|
||||
h.record_winner(&"a", &"b", 1).unwrap();
|
||||
h.record_winner(&"a", &"b", 2).unwrap();
|
||||
h.converge().unwrap();
|
||||
|
||||
let a = h.current_skill(&"a").unwrap();
|
||||
assert!(a.mu() > 25.0);
|
||||
let b = h.current_skill(&"b").unwrap();
|
||||
assert!(b.mu() < 25.0);
|
||||
|
||||
let a_curve = h.learning_curve(&"a");
|
||||
assert_eq!(a_curve.len(), 2);
|
||||
assert_eq!(a_curve[0].0, 1);
|
||||
assert_eq!(a_curve[1].0, 2);
|
||||
|
||||
let all = h.learning_curves();
|
||||
assert_eq!(all.len(), 2);
|
||||
assert!(all.contains_key("a"));
|
||||
assert!(all.contains_key("b"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn log_evidence_total_vs_subset() {
|
||||
use trueskill_tt::{ConstantDrift, History};
|
||||
let mut h = History::builder()
|
||||
.mu(0.0)
|
||||
.sigma(6.0)
|
||||
.beta(1.0)
|
||||
.p_draw(0.0)
|
||||
.drift(ConstantDrift(0.0))
|
||||
.build();
|
||||
h.record_winner(&"a", &"b", 1).unwrap();
|
||||
h.record_winner(&"b", &"a", 2).unwrap();
|
||||
let total = h.log_evidence();
|
||||
let a_only = h.log_evidence_for(&[&"a"]);
|
||||
assert!(total.is_finite());
|
||||
assert!(a_only.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predict_quality_two_teams() {
|
||||
use trueskill_tt::History;
|
||||
let mut h = History::builder()
|
||||
.mu(25.0)
|
||||
.sigma(25.0 / 3.0)
|
||||
.beta(25.0 / 6.0)
|
||||
.p_draw(0.0)
|
||||
.build();
|
||||
h.record_winner(&"a", &"b", 1).unwrap();
|
||||
h.converge().unwrap();
|
||||
|
||||
let q = h.predict_quality(&[&[&"a"], &[&"b"]]);
|
||||
assert!(q > 0.0 && q <= 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predict_outcome_two_teams_sums_to_one() {
|
||||
use trueskill_tt::History;
|
||||
let mut h = History::builder()
|
||||
.mu(25.0)
|
||||
.sigma(25.0 / 3.0)
|
||||
.beta(25.0 / 6.0)
|
||||
.p_draw(0.0)
|
||||
.build();
|
||||
h.record_winner(&"a", &"b", 1).unwrap();
|
||||
h.converge().unwrap();
|
||||
|
||||
let p = h.predict_outcome(&[&[&"a"], &[&"b"]]);
|
||||
assert_eq!(p.len(), 2);
|
||||
assert!((p[0] + p[1] - 1.0).abs() < 1e-9);
|
||||
assert!(p[0] > p[1]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user