Skip to content

Feat/global rng - #375

Merged
peterrrock2 merged 9 commits into
3.4.1from
feat/global-rng
Aug 5, 2026
Merged

Feat/global rng#375
peterrrock2 merged 9 commits into
3.4.1from
feat/global-rng

Conversation

@graceg571

@graceg571 graceg571 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Addresses #347

Summary

All public apis that call a random function accept a random_seed keyword argument. No seed is provided by default. The internal functions called by the public apis accept a seeded RNG object to ensure their results are the same given the same inputs and seed to the public API.

random.Random() is used where possible as the rng object. np.random.default_rng() used elsewhere as numpy_rng object when an equivalent random.Random() function does not exist.

Sets that are randomly sampled/sorted are ordered canonically to ensure a different PYTHONHASHSEED will not change results even if the same seed is given.

Why

VoteKit uses random functions or random number generators (RNGs) throughout to generate ballots/profiles, transfer votes, break ties, or sample preferences for candidates. When users simulate elections, they are unable to reproduce results as these random functions are unseeded. A seed argument was added to all public APIs that call random functions so that users can have reproducible results across election simulations.

Changes

  • cumulative.py: uses numpy_rng for multinomial
  • name_bradley_terry.py: uses rng. choice -> choices for _inner_name_bradley_terry(), samples with replacement. _inner_name_bradley_terry_mcmc() continues to use choices
  • name_plackett_luce.py: uses numpy_rng for choice: weighted sampling without replacement. No Python random stdlib equivalent function.
  • pref_interval.py: uses numpy_rng for from_dirichlet() dirichlet
  • utils.py: tiebreak_set() uses rng for sample and tiebreak_ranking() passes along rng to tiebreak_set()
  • slate_utils.py: uses numpy_rng for random(size) of _append_zero_slate_symbols() and uniform(low, high, size) of _fast_sample_without_replacement()
  • ballot_generator.py: from_params() takes a random_seed and initializes a numpy_rng with seed and passes along to PreferenceInterval.from_dirichlet()
  • slate_bradley_terry.py: _sample_bt_slate_ballots_deterministic() uses rng and choice->choices, sample with replacement, _sample_bt_slate_ballots_mcmc() uses rng and permutation->sample, choice->choices. inner_slate_bradley_terry() creates 2 random objects one with random.Random and other with np.random.default_rng with the same seed if provided. Both use different algorithms for initialization.
  • slate_plackett_luce.py: uses numpy_rng due to uniform(size)
  • core.py: uses numpy_rng for PreferenceInterval.from_dirichlet()
  • impartial_anon_culture.py: uses rng for sample
  • impartial_culture.py: uses rng for _generate_profile_optimized_non_short() np.random.choice->sample: sample without replacement and randint for _generate_profile_optimized_with_short()
  • spacial.py uses numpy_rng for np.normal.uniform, normal, etc.
  • transfers.py: random_transfer() uses rng for sample
  • boosted_random_dictator.py and random_dictator.py: uses rng for choices
  • plurality_veto.py uses rng and np.random.permutation->sample
  • plurality.py, plurality_veto.py, boosted_random_dictator.py, random_dictator.py, numpy_stv_base.py, stv.py, rating.py add self._rng for random tiebreaks
  • stv/utils.py: uses rng. np.random.choice-> sample for sampling without replacement. Store sample result as np.array to use numpy sort function.
  • stv.py: STV class takes a transfer function. If set to random_transfer() will need to use the _rng attribute from STV to have reproducible results if seeded. The rng will be binded to random_transfer() via partial so the positional argument check stays the same across fractional_transfer and random_transfer.

Testing

test_rng_seed.py tests that all ballot generators and elections with random transfer or tiebreak are reproducible when a seed is given and non-deterministic if no seed is given. Tests were added where the PYTHONHASHSEED is changed per process for functions that act upon sets to ensure PYTHONHASHSEED does not change results when given the same seed for the RNG.

@graceg571
graceg571 requested a review from peterrrock2 July 31, 2026 21:10
@graceg571 graceg571 self-assigned this Jul 31, 2026
@graceg571
graceg571 changed the base branch from main to 3.4.1 July 31, 2026 21:12
@graceg571 graceg571 linked an issue Aug 3, 2026 that may be closed by this pull request
@graceg571 graceg571 added this to the 3.4.1 milestone Aug 3, 2026
@graceg571
graceg571 marked this pull request as ready for review August 3, 2026 20:36

@peterrrock2 peterrrock2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@peterrrock2
peterrrock2 merged commit 9f300ec into 3.4.1 Aug 5, 2026
4 checks passed
@peterrrock2
peterrrock2 deleted the feat/global-rng branch August 5, 2026 22:00
@peterrrock2 peterrrock2 mentioned this pull request Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Move to globally managed RNG

2 participants