Combinatorial TSP: reproducible under joblib(threads) — per-worker RNG streams (closes #117) - #119
Merged
Merged
Conversation
…#117) The combinatorial TSP solvers drew from the process-global, thread-unsafe np.random while dispatching ants/individuals via joblib.Parallel(prefer=threads), so a seeded run gave a different tour every execution at n_jobs>1 (and none at all under loky). The continuous stack already solved this with core.random's per-worker streams; the combinatorial solvers never adopted it. - Route every RNG draw in aco.py/ga.py/aco_mst.py through core.random.rng() (the thread-local stream), replacing all np.random.* calls. - Give each parallel task an independent, seed-derived stream: spawn_streams(n_jobs) once per generation, wrap each task body in use_stream(stream). Which numbers a task sees now depends on its index, not on scheduling. - GA-specific concurrency bug also fixed: _tournament_selection returned a VIEW into the shared genome, which the genetic operators mutate in place -- under threads that corrupts the array other workers are reading. Return a copy. Verified reproducible at n_jobs=4 (same seed -> identical solution) for all three: ACO (6.151822==6.151822), ACO-MST (3.51003==3.51003), GA (8.96930==8.96930), and GA also at n_jobs=1. tests/test_combinatorics.py + tests/test_determinism.py green (27). Closes #117. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxgEQmoqtAj589pvbwuGo4
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxgEQmoqtAj589pvbwuGo4
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxgEQmoqtAj589pvbwuGo4
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.
Closes #117. Makes the combinatorial TSP solvers (ACO, GA, ACO-MST) reproducible under
joblib.Parallel(prefer="threads"), adopting the per-worker stream infrastructure the continuous stack already uses.Problem
The solvers drew from the process-global, thread-unsafe
np.randomwhile dispatching ants/individuals across joblib threads → a seeded run gave a different tour every execution atn_jobs>1.Fix
core.random.rng()(thread-local stream) inaco.py,ga.py,aco_mst.py.spawn_streams(n_jobs)+ wrap each task body inuse_stream(stream), so which numbers a task sees depends on its index, not scheduling._tournament_selectionreturned a view into the shared genome that the genetic operators mutate in place — under threads that corrupts the array other workers read. Return a.copy(). (This was the residual non-determinism after the RNG fix — reproducible serial, not parallel.)Verified (same seed → identical solution, n_jobs=4)
ACO
6.151822==6.151822, ACO-MST3.51003==3.51003, GA8.96930==8.96930(and GA at n_jobs=1).test_combinatorics+test_determinismgreen (27).Note: TSP-solver outputs are not in any reported dissertation table, so the (now-correct) result changes are internal.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LxgEQmoqtAj589pvbwuGo4