fix(evidence): accumulate in log space and floor the per-link value
Per-link evidence was multiplied in linear space and logged only at the end. Each link contributes a probability in (0, 1], so the product over an n-team game decays geometrically: around a thousand links it flushes to exactly 0.0 and `ln(0.0)` is `-inf`, which then propagates through the sum in `History::log_evidence_internal` and takes the whole history with it. `Game::free_for_all` builds one team per player, so this is reachable at the competitor counts the T3 benchmarks target. `Game`, `OwnedGame`, and `time_slice::Event` now carry `log_evidence` directly, summed over links rather than multiplied then logged. The cached per-link evidence is also floored at `f64::MIN_POSITIVE`. It could legitimately reach zero or go negative: `1.0 - cdf(..)` rounds to zero for a near-certain outcome, and the `erfc` approximation carries ~1e-7 error so `cdf` can exceed 1.0 and make the difference negative — `ln` of which is NaN. Existing log-evidence goldens are unchanged, confirming the accumulation is numerically equivalent in the range where the old form worked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnsaJg74eNSva3PJjK2eej
This commit is contained in:
+28
-26
@@ -87,7 +87,7 @@ struct Team {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Event {
|
||||
teams: Vec<Team>,
|
||||
evidence: f64,
|
||||
log_evidence: f64,
|
||||
weights: Vec<Vec<f64>>,
|
||||
kind: EventKind,
|
||||
}
|
||||
@@ -161,7 +161,7 @@ impl Event {
|
||||
}
|
||||
}
|
||||
|
||||
self.evidence = g.evidence;
|
||||
self.log_evidence = g.log_evidence;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ impl<T: Time> TimeSlice<T> {
|
||||
|
||||
Event {
|
||||
teams,
|
||||
evidence: 0.0,
|
||||
log_evidence: 0.0,
|
||||
weights,
|
||||
kind: kinds[e],
|
||||
}
|
||||
@@ -353,7 +353,7 @@ impl<T: Time> TimeSlice<T> {
|
||||
}
|
||||
}
|
||||
|
||||
event.evidence = g.evidence;
|
||||
event.log_evidence = g.log_evidence;
|
||||
}
|
||||
} else {
|
||||
self.sweep_color_groups(agents);
|
||||
@@ -530,26 +530,28 @@ impl<T: Time> TimeSlice<T> {
|
||||
let teams = event.within_priors(online, forward, &self.skills, agents);
|
||||
let result = event.outputs();
|
||||
match event.kind {
|
||||
EventKind::Ranked => Game::ranked_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&event.weights,
|
||||
self.p_draw,
|
||||
self.convergence,
|
||||
arena,
|
||||
)
|
||||
.evidence
|
||||
.ln(),
|
||||
EventKind::Scored { score_sigma } => Game::scored_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&event.weights,
|
||||
score_sigma,
|
||||
self.convergence,
|
||||
arena,
|
||||
)
|
||||
.evidence
|
||||
.ln(),
|
||||
EventKind::Ranked => {
|
||||
Game::ranked_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&event.weights,
|
||||
self.p_draw,
|
||||
self.convergence,
|
||||
arena,
|
||||
)
|
||||
.log_evidence
|
||||
}
|
||||
EventKind::Scored { score_sigma } => {
|
||||
Game::scored_with_arena(
|
||||
teams,
|
||||
&result,
|
||||
&event.weights,
|
||||
score_sigma,
|
||||
self.convergence,
|
||||
arena,
|
||||
)
|
||||
.log_evidence
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -560,7 +562,7 @@ impl<T: Time> TimeSlice<T> {
|
||||
.map(|event| run_event(event, &mut arena))
|
||||
.sum()
|
||||
} else {
|
||||
self.events.iter().map(|event| event.evidence.ln()).sum()
|
||||
self.events.iter().map(|event| event.log_evidence).sum()
|
||||
}
|
||||
} else if online || forward {
|
||||
self.events
|
||||
@@ -584,7 +586,7 @@ impl<T: Time> TimeSlice<T> {
|
||||
.flat_map(|team| &team.items)
|
||||
.any(|item| targets.contains(&item.agent))
|
||||
})
|
||||
.map(|event| event.evidence.ln())
|
||||
.map(|event| event.log_evidence)
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user