refactor: dedupe Game::likelihoods and likelihoods_scored via run_chain
Both methods were 95-line near-duplicates differing only in the closure that builds the per-diff DiffFactor. Extract the shared body as a private run_chain<F>(&self, arena, make_link) helper that returns (evidence, likelihoods); the two callers shrink to ~10 lines each. Pure code-shape change: posteriors and evidence remain bit-equal; all existing tests (lib + integration) pass unchanged.
This commit is contained in:
+35
-139
@@ -233,12 +233,14 @@ impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn likelihoods(&mut self, arena: &mut ScratchArena) {
|
fn run_chain<F>(&self, arena: &mut ScratchArena, mut make_link: F) -> (f64, Vec<Vec<Gaussian>>)
|
||||||
|
where
|
||||||
|
F: FnMut(usize, &[usize], &mut crate::factor::VarStore) -> DiffFactor,
|
||||||
|
{
|
||||||
arena.reset();
|
arena.reset();
|
||||||
|
|
||||||
let n_teams = self.teams.len();
|
let n_teams = self.teams.len();
|
||||||
|
|
||||||
// Sort teams by result descending; reuse arena.sort_buf to avoid allocation.
|
|
||||||
arena.sort_buf.extend(0..n_teams);
|
arena.sort_buf.extend(0..n_teams);
|
||||||
arena.sort_buf.sort_by(|&i, &j| {
|
arena.sort_buf.sort_by(|&i, &j| {
|
||||||
self.result[j]
|
self.result[j]
|
||||||
@@ -246,7 +248,6 @@ impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
|||||||
.unwrap_or(Ordering::Equal)
|
.unwrap_or(Ordering::Equal)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Team performance priors written into arena buffer (capacity reused across games).
|
|
||||||
arena.team_prior.extend(arena.sort_buf.iter().map(|&t| {
|
arena.team_prior.extend(arena.sort_buf.iter().map(|&t| {
|
||||||
self.teams[t]
|
self.teams[t]
|
||||||
.iter()
|
.iter()
|
||||||
@@ -256,30 +257,10 @@ impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
|||||||
|
|
||||||
let n_diffs = n_teams.saturating_sub(1);
|
let n_diffs = n_teams.saturating_sub(1);
|
||||||
|
|
||||||
// One DiffFactor per adjacent sorted-team pair; each owns a diff VarId.
|
|
||||||
// links stays local (fresh state per game; Vec capacity is typically small).
|
|
||||||
let mut links: Vec<DiffFactor> = (0..n_diffs)
|
let mut links: Vec<DiffFactor> = (0..n_diffs)
|
||||||
.map(|i| {
|
.map(|i| make_link(i, &arena.sort_buf, &mut arena.vars))
|
||||||
let tie = self.result[arena.sort_buf[i]] == self.result[arena.sort_buf[i + 1]];
|
|
||||||
let margin = if self.p_draw == 0.0 {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
let a: f64 = self.teams[arena.sort_buf[i]]
|
|
||||||
.iter()
|
|
||||||
.map(|p| p.beta.powi(2))
|
|
||||||
.sum();
|
|
||||||
let b: f64 = self.teams[arena.sort_buf[i + 1]]
|
|
||||||
.iter()
|
|
||||||
.map(|p| p.beta.powi(2))
|
|
||||||
.sum();
|
|
||||||
compute_margin(self.p_draw, (a + b).sqrt())
|
|
||||||
};
|
|
||||||
let vid = arena.vars.alloc(N_INF);
|
|
||||||
DiffFactor::Trunc(TruncFactor::new(vid, margin, tie))
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Per-team messages from neighbouring RankDiff factors (replaces TeamMessage).
|
|
||||||
arena.lhood_lose.resize(n_teams, N_INF);
|
arena.lhood_lose.resize(n_teams, N_INF);
|
||||||
arena.lhood_win.resize(n_teams, N_INF);
|
arena.lhood_win.resize(n_teams, N_INF);
|
||||||
|
|
||||||
@@ -289,7 +270,6 @@ impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
|||||||
while tuple_gt(step, 1e-6) && iter < 10 {
|
while tuple_gt(step, 1e-6) && iter < 10 {
|
||||||
step = (0.0_f64, 0.0_f64);
|
step = (0.0_f64, 0.0_f64);
|
||||||
|
|
||||||
// Forward sweep: diffs 0 .. n_diffs-2 (all but the last).
|
|
||||||
for (e, lf) in links[..n_diffs.saturating_sub(1)].iter_mut().enumerate() {
|
for (e, lf) in links[..n_diffs.saturating_sub(1)].iter_mut().enumerate() {
|
||||||
let pw = arena.team_prior[e] * arena.lhood_lose[e];
|
let pw = arena.team_prior[e] * arena.lhood_lose[e];
|
||||||
let pl = arena.team_prior[e + 1] * arena.lhood_win[e + 1];
|
let pl = arena.team_prior[e + 1] * arena.lhood_win[e + 1];
|
||||||
@@ -303,7 +283,6 @@ impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
|||||||
arena.lhood_lose[e + 1] = new_ll;
|
arena.lhood_lose[e + 1] = new_ll;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backward sweep: diffs n_diffs-1 .. 1 (reverse, all but the first).
|
|
||||||
for (rev_i, lf) in links[1..].iter_mut().rev().enumerate() {
|
for (rev_i, lf) in links[1..].iter_mut().rev().enumerate() {
|
||||||
let e = n_diffs - 1 - rev_i;
|
let e = n_diffs - 1 - rev_i;
|
||||||
let pw = arena.team_prior[e] * arena.lhood_lose[e];
|
let pw = arena.team_prior[e] * arena.lhood_lose[e];
|
||||||
@@ -337,8 +316,7 @@ impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
|||||||
arena.lhood_lose[n_teams - 1] = pw_last - links[n_diffs - 1].msg();
|
arena.lhood_lose[n_teams - 1] = pw_last - links[n_diffs - 1].msg();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evidence = product of per-diff evidences (each cached on first propagation).
|
let evidence: f64 = links.iter().map(|l| l.evidence()).product();
|
||||||
self.evidence = links.iter().map(|l| l.evidence()).product();
|
|
||||||
|
|
||||||
// Inverse permutation: inv_buf[orig_i] = sorted_i.
|
// Inverse permutation: inv_buf[orig_i] = sorted_i.
|
||||||
arena.inv_buf.resize(n_teams, 0);
|
arena.inv_buf.resize(n_teams, 0);
|
||||||
@@ -346,7 +324,7 @@ impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
|||||||
arena.inv_buf[orig_i] = si;
|
arena.inv_buf[orig_i] = si;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.likelihoods = self
|
let likelihoods = self
|
||||||
.teams
|
.teams
|
||||||
.iter()
|
.iter()
|
||||||
.zip(self.weights.iter())
|
.zip(self.weights.iter())
|
||||||
@@ -368,120 +346,38 @@ impl<'a, T: Time, D: Drift<T>> Game<'a, T, D> {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
(evidence, likelihoods)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn likelihoods(&mut self, arena: &mut ScratchArena) {
|
||||||
|
let (evidence, likelihoods) = self.run_chain(arena, |i, sort_buf, vars| {
|
||||||
|
let tie = self.result[sort_buf[i]] == self.result[sort_buf[i + 1]];
|
||||||
|
let margin = if self.p_draw == 0.0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
let a: f64 = self.teams[sort_buf[i]].iter().map(|p| p.beta.powi(2)).sum();
|
||||||
|
let b: f64 = self.teams[sort_buf[i + 1]]
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.beta.powi(2))
|
||||||
|
.sum();
|
||||||
|
compute_margin(self.p_draw, (a + b).sqrt())
|
||||||
|
};
|
||||||
|
let vid = vars.alloc(N_INF);
|
||||||
|
DiffFactor::Trunc(TruncFactor::new(vid, margin, tie))
|
||||||
|
});
|
||||||
|
self.evidence = evidence;
|
||||||
|
self.likelihoods = likelihoods;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn likelihoods_scored(&mut self, arena: &mut ScratchArena, score_sigma: f64) {
|
fn likelihoods_scored(&mut self, arena: &mut ScratchArena, score_sigma: f64) {
|
||||||
arena.reset();
|
let (evidence, likelihoods) = self.run_chain(arena, |i, sort_buf, vars| {
|
||||||
|
let m_obs = self.result[sort_buf[i]] - self.result[sort_buf[i + 1]];
|
||||||
let n_teams = self.teams.len();
|
let vid = vars.alloc(N_INF);
|
||||||
|
|
||||||
arena.sort_buf.extend(0..n_teams);
|
|
||||||
arena.sort_buf.sort_by(|&i, &j| {
|
|
||||||
self.result[j]
|
|
||||||
.partial_cmp(&self.result[i])
|
|
||||||
.unwrap_or(Ordering::Equal)
|
|
||||||
});
|
|
||||||
|
|
||||||
arena.team_prior.extend(arena.sort_buf.iter().map(|&t| {
|
|
||||||
self.teams[t]
|
|
||||||
.iter()
|
|
||||||
.zip(self.weights[t].iter())
|
|
||||||
.fold(N00, |p, (player, &w)| p + (player.performance() * w))
|
|
||||||
}));
|
|
||||||
|
|
||||||
let n_diffs = n_teams.saturating_sub(1);
|
|
||||||
|
|
||||||
let mut links: Vec<DiffFactor> = (0..n_diffs)
|
|
||||||
.map(|i| {
|
|
||||||
// After descending-by-score sort, m_obs >= 0 for every adjacent pair.
|
|
||||||
let m_obs = self.result[arena.sort_buf[i]] - self.result[arena.sort_buf[i + 1]];
|
|
||||||
let vid = arena.vars.alloc(N_INF);
|
|
||||||
DiffFactor::Margin(MarginFactor::new(vid, m_obs, score_sigma))
|
DiffFactor::Margin(MarginFactor::new(vid, m_obs, score_sigma))
|
||||||
})
|
});
|
||||||
.collect();
|
self.evidence = evidence;
|
||||||
|
self.likelihoods = likelihoods;
|
||||||
arena.lhood_lose.resize(n_teams, N_INF);
|
|
||||||
arena.lhood_win.resize(n_teams, N_INF);
|
|
||||||
|
|
||||||
let mut step = (f64::INFINITY, f64::INFINITY);
|
|
||||||
let mut iter = 0;
|
|
||||||
|
|
||||||
while tuple_gt(step, 1e-6) && iter < 10 {
|
|
||||||
step = (0.0_f64, 0.0_f64);
|
|
||||||
|
|
||||||
for (e, lf) in links[..n_diffs.saturating_sub(1)].iter_mut().enumerate() {
|
|
||||||
let pw = arena.team_prior[e] * arena.lhood_lose[e];
|
|
||||||
let pl = arena.team_prior[e + 1] * arena.lhood_win[e + 1];
|
|
||||||
let raw = pw - pl;
|
|
||||||
arena.vars.set(lf.diff(), raw * lf.msg());
|
|
||||||
let d = lf.propagate(&mut arena.vars);
|
|
||||||
step = tuple_max(step, d);
|
|
||||||
|
|
||||||
let new_ll = pw - lf.msg();
|
|
||||||
step = tuple_max(step, arena.lhood_lose[e + 1].delta(new_ll));
|
|
||||||
arena.lhood_lose[e + 1] = new_ll;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (rev_i, lf) in links[1..].iter_mut().rev().enumerate() {
|
|
||||||
let e = n_diffs - 1 - rev_i;
|
|
||||||
let pw = arena.team_prior[e] * arena.lhood_lose[e];
|
|
||||||
let pl = arena.team_prior[e + 1] * arena.lhood_win[e + 1];
|
|
||||||
let raw = pw - pl;
|
|
||||||
arena.vars.set(lf.diff(), raw * lf.msg());
|
|
||||||
let d = lf.propagate(&mut arena.vars);
|
|
||||||
step = tuple_max(step, d);
|
|
||||||
|
|
||||||
let new_lw = pl + lf.msg();
|
|
||||||
step = tuple_max(step, arena.lhood_win[e].delta(new_lw));
|
|
||||||
arena.lhood_win[e] = new_lw;
|
|
||||||
}
|
|
||||||
|
|
||||||
iter += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if n_diffs == 1 {
|
|
||||||
let raw = (arena.team_prior[0] * arena.lhood_lose[0])
|
|
||||||
- (arena.team_prior[1] * arena.lhood_win[1]);
|
|
||||||
arena.vars.set(links[0].diff(), raw * links[0].msg());
|
|
||||||
links[0].propagate(&mut arena.vars);
|
|
||||||
}
|
|
||||||
|
|
||||||
if n_diffs > 0 {
|
|
||||||
let pl1 = arena.team_prior[1] * arena.lhood_win[1];
|
|
||||||
arena.lhood_win[0] = pl1 + links[0].msg();
|
|
||||||
let pw_last = arena.team_prior[n_teams - 2] * arena.lhood_lose[n_teams - 2];
|
|
||||||
arena.lhood_lose[n_teams - 1] = pw_last - links[n_diffs - 1].msg();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.evidence = links.iter().map(|l| l.evidence()).product();
|
|
||||||
|
|
||||||
arena.inv_buf.resize(n_teams, 0);
|
|
||||||
for (si, &orig_i) in arena.sort_buf.iter().enumerate() {
|
|
||||||
arena.inv_buf[orig_i] = si;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.likelihoods = self
|
|
||||||
.teams
|
|
||||||
.iter()
|
|
||||||
.zip(self.weights.iter())
|
|
||||||
.enumerate()
|
|
||||||
.map(|(orig_i, (players, weights))| {
|
|
||||||
let si = arena.inv_buf[orig_i];
|
|
||||||
let m = arena.lhood_win[si] * arena.lhood_lose[si];
|
|
||||||
let performance = players
|
|
||||||
.iter()
|
|
||||||
.zip(weights.iter())
|
|
||||||
.fold(N00, |p, (player, &w)| p + (player.performance() * w));
|
|
||||||
players
|
|
||||||
.iter()
|
|
||||||
.zip(weights.iter())
|
|
||||||
.map(|(player, &w)| {
|
|
||||||
((m - performance.exclude(player.performance() * w)) * (1.0 / w))
|
|
||||||
.forget(player.beta.powi(2))
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn posteriors(&self) -> Vec<Vec<Gaussian>> {
|
pub fn posteriors(&self) -> Vec<Vec<Gaussian>> {
|
||||||
|
|||||||
Reference in New Issue
Block a user