This repository runs Monte Carlo simulations of Voter Satisfaction Efficiency
(VSE) for voting methods under different electorate and strategy models. The
published explanation and results live in docs/.
Production code uses an installable src/vse_sim package. Run development
commands from the repository root after uv sync --locked.
- Supported Python: 3.14; local and CI default to Python 3.14.
- Dependency manager:
uv; keepuv.lockin sync withpyproject.toml. - Install dependencies with
uv sync --locked. - Run the test suite with
uv run python -m pytest. - Run repository lint and security checks with
trunk check. - Do not commit generated
SimResults*.csvor ad hoc simulation dumps.
Pytest is configured with --doctest-modules, so examples in module docstrings
are tests. Add focused pytest tests for regressions that are awkward to express
as doctests. Keep random tests deterministic and seed both Python's random
module and NumPy.
src/vse_sim/simulation.py: orchestration, method presets, and CSV output.src/vse_sim/core.py: core method API, tallies, ballot caching, and VSE rows.src/vse_sim/methods/: voting method and ballot implementations, one module per method.src/vse_sim/voter_models.py: voter and electorate models.src/vse_sim/strategies.py: strategic ballot choosers and media models.src/vse_sim/decorators.py: local decorators used by the package.src/vse_sim/diagnostics.py: TRACE-level diagnostics.tests/: pytest regression tests; package doctests are also collected.scripts/recalculate_irv_pages.py: reproducible, parallel IRV calculations.scripts/regenerate_pages_images.py: generated HTML and chart updates.docs/: GitHub Pages source plus committed generated charts.experiments/soda.py: legacy SODA experiment; production code must not depend on it.artifacts/: retained historical simulation outputs.analysis/: ancillary analysis code.
Voting methods derive from vse_sim.core.Method. Preserve the existing ballot
and result conventions unless a deliberate migration updates all callers:
- candidate results are index-aligned sequences;
- the winning candidate is selected through
Method.winner; - ballot functions are memoized on voter objects by method class name;
- chooser names and tally fields are serialized into CSV and may be consumed by scripts or published-data tooling.
Add tests for ties, identical utilities, empty or minimal profiles, and cyclic profiles as applicable. Do not infer correctness from one happy-path doctest.
Election metadata is held in the method instance's ElectionContext, and Mav
cutoffs are captured by the election's ballot function. Keep this state
election-scoped:
- reset it before each independent election;
- do not parallelize elections that share method classes unless state has first been isolated;
- do not introduce new class-level mutable simulation state;
- extend
ElectionContextinstead of adding implicit cross-phase state.
For reproducible runners, derive and set both Python and NumPy seeds. Prefer local RNG objects in new code over adding more process-global RNG use.
VSE normalizes by best - rand, and score ballots normalize by each voter's
utility range. Handle zero ranges explicitly. Define and test the intended
result rather than allowing ZeroDivisionError, NaN, or infinity.
Avoid private NumPy import paths such as numpy.core.*; use public numpy
APIs.
Changes to voter generation, strategies, tabulation, tie-breaking, seeding, or VSE normalization can alter published numbers. When such behavior changes:
- Add a small deterministic regression test.
- Run an appropriately sized smoke calculation with
scripts/recalculate_irv_pages.py. - If the change is intended to update published results, regenerate the site artifacts and explain the changed assumptions in the same change.
- Do not hand-edit generated HTML or PNG output.
The full published run can be expensive. Use a small election count while developing, then use the documented seed and full command before publishing.
When touching nearby code, prefer small staged changes in this order:
- Protect correctness with regression tests, especially Schulze cycles, normalization edge cases, and strategy chooser behavior.
- Extend the explicit election context instead of introducing shared state.
- Use
retain_rows=Falsefor large CSV batches and preserve the streaming path when changing persistence. - Keep import paths package-relative within
src/vse_sim; scripts and tests should import the installedvse_simpackage.
Do not combine algorithm changes with broad formatting or module moves. Voting method changes should remain reviewable against the prior mathematical behavior.
- Preserve unrelated working-tree changes.
- Keep runtime dependencies minimal; charting and test tools belong in the dev dependency group.
- Update
README.mdwhen setup or common commands change. - If generated files change, identify the generating command in the change description.