From 47cb1d5f79eb73e1086dbfdadcb8e2d92bbc3d27 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Wed, 13 May 2026 19:49:52 -0600 Subject: [PATCH] test(repair): pin ProjectToSimplex argmax tie-breaking and position MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The degenerate-magnitude shortcut in ProjectToSimplex::repair scans `decision` for the argmax and concentrates all mass there. cargo mutants found that the strict-greater scan was unpinned: replacing `>` with `>=` (which would shift the argmax to the last tied index) and `>` with `==` (which would silently skip larger values further along) both survived. Two tests: - A 3-element vector with two tied maxima at the front pins that the scan keeps the first index on a tie. - A 3-element vector whose argmax is at index 1 pins that the scan actually walks past the start when later values are larger. Other mutants in this file (the `>` ↔ `>=` threshold check at line 106, the `*` ↔ `+` in the threshold constant, the `-` ↔ `+` / `/` in the tau-fallback initializer, and the `>` ↔ `>=` in the projection loop's rho update) are equivalent mutants for non-pathological inputs: the normal-path and shortcut-path math converge to the same projection result for any input the operator is documented to handle. Leaving them in the residue. --- src/operators/repair.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/operators/repair.rs b/src/operators/repair.rs index cc26f7a..4969f39 100644 --- a/src/operators/repair.rs +++ b/src/operators/repair.rs @@ -249,4 +249,32 @@ mod tests { assert!(approx_eq(v, 0.25, 1e-12)); } } + + // ---- Mutation-test coverage for ProjectToSimplex ---------------------- + // + // The degenerate-magnitude shortcut concentrates mass on argmax(x). The + // next two tests pin the *position* of that argmax precisely. + + /// The shortcut picks the **first** index on a tie. Strict `>` keeps + /// the earlier index; `>=` would overwrite with the later equal index. + /// Kills `> → >=` in the argmax scan. + #[test] + fn project_extreme_magnitudes_keeps_first_index_on_tie() { + let mut r = ProjectToSimplex::new(1.0); + let mut x = vec![1e20, 1e20, -1e20]; + r.repair(&mut x); + assert_eq!(x, vec![1.0, 0.0, 0.0]); + } + + /// The shortcut finds the argmax at a non-zero index. With `> → ==` + /// the scan stops updating because `1e20 == -1e20` is false at i=1 + /// and the argmax stays at 0 — but the true argmax is at index 1. + /// Kills `> → ==` in the argmax scan. + #[test] + fn project_extreme_magnitudes_finds_argmax_at_non_zero_index() { + let mut r = ProjectToSimplex::new(1.0); + let mut x = vec![-1e20, 1e20, 5e19]; + r.repair(&mut x); + assert_eq!(x, vec![0.0, 1.0, 0.0]); + } }