Severity: Medium
Stochastic structured local-search kernels re-seed their RNG identically on every run() invocation instead of advancing across study steps, biasing which moves/leaves ever get explored.
Locations — src/variopt/algorithms/local_search/structured/stochastic.py:290, iterated.py:280, variable_neighborhood.py:303
Each of these kernels calls normalize_random_state(self.random_state) (default seed 0) inside every run() call. study/execution.py:477 invokes kernel.run once per study step, so every step replays an identical RNG stream rather than advancing.
Scenario
StructuredStochasticNeighborhoodKernel(max_neighbors_per_step=8) over many study steps samples the same move-index pattern every step whenever the move-list length matches, systematically biasing which leaves are ever explored. Iterated local search likewise re-samples the same "kick" leaf indices every batch. This is unlike the population-optimizer family, which correctly threads RNG forward through RandomStateSnapshot state across asks.
Fix direction
Derive per-run randomness from run-scoped state instead of re-seeding identically per run() — e.g. thread a RandomStateSnapshot through kernel hints/state (matching the population family's pattern), or fold a per-query counter into the seed.
Severity: Medium
Stochastic structured local-search kernels re-seed their RNG identically on every
run()invocation instead of advancing across study steps, biasing which moves/leaves ever get explored.Locations —
src/variopt/algorithms/local_search/structured/stochastic.py:290,iterated.py:280,variable_neighborhood.py:303Each of these kernels calls
normalize_random_state(self.random_state)(default seed 0) inside everyrun()call.study/execution.py:477invokeskernel.runonce per study step, so every step replays an identical RNG stream rather than advancing.Scenario
StructuredStochasticNeighborhoodKernel(max_neighbors_per_step=8)over many study steps samples the same move-index pattern every step whenever the move-list length matches, systematically biasing which leaves are ever explored. Iterated local search likewise re-samples the same "kick" leaf indices every batch. This is unlike the population-optimizer family, which correctly threads RNG forward throughRandomStateSnapshotstate across asks.Fix direction
Derive per-run randomness from run-scoped state instead of re-seeding identically per
run()— e.g. thread aRandomStateSnapshotthrough kernel hints/state (matching the population family's pattern), or fold a per-query counter into the seed.