refactor!: remove the factor-graph surface nothing used, add try_winner
Two decisions taken before cutting 0.5.0. #42 — the `Schedule` trait was public API the engine never called. Its only call site was `Game::custom`, itself `#[doc(hidden)]`, and `EpsilonOrMax` was never constructed anywhere. Removing that surface showed the problem was larger than the issue described: with `custom` gone, the compiler found `Factor`, `BuiltinFactor`, `RankDiffFactor` and `TeamSumFactor` all dead too. `Game::run_chain` drives a local `DiffFactor` enum and bypasses the whole T1 abstraction — it has done since it was written. So this is not just an unused extension point but the machinery it was built on, and `CLAUDE.md` was documenting it as live architecture. Removed: `graph` module, `Schedule`, `EpsilonOrMax`, `ScheduleReport`, `Game::custom`, `Factor`, `BuiltinFactor`, `RankDiffFactor`, `TeamSumFactor`. `TruncFactor`, `MarginFactor`, `VarStore` and `VarId` stay — inference uses those. The measurement behind choosing removal over wiring is in #42: the within-game loop converges in 1 to 8 iterations against a cap of 30, so a `Residual` schedule has no headroom to reclaim, and `Damped` already shipped as `ConvergenceOptions::alpha`. #20 — `Outcome::winner` panicking on an out-of-range index. Kept, and the reasoning is now on the method. It is the only constructor here that validates, which looks inconsistent until you try deferring like its siblings: `winner(5, 2)` produces ranks `[1, 1]`, an all-tied draw that ingestion accepts without complaint when `p_draw > 0`. Asking "team 5 won" and silently getting "everyone drew" is the exact failure this crate keeps removing, so the check belongs where the mistake is. Adds `Outcome::try_winner` for indices that are computed or parsed rather than written literally, following the `new`/`try_new` convention. That is additive; the panicking form stays because every call site in this repo, its tests and its README passes literals, where a `?` would be noise. BREAKING CHANGE: the `graph` module and everything it exported are removed, as are `Game::custom` and `ScheduleReport`. Closes #42. Closes #20. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
This commit is contained in:
+32
-3
@@ -34,12 +34,41 @@ impl Outcome {
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `winner >= n`.
|
||||
/// Panics if `winner >= n`. Use [`Outcome::try_winner`] when the index
|
||||
/// comes from data rather than a literal.
|
||||
///
|
||||
/// This is the one constructor here that validates, and deliberately so.
|
||||
/// Its siblings build freely and let ingestion reject what it cannot use,
|
||||
/// which works because a malformed rank vector stays recognisable. An
|
||||
/// out-of-range winner does not: `winner(5, 2)` would produce ranks
|
||||
/// `[1, 1]`, an all-tied draw that ingestion accepts without complaint when
|
||||
/// `p_draw > 0`. Asking "team 5 won" and silently getting "everyone drew"
|
||||
/// is exactly the class of quiet wrong answer this crate keeps removing, so
|
||||
/// the check happens here where the mistake is.
|
||||
#[must_use]
|
||||
pub fn winner(winner: u32, n: u32) -> Self {
|
||||
assert!(winner < n, "winner index {winner} out of range 0..{n}");
|
||||
Self::try_winner(winner, n)
|
||||
.unwrap_or_else(|_| panic!("winner index {winner} out of range 0..{n}"))
|
||||
}
|
||||
|
||||
/// `n`-team outcome where team `winner` won, or an error if `winner` is not
|
||||
/// a valid team index.
|
||||
///
|
||||
/// The fallible form of [`Outcome::winner`], for when the index is computed
|
||||
/// or parsed rather than written literally.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `InvalidParameter` if `winner >= n`.
|
||||
pub fn try_winner(winner: u32, n: u32) -> Result<Self, crate::InferenceError> {
|
||||
if winner >= n {
|
||||
return Err(crate::InferenceError::InvalidParameter {
|
||||
name: "winner",
|
||||
value: f64::from(winner),
|
||||
});
|
||||
}
|
||||
let ranks: SmallVec<[u32; 4]> = (0..n).map(|i| if i == winner { 0 } else { 1 }).collect();
|
||||
Self::Ranked(ranks)
|
||||
Ok(Self::Ranked(ranks))
|
||||
}
|
||||
|
||||
/// All `n` teams tied.
|
||||
|
||||
Reference in New Issue
Block a user