Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions .github/workflows/trunk.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
name: Annotate PR with trunk issues
name: Trunk Check

on:
workflow_run:
workflows: ["Pull Request"]
types:
- completed
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read
pull-requests: write
checks: write

jobs:
trunk_check:
name: Trunk Check Annotate
name: Trunk Check
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Trunk Check
uses: trunk-io/trunk-action@v1
uses: trunk-io/trunk-action@75699af9e26881e564e9d832ef7dc3af25ec031b # v1.2.4
with:
post-annotations: true
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
*.pyc
python3
__pycache__
.venv/
.pytest_cache/
*.egg-info/
.pydevproject
*.csv
SimResults*.csv
newResults.txt
tenK.txt
*.rtf
*.bak
.Rhistory
Expand Down
2 changes: 1 addition & 1 deletion .trunk/trunk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ runtimes:
enabled:
- go@1.21.0
- node@22.16.0
- python@3.14.4
- python@3.12.10
actions:
enabled:
- trunk-announce
Expand Down
119 changes: 119 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# AGENTS.md

## Project

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/`.

The code currently uses a flat module layout. Run commands from the repository
root; do not assume the project is installed as a Python package.

## Environment and validation

- Supported Python: 3.10 through 3.12; local and CI default to Python 3.12.
- Dependency manager: `uv`; keep `uv.lock` in sync with `pyproject.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*.csv` or 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.

## Code map

- `vse.py`: simulation orchestration, method presets, and CSV output.
- `dataClasses.py`: core method API, tallies, ballot caching, and VSE rows.
- `methods.py`: voting method and ballot implementations.
- `voterModels.py`: voter, electorate, and spatial/clustered voter models.
- `stratFunctions.py`: strategic ballot choosers and media models.
- `mydecorators.py`: local decorators used throughout the simulation.
- `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.
- `sodaTest.py`: experimental legacy code; do not make production code depend
on it.

## Change guidance

### Voting methods

Voting methods derive from `dataClasses.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.

### Simulation state and randomness

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 `ElectionContext` instead 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.

### Numerical behavior

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. NumPy 2.x remains unsupported until behavioral compatibility has been
validated and the dependency bounds are deliberately updated.

### Published results

Changes to voter generation, strategies, tabulation, tie-breaking, seeding, or
VSE normalization can alter published numbers. When such behavior changes:

1. Add a small deterministic regression test.
2. Run an appropriately sized smoke calculation with
`scripts/recalculate_irv_pages.py`.
3. If the change is intended to update published results, regenerate the site
artifacts and explain the changed assumptions in the same change.
4. 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.

## Refactoring priorities

When touching nearby code, prefer small staged changes in this order:

1. Protect correctness with regression tests, especially Schulze cycles,
normalization edge cases, and strategy chooser behavior.
2. Extend the explicit election context instead of introducing shared state.
3. Use `retain_rows=False` for large CSV batches and preserve the streaming
path when changing persistence.
4. Introduce a package layout only as a deliberate migration; update scripts,
doctests, CI, and imports together.

Do not combine algorithm changes with broad formatting or module moves. Voting
method changes should remain reviewable against the prior mathematical
behavior.

## Repository hygiene

- Preserve unrelated working-tree changes.
- Keep runtime dependencies minimal; charting and test tools belong in the dev
dependency group.
- Update `README.md` when setup or common commands change.
- If generated files change, identify the generating command in the change
description.
86 changes: 68 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,81 @@
# Voter Satisfaction Efficiency

These are some methods for running VSE (Voter Satisfaction Efficiency)
simulations for various voting systems.
This repository runs Voter Satisfaction Efficiency (VSE) simulations for
different voting systems, electorate models, and strategic behaviors.

See [Voter Satisfaction Efficiency FAQ](http://electionscience.github.io/vse-sim/) for an explanation of the methods and results.
See the [VSE FAQ](https://electionscience.github.io/vse-sim/) for an explanation
of the methods and published results.

## Installing the code
## Setup

Requirements: python3, scipy, pydoc
The project supports Python 3.10 through 3.12 and uses
[uv](https://docs.astral.sh/uv/) with a committed lockfile.

Testing uses pydoc, which should make most things pretty self-documenting.
```sh
uv sync --locked
```

E.g.:
The repository has a flat module layout, so run commands from its root.

python3 -m doctest methods.py
python3 -m doctest voterModels.py
python3 -m doctest dataClasses.py
python3 vse.py
## Validation

Doctests are part of the pytest suite:

```sh
uv run python -m pytest
trunk check
```

## Running simulations

Try
```python
from voterModels import PolyaModel
from vse import CsvBatch, Mav, Score, baseRuns, medianRuns

batch = CsvBatch(
PolyaModel(),
[[Score(), baseRuns], [Mav(), medianRuns]],
nvot=5,
ncand=4,
niter=3,
)
batch.saveFile()
```

This writes the next available `SimResultsN.csv`.

Large runs can write rows directly instead of retaining every row in memory:

```python
CsvBatch(
PolyaModel(),
[[Score(), baseRuns]],
nvot=40,
ncand=6,
niter=15_000,
baseName="SimResults",
retain_rows=False,
)
```

## Reproducing published IRV results

Use a small deterministic run while developing:

```sh
uv run python scripts/recalculate_irv_pages.py \
--elections 50 \
--workers 1 \
--seed smoke
```

The full published configuration and seed are the script defaults:

$ python3
>>> from vse import CsvBatch, baseRuns, Mav, medianRuns, Score
>>> from voterModels import PolyaModel
>>> csvs = CsvBatch(PolyaModel(), [[Score(), baseRuns], [Mav(), medianRuns]], nvot=5, ncand=4, niter=3)
>>> csvs.saveFile()
```sh
uv run python scripts/recalculate_irv_pages.py
uv run python scripts/regenerate_pages_images.py
```

and look for the results in `SimResults1.csv`
Changes to voter generation, strategies, tabulation, tie-breaking, random
seeding, or VSE normalization can change published results. See `AGENTS.md` for
the required regeneration workflow.
Loading
Loading