Make MIS → KingsSubgraph reduction deterministic (#1061)#1063
Open
Make MIS → KingsSubgraph reduction deterministic (#1061)#1063
Conversation
Two compounding sources of non-determinism in the greedy path decomposition were making `pred reduce` vary ~15-20% in target-graph size across runs: - `greedy_step` used `rand::rng()` (unseeded thread-local RNG) for tie-breaking. - `AdjList` was `Vec<HashSet<usize>>`; per-process HashSet iteration order leaked into `layout.neighbors` via pushes in `vsep_updated_neighbors`, so even a fixed RNG seed would still drift. Fix: - Switch `AdjList` to `Vec<BTreeSet<usize>>` so iteration is sorted. - Thread a seeded `SmallRng` through `greedy_step` / `greedy_decompose` / `pathwidth`, using `DEFAULT_PATHWIDTH_SEED = 0` for the public entry point. - Expose `pathwidth_with_seed(.., seed)` as an escape hatch for callers that want to sample the layout space (e.g. future minimum-atom search in #1062). Three CLI invocations of `pred reduce` on a 64-vertex MIS now produce byte-identical bundles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The triangular mapping shares the same `pathwidth` call as KSG, so it had the same non-determinism before this PR. Pin the behavior with an equivalent regression test so future changes to pathdecomposition can't silently regress this mapping either. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1063 +/- ##
========================================
Coverage 97.92% 97.92%
========================================
Files 966 966
Lines 100043 100153 +110
========================================
+ Hits 97967 98076 +109
- Misses 2076 2077 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1061.
pred reduceoutput forMIS<SimpleGraph, One> → MIS<KingsSubgraph, One>now produces byte-identical bundles across repeated invocations on the same input. Threepred reduceruns on a 64-vertex MIS used to vary ~15–20 % in atom/edge count; they now match exactly.Root cause
Two compounding sources of non-determinism in
src/rules/unitdiskmapping/pathdecomposition.rs:greedy_stepusedrand::rng()(thread-local, OS-seeded) to pick among tied candidates. Compounded bypathwidth's 10 random restarts for >30-vertex inputs.AdjListwasVec<HashSet<usize>>. Whenvsep_updated_neighborsdidfor &w in &adj[v] { nbs.push(w); }, per-process HashSet iteration order leaked intolayout.neighbors, which then fed the next greedy step. Seeding the RNG alone was not enough (I hit this: same seed, different output — which is how I found this second source).Fix
AdjListfromVec<HashSet<usize>>toVec<BTreeSet<usize>>so iteration is deterministic (sorted).SmallRngthroughgreedy_step/greedy_decompose/pathwidth, seeded from newDEFAULT_PATHWIDTH_SEED = 0so the public API is reproducible by default.pathwidth_with_seed(.., seed)as an escape hatch for callers that want to sample the layout space. SingleSmallRngthreads through all 10 restarts so each restart remains diverse (RNG state progresses) while overall output is reproducible given the seed.reduce_to()stays a pure function — no env vars, no global state, no trait changes.Non-goals (tracked separately)
--seedflag onpred reduce— would require plumbing an optimization hyperparameter throughReduceTo::reduce_to(&self); out of scope for a determinism fix.Tests
Three new tests (all passing):
test_pathwidth_greedy_is_deterministic— 64-vertex grid graph, 4pathwidthcalls must yield identical vertex orderings and vsep.test_pathwidth_with_seed_varies_output— confirms seed actually threads into tie-breaking (same seed → same output; at least one seed in 1..=10 → different output).test_mis_simple_one_to_kings_one_is_deterministic_on_large_graph— end-to-end regression for the reporter's 64-vertex scenario; 4reduce_tocalls must agree on targetnum_verticesandnum_edges.Full suite: 4960 library tests + 316 CLI tests pass.
cargo clippyandcargo fmt --checkclean.Test plan
cargo test --lib(4960 pass, 0 fail)cargo test -p problemreductions-cli(316 pass, 0 fail)cargo clippy --lib --all-featurescleancargo fmt --checkcleanpred reduceon 64-vertex MIS produce byte-identicaldiff-empty bundles🤖 Generated with Claude Code