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
10 changes: 5 additions & 5 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The checkout step line is malformed and will break the workflow YAML.

This line combines two uses: declarations (@34e... # v4.3.1 and @v4) on a single entry, which produces invalid YAML and stops the workflow from running. Please split this into separate steps or keep only the pinned SHA form, e.g.

      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Set up Python 3.14
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 uses: actions/setup-python@v5
Comment on lines +21 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The setup-python step mixes two uses clauses on one line, making the YAML invalid.

This step currently has two uses declarations on the same line (actions/setup-python@... # v5.6.0 uses: actions/setup-python@v5), which is not valid for YAML or GitHub Actions. Please keep a single, pinned uses entry, for example:

      - name: Set up Python 3.14
        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
        with:
          python-version: "3.14"

As-is, the workflow will not load successfully.

with:
python-version: "3.12"
python-version: "3.14"

- name: Install uv
uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 uses: astral-sh/setup-uv@v6
Comment on lines 26 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The uv setup step also has two uses values merged into one line, which will break the workflow.

This step is invalid because two uses entries are on the same line; GitHub Actions only allows one uses per step. Please keep a single, pinned uses value, e.g.:

      - name: Install uv
        uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"

Otherwise the workflow will fail to run.

with:
enable-cache: true
cache-dependency-glob: "uv.lock"
Expand Down
18 changes: 0 additions & 18 deletions .project

This file was deleted.

1 change: 0 additions & 1 deletion .trunk/trunk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ runtimes:
enabled:
- go@1.21.0
- node@22.16.0
- python@3.12.10
actions:
enabled:
- trunk-announce
Expand Down
35 changes: 19 additions & 16 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ 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.
Production code uses an installable `src/vse_sim` package. Run development
commands from the repository root after `uv sync --locked`.

## Environment and validation

- Supported Python: 3.10 through 3.12; local and CI default to Python 3.12.
- Supported Python: 3.14; local and CI default to Python 3.14.
- 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`.
Expand All @@ -25,23 +25,27 @@ 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.
- `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.py`: voting method and ballot implementations.
- `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.
- `sodaTest.py`: experimental legacy code; do not make production code depend
on it.
- `experiments/soda.py`: legacy SODA experiment; production code must not
depend on it.
- `artifacts/`: retained historical simulation outputs.
- `analysis/`: ancillary analysis code.

## Change guidance

### Voting methods

Voting methods derive from `dataClasses.Method`. Preserve the existing ballot
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;
Expand Down Expand Up @@ -75,8 +79,7 @@ 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.
APIs.

### Published results

Expand All @@ -102,8 +105,8 @@ When touching nearby code, prefer small staged changes in this order:
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.
4. Keep import paths package-relative within `src/vse_sim`; scripts and tests
should import the installed `vse_sim` package.

Do not combine algorithm changes with broad formatting or module moves. Voting
method changes should remain reviewable against the prior mathematical
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ full simulation CSV used by the chart analysis.

## Setup

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

```sh
uv sync --locked
```

The repository has a flat module layout, so run commands from its root.
`uv sync` installs the `vse_sim` package from `src/`. Run development commands
from the repository root.

## Validation

Expand All @@ -32,8 +33,9 @@ trunk check
## Running simulations

```python
from voterModels import PolyaModel
from vse import CsvBatch, Mav, Score, baseRuns, medianRuns
from vse_sim.methods import Mav, Score
from vse_sim.simulation import CsvBatch, baseRuns, medianRuns
from vse_sim.voter_models import PolyaModel

batch = CsvBatch(
PolyaModel(),
Expand Down Expand Up @@ -66,7 +68,7 @@ CsvBatch(
Use a small deterministic run while developing:

```sh
uv run python scripts/recalculate_irv_pages.py \
uv run python -m scripts.recalculate_irv_pages \
--elections 50 \
--workers 1 \
--seed smoke
Expand All @@ -75,8 +77,8 @@ uv run python scripts/recalculate_irv_pages.py \
The full published configuration and seed are the script defaults:

```sh
uv run python scripts/recalculate_irv_pages.py
uv run python scripts/regenerate_pages_images.py
uv run python -m scripts.recalculate_irv_pages
uv run python -m scripts.regenerate_pages_images
```

Changes to voter generation, strategies, tabulation, tie-breaking, random
Expand Down
5 changes: 5 additions & 0 deletions analysis/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Analysis

Ancillary analysis code lives here. It is not installed with the Python
package and may require tools or historical input files beyond the standard
development environment.
File renamed without changes.
8 changes: 8 additions & 0 deletions artifacts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Historical artifacts

These files are retained outputs from earlier simulation runs. They are not
used by the package or test suite and do not define current published results.

New ad hoc simulation output should remain untracked. Reproducible published
results belong in `docs/` and must be generated through the scripts in
`scripts/`.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes
5 changes: 5 additions & 0 deletions experiments/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Experiments

This directory contains exploratory or legacy voting-method code that is not
part of the supported `vse_sim` package. Production modules and tests must not
depend on it.
2 changes: 1 addition & 1 deletion sodaTest.py → experiments/soda.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import numpy as np

from debugDump import trace
from vse_sim.diagnostics import trace


def autoargs(*include,**kwargs):
Expand Down
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[tools]
python = "3.12"
python = "3.14"
15 changes: 11 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "vse-sim"
version = "0.1.0"
description = "Voter Satisfaction Efficiency simulation tools"
requires-python = ">=3.10,<3.13"
dependencies = ["numpy>=1.23,<2", "scipy>=1.9,<1.12"]
requires-python = ">=3.14,<3.15"
dependencies = ["numpy>=2.3,<3", "scipy>=1.16,<2"]

[dependency-groups]
dev = ["matplotlib>=3.10,<4", "pytest>=7,<9"]
dev = ["matplotlib>=3.10,<4", "pytest>=7,<10"]

[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
addopts = "--doctest-modules"
testpaths = ["."]
testpaths = ["src/vse_sim", "tests"]
1 change: 1 addition & 0 deletions scripts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Repository maintenance and publishing scripts."""
17 changes: 7 additions & 10 deletions scripts/recalculate_irv_pages.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
"""Generate the IRV/RCV VSE values cited by the GitHub Pages site.

This reproduces the historical run configuration documented in vse.py without
retaining every election result in memory. For the published 15,000-election
run, use:
This reproduces the historical run configuration in
``vse_sim.simulation`` without retaining every election result in memory. For
the published 15,000-election run, use:

uv run python scripts/recalculate_irv_pages.py
"""

import argparse
import csv
import os
import sys
from collections import defaultdict
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from debugDump import setDebug
from methods import Irv, Schulze
from voterModels import KSModel
from vse import baseRuns, fuzzyMediaFor, seedRandomGenerators
from vse_sim.diagnostics import setDebug
from vse_sim.methods import Irv, Schulze
from vse_sim.simulation import baseRuns, fuzzyMediaFor, seedRandomGenerators
from vse_sim.voter_models import KSModel

DEFAULT_WORKERS = 10

Expand Down
6 changes: 2 additions & 4 deletions scripts/regenerate_pages_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@
import argparse
import json
import re
import sys
from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib.lines import Line2D

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.recalculate_irv_pages import DEFAULT_WORKERS, recalculate

from scripts.recalculate_irv_pages import DEFAULT_WORKERS, recalculate # noqa: E402
ROOT = Path(__file__).resolve().parents[1]

STRATEGIES = [
"a.100% honest",
Expand Down
1 change: 1 addition & 0 deletions src/vse_sim/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Voter Satisfaction Efficiency simulation tools."""
4 changes: 2 additions & 2 deletions dataClasses.py → src/vse_sim/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from numpy import isclose, mean

from mydecorators import autoassign, decorator
from .decorators import autoassign, decorator


def isnum(x):
Expand Down Expand Up @@ -210,7 +210,7 @@ def multiResults(self, voters, chooserFuns=(), media=(lambda x,t:x),
election's extra event metadata. Strategic results use common polling
information produced by ``media(honest_results)``.
"""
from stratFunctions import OssChooser
from .strategies import OssChooser

honTally = SideTally()
self.context = ElectionContext()
Expand Down
2 changes: 1 addition & 1 deletion mydecorators.py → src/vse_sim/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from inspect import getfullargspec, isfunction
from itertools import starmap

from debugDump import trace
from .diagnostics import trace

_missing = object()

Expand Down
File renamed without changes.
8 changes: 4 additions & 4 deletions methods.py → src/vse_sim/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

from numpy import argsort, floor, mean, percentile, sign

from dataClasses import CandidateWithCount, Method, rememberBallot, rememberBallots
from voterModels import DeterministicModel, Voter # noqa: F401
from .core import CandidateWithCount, Method, rememberBallot, rememberBallots
from .voter_models import DeterministicModel, Voter # noqa: F401


# Election methods
Expand Down Expand Up @@ -247,7 +247,7 @@ def results(self, ballots, **kwargs):
upset = sum(sign(ballot[runnerUp] - ballot[top]) for ballot in ballots)
if upset > 0:
baseResults[runnerUp] = baseResults[top] + 0.01
return baseResults
return [result.item() if hasattr(result, "item") else result for result in baseResults]
return Srv0to()


Expand Down Expand Up @@ -758,7 +758,7 @@ def results(self, ballots, isHonest=False, **kwargs):
> 0)
self.extraEvents["4beats1"] = fourthWin

return r2s
return [result.item() if hasattr(result, "item") else result for result in r2s]

def stratBallotFor(self, polls):
"""Returns a function which takes utilities and returns a dict(
Expand Down
10 changes: 5 additions & 5 deletions vse.py → src/vse_sim/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@

import numpy as np

from debugDump import debug, setDebug
from methods import (
from .decorators import autoassign, timeit
from .diagnostics import debug, setDebug
from .methods import (
IRNR,
V321,
Borda,
Expand All @@ -23,8 +24,7 @@
Score,
Srv,
)
from mydecorators import autoassign, timeit
from stratFunctions import (
from .strategies import (
Chooser,
LazyChooser,
OssChooser,
Expand All @@ -40,7 +40,7 @@
topNMediaFor,
truth,
)
from voterModels import (
from .voter_models import (
DeterministicModel,
DimElectorate,
DimModel,
Expand Down
4 changes: 2 additions & 2 deletions stratFunctions.py → src/vse_sim/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

from numpy import std

from dataClasses import SideTally
from mydecorators import autoassign, cached_property
from .core import SideTally
from .decorators import autoassign, cached_property


class Chooser:
Expand Down
Loading
Loading