test(history): end-to-end per-event score_sigma override tests
Three integration tests on a 2-team scored event: - inheritance: Outcome::scores(...) with no override produces bit-equal posteriors to the same outcome wrapped in scores_with_sigma(scores, history.score_sigma) - override-supersedes-default: scores_with_sigma(scores, X) with history score_sigma(Y) produces bit-equal posteriors to scores(...) with history score_sigma(X), AND differs measurably from scores(...) with history score_sigma(Y) - builder threading: EventBuilder::scores_with_sigma reaches the ingest path identically to the Outcome constructor
This commit is contained in:
+149
@@ -1812,4 +1812,153 @@ mod tests {
|
||||
"α=0.5 should reach the same fixed point as α=1.0; max_diff={max_diff}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outcome_scores_default_sigma_uses_history_default() {
|
||||
use crate::Outcome;
|
||||
|
||||
// Path A: explicit sigma=0.5 via override.
|
||||
let mut h_a = crate::History::builder().score_sigma(0.5).build();
|
||||
h_a.add_events([crate::Event {
|
||||
time: 0_i64,
|
||||
teams: smallvec::smallvec![
|
||||
crate::Team::with_members([crate::Member::new("a")]),
|
||||
crate::Team::with_members([crate::Member::new("b")]),
|
||||
],
|
||||
outcome: Outcome::scores_with_sigma([3.0, 1.0], 0.5),
|
||||
}])
|
||||
.unwrap();
|
||||
h_a.converge().unwrap();
|
||||
|
||||
// Path B: history-wide default 0.5, no per-event override.
|
||||
let mut h_b = crate::History::builder().score_sigma(0.5).build();
|
||||
h_b.add_events([crate::Event {
|
||||
time: 0_i64,
|
||||
teams: smallvec::smallvec![
|
||||
crate::Team::with_members([crate::Member::new("a")]),
|
||||
crate::Team::with_members([crate::Member::new("b")]),
|
||||
],
|
||||
outcome: Outcome::scores([3.0, 1.0]),
|
||||
}])
|
||||
.unwrap();
|
||||
h_b.converge().unwrap();
|
||||
|
||||
// Inheritance: posteriors must be bit-equal.
|
||||
let curves_a = h_a.learning_curves();
|
||||
let curves_b = h_b.learning_curves();
|
||||
for (key, a_pts) in curves_a.iter() {
|
||||
let b_pts = curves_b.get(key).expect("agent missing in path B");
|
||||
for (a, b) in a_pts.iter().zip(b_pts.iter()) {
|
||||
assert_eq!(a.1.pi(), b.1.pi(), "mismatch at agent {key:?}");
|
||||
assert_eq!(a.1.tau(), b.1.tau(), "mismatch at agent {key:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outcome_scores_with_sigma_overrides_history_default() {
|
||||
use crate::Outcome;
|
||||
|
||||
// Path A: history-wide default 0.5, per-event override 2.0.
|
||||
let mut h_a = crate::History::builder().score_sigma(0.5).build();
|
||||
h_a.add_events([crate::Event {
|
||||
time: 0_i64,
|
||||
teams: smallvec::smallvec![
|
||||
crate::Team::with_members([crate::Member::new("a")]),
|
||||
crate::Team::with_members([crate::Member::new("b")]),
|
||||
],
|
||||
outcome: Outcome::scores_with_sigma([3.0, 1.0], 2.0),
|
||||
}])
|
||||
.unwrap();
|
||||
h_a.converge().unwrap();
|
||||
|
||||
// Path B: history-wide default 2.0, no per-event override.
|
||||
let mut h_b = crate::History::builder().score_sigma(2.0).build();
|
||||
h_b.add_events([crate::Event {
|
||||
time: 0_i64,
|
||||
teams: smallvec::smallvec![
|
||||
crate::Team::with_members([crate::Member::new("a")]),
|
||||
crate::Team::with_members([crate::Member::new("b")]),
|
||||
],
|
||||
outcome: Outcome::scores([3.0, 1.0]),
|
||||
}])
|
||||
.unwrap();
|
||||
h_b.converge().unwrap();
|
||||
|
||||
// Override == default-set-to-the-override-value: bit-equal.
|
||||
let curves_a = h_a.learning_curves();
|
||||
let curves_b = h_b.learning_curves();
|
||||
for (key, a_pts) in curves_a.iter() {
|
||||
let b_pts = curves_b.get(key).expect("agent missing in path B");
|
||||
for (a, b) in a_pts.iter().zip(b_pts.iter()) {
|
||||
assert_eq!(a.1.pi(), b.1.pi(), "mismatch at agent {key:?}");
|
||||
assert_eq!(a.1.tau(), b.1.tau(), "mismatch at agent {key:?}");
|
||||
}
|
||||
}
|
||||
|
||||
// Path C: history-wide default 0.5, no override. Different sigma → different posteriors.
|
||||
let mut h_c = crate::History::builder().score_sigma(0.5).build();
|
||||
h_c.add_events([crate::Event {
|
||||
time: 0_i64,
|
||||
teams: smallvec::smallvec![
|
||||
crate::Team::with_members([crate::Member::new("a")]),
|
||||
crate::Team::with_members([crate::Member::new("b")]),
|
||||
],
|
||||
outcome: Outcome::scores([3.0, 1.0]),
|
||||
}])
|
||||
.unwrap();
|
||||
h_c.converge().unwrap();
|
||||
|
||||
let curves_c = h_c.learning_curves();
|
||||
let mut max_diff: f64 = 0.0;
|
||||
for (key, a_pts) in curves_a.iter() {
|
||||
let c_pts = curves_c.get(key).expect("agent missing in path C");
|
||||
for (a, c) in a_pts.iter().zip(c_pts.iter()) {
|
||||
max_diff = max_diff.max((a.1.mu() - c.1.mu()).abs());
|
||||
max_diff = max_diff.max((a.1.sigma() - c.1.sigma()).abs());
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
max_diff > 1e-6,
|
||||
"override should produce different posteriors from inherited default; max_diff={max_diff}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn event_builder_scores_with_sigma_threading() {
|
||||
use crate::Outcome;
|
||||
|
||||
// Path A: builder fluent API with sigma override.
|
||||
let mut h_a = crate::History::builder().score_sigma(0.5).build();
|
||||
h_a.event(0_i64)
|
||||
.team(["a"])
|
||||
.team(["b"])
|
||||
.scores_with_sigma([3.0, 1.0], 2.0)
|
||||
.commit()
|
||||
.unwrap();
|
||||
h_a.converge().unwrap();
|
||||
|
||||
// Path B: same outcome via the explicit Outcome constructor.
|
||||
let mut h_b = crate::History::builder().score_sigma(0.5).build();
|
||||
h_b.add_events([crate::Event {
|
||||
time: 0_i64,
|
||||
teams: smallvec::smallvec![
|
||||
crate::Team::with_members([crate::Member::new("a")]),
|
||||
crate::Team::with_members([crate::Member::new("b")]),
|
||||
],
|
||||
outcome: Outcome::scores_with_sigma([3.0, 1.0], 2.0),
|
||||
}])
|
||||
.unwrap();
|
||||
h_b.converge().unwrap();
|
||||
|
||||
let curves_a = h_a.learning_curves();
|
||||
let curves_b = h_b.learning_curves();
|
||||
for (key, a_pts) in curves_a.iter() {
|
||||
let b_pts = curves_b.get(key).expect("agent missing");
|
||||
for (a, b) in a_pts.iter().zip(b_pts.iter()) {
|
||||
assert_eq!(a.1.pi(), b.1.pi(), "mismatch at agent {key:?}");
|
||||
assert_eq!(a.1.tau(), b.1.tau(), "mismatch at agent {key:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user