High-performance agent-based electoral simulation toolkit with support for 11+ countries, multiple voting systems, and advanced voter behavior models
Project description
ElectoralSim
High-Performance Electoral Simulation Toolkit
Early-stage • Functional • Under active development
Documentation • PyPI • Quick Start
Disclaimer: ElectoralSim is a simulation and electoral-system comparison toolkit, not an election forecasting model. Default country presets are structural demonstrations unless explicitly marked as calibrated against real election data. See Limitations for more.
A modular simulation toolkit for electoral systems, voter behavior, and political dynamics. Uses a hybrid approach: voter populations are stored as vectorized Polars DataFrames (not individual Python objects) for million-scale performance, with Mesa for model orchestration, Numba for JIT acceleration, and NetworkX for opinion dynamics.
Feature Status
| Area | Status | Notes |
|---|---|---|
| FPTP, PR allocation (D'Hondt, Sainte-Laguë, Hare, Droop) | ✅ Stable | Numba-accelerated, tested |
| IRV/RCV, STV, Approval, Condorcet | ✅ Stable | Algorithmic implementation |
| Voter generation (demographics, ideology) | ✅ Stable | Rich synthetic voter profiles |
| Behavior models (Proximity, Valence, Retrospective) | ✅ Stable | Weighted composition via BehaviorEngine |
| Metrics (Gallagher, ENP, HHI, VSE, etc.) | ✅ Stable | Validated against known values |
| BatchRunner (parameter sweeps) | 🟡 Beta | Sequential stable; parallel has Numba/OpenMP caveat |
| Country presets (11 countries) | 🟡 Beta | Structural presets, not calibrated forecasts |
| India simulator (543 Lok Sabha) | 🟡 Beta | Specialized implementation; being migrated to generic engine |
| Strategic/WastedVote models | 🟡 Beta | Functional; viability inputs are synthetic by default |
| Opinion dynamics (networks, bounded confidence) | 🟡 Beta | Core algorithms tested; needs empirical validation |
| Coalition formation & government stability | 🟡 Beta | MWC, MCW, Laver-Shepsle, collapse models |
| Streamlit dashboard | 🟡 Beta | Interactive explorer for multiple countries |
| Sociotropic/Pocketbook model | 🔶 Experimental | Requires external economic data for realism |
| Voter psychology (Big Five, Moral Foundations, etc.) | 🔶 Experimental | Synthetic features; not survey-calibrated |
| Media effects & Raducha susceptibility | 🔶 Experimental | Simplified models |
| Event manager (scandals, shocks) | 🔶 Experimental | Activated only when explicitly configured |
| Adaptive party strategy | 🔶 Experimental | Median-voter walk; disabled by default |
| GPU acceleration (CuPy) | 🔶 Experimental | Utility computation & MNL sampling only; needs correctness tests |
Key Features
Electoral Systems
| System | Methods | Status |
|---|---|---|
| Plurality | First Past The Post (FPTP) | Stable |
| Proportional | D'Hondt, Sainte-Laguë, Hare Quota, Droop Quota | Stable |
| Ranked Choice | IRV/RCV, STV (Single Transferable Vote) | Stable |
| Other | Approval Voting, Condorcet Winner | Stable |
Voter Behavior Models
- Proximity Model — Spatial voting based on ideological distance
- Valence Model — Non-policy candidate appeal
- Retrospective Model — Economic voting (reward/punish incumbents)
- Strategic Voting — Wasted vote model (Experimental)
- Sociotropic/Pocketbook — National vs personal economic evaluation (Experimental)
Opinion Dynamics (Experimental)
- Network Topologies — Barabási-Albert, Watts-Strogatz, Erdős-Rényi
- Models — Bounded Confidence, Noisy Voter, Zealots
Coalition & Government (Beta)
- Formation — MWC, MCW, Laver-Shepsle portfolio allocation
- Stability — Sigmoid/Linear/Exponential collapse models
- Analysis — Coalition strain, junior partner penalty, Cox hazard
Metrics
- Gallagher Index (disproportionality)
- Effective Number of Parties (Laakso-Taagepera)
- Efficiency Gap, Loosemore-Hanby, Herfindahl-Hirschman Index
- Voter Satisfaction Efficiency (VSE)
Country Presets (11 Countries)
These are structural presets — they encode electoral-system rules, approximate party positions, and default parameters for demonstration and comparative simulation. They are not calibrated election forecasts.
| Region | Countries |
|---|---|
| Asia | 🇮🇳 India (543 Lok Sabha), 🇯🇵 Japan |
| Europe | 🇬🇧 UK, 🇩🇪 Germany, 🇫🇷 France, 🇪🇺 EU Parliament (720 MEPs) |
| Americas | 🇺🇸 USA, 🇧🇷 Brazil |
| Oceania/Africa | 🇦🇺 Australia, 🇿🇦 South Africa |
Performance
- Vectorized Polars DataFrames for voter storage (not individual Python objects)
- Numba JIT acceleration for vote counting and MNL sampling (~10-50x over pure Python)
- Batch parameter sweeps for systematic exploration
- Parallel execution with multiprocessing (see parallel caveat)
Installation
pip install electoral-sim
From source:
git clone https://github.com/Ayush12358/ElectoralSim.git
cd ElectoralSim
pip install -e .
Optional dependencies:
pip install electoral-sim[viz] # Visualization (matplotlib, plotly)
pip install electoral-sim[gpu] # GPU acceleration (cupy)
pip install electoral-sim[all] # Everything
Command-Line Interface
Run simulations directly from the command line:
# Basic simulation
electoral-sim run --voters 50000 --constituencies 10
# Use country preset
electoral-sim run --preset india --output results.json
# Batch parameter sweeps
electoral-sim batch --config batch_config.json --output results.csv
# List available presets
electoral-sim list-presets
See the CLI Guide for comprehensive usage.
Quick Start
Basic Election
from electoral_sim import ElectionModel
# Create model with 100K voters
model = ElectionModel(n_voters=100_000, seed=42)
results = model.run_election()
print(f"Turnout: {results['turnout']:.1%}")
print(f"Gallagher Index: {results['gallagher']:.2f}")
print(f"ENP (votes): {results['enp_votes']:.2f}")
Country Presets
# India - Full Lok Sabha simulation
from electoral_sim import simulate_india_election
result = simulate_india_election(n_voters_per_constituency=1000)
print(f"BJP: {result.seats['BJP']} seats")
print(f"NDA Alliance: {result.nda_seats} seats")
# Other countries
model = ElectionModel.from_preset("germany") # MMP with 5% threshold
model = ElectionModel.from_preset("usa") # 435 House districts
model = ElectionModel.from_preset("uk") # 650 Commons seats
Chainable API
results = (
ElectionModel(n_voters=100_000)
.with_system("PR")
.with_allocation("sainte_lague")
.with_threshold(0.05)
.with_temperature(0.3) # More deterministic voting
.run_election()
)
Batch Runner - Parameter Sweeps
Systematic parameter exploration for sensitivity analysis:
from electoral_sim.analysis import BatchRunner, ParameterSweep
# Define parameter sweep
sweep = ParameterSweep({
'n_voters': [10_000, 50_000, 100_000],
'temperature': [0.3, 0.5, 0.7],
'economic_growth': [-0.02, 0.0, 0.02]
})
# Run batch with parallel execution
runner = BatchRunner(
model_class=ElectionModel,
parameter_sweep=sweep,
n_runs_per_config=5,
n_jobs=4
)
results_df = runner.run()
runner.export_results('results.csv')
Custom Behavior Engine
from electoral_sim import (
ElectionModel, BehaviorEngine,
ProximityModel, ValenceModel, StrategicVotingModel
)
# Build custom voter behavior
engine = BehaviorEngine()
engine.add_model(ProximityModel(weight=1.0))
engine.add_model(ValenceModel(weight=0.5))
engine.add_model(StrategicVotingModel(sensitivity=2.0))
model = ElectionModel(n_voters=50_000, behavior_engine=engine)
results = model.run_election()
Opinion Dynamics
from electoral_sim import ElectionModel, OpinionDynamics
# Create social network
od = OpinionDynamics(n_agents=10_000, topology="barabasi_albert", m=3)
# Simulate with opinion evolution
model = ElectionModel(n_voters=10_000, opinion_dynamics=od)
for _ in range(100):
model.step() # Opinions evolve
result = model.run_election()
Coalition Formation
from electoral_sim import form_government, coalition_strain
import numpy as np
seats = np.array([150, 120, 80, 50])
positions = np.array([0.6, -0.3, 0.1, -0.6])
names = ["Right", "Left", "Center", "Far-Left"]
gov = form_government(seats, positions, names)
print(f"Coalition: {gov['coalition_names']}")
print(f"Majority: {gov['seats']} seats")
print(f"Stability: {gov['stability']:.2f}")
Streamlit Dashboard
Launch the interactive election explorer:
streamlit run app.py
Features:
- Multi-country simulation (India, USA, UK, Germany, etc.)
- Dynamic parameters (economic growth, national mood, anti-incumbency)
- Real-time seat distribution and vote share charts
- Swing analysis and ideological landscape visualization
More screenshots: docs/assets/
Project Structure
electoral_sim/
├── core/ # ElectionModel, Config, Voter Generation
├── agents/ # Voter, Party, Adaptive Strategy
├── behavior/ # Behavior models (Proximity, Valence, Strategic, etc.)
├── dynamics/ # Opinion dynamics (networks, bounded confidence)
├── engine/ # Numba/GPU acceleration, Coalition, Government
├── events/ # Event manager (scandals, economic shocks)
├── metrics/ # Gallagher, ENP, VSE, Efficiency Gap
├── presets/ # Country configs (11 countries + EU)
│ ├── india/ # 543 constituencies, 17 parties
│ ├── eu/ # 27 member states, 720 MEPs
│ └── ...
├── systems/ # Electoral systems (allocation, IRV, STV)
├── visualization/ # Plots, maps, animations
└── data/ # Historical election data
Performance Benchmarks
Benchmarks below were run on an Intel i5-1135G7 (4C/8T, 16 GB RAM, Ubuntu 22.04, Python 3.12, Numba 0.65). Timings exclude Numba JIT warmup. Memory is RSS delta measured via
psutil. Seebenchmarks/for reproducible scripts.
| Scale | Create Time | Election Time | Memory (RSS delta) |
|---|---|---|---|
| 10K voters | ~18ms | ~5ms | ~52 MB |
| 100K voters | ~109ms | ~35ms | ~15 MB |
| 500K voters | ~608ms | ~176ms | ~95 MB |
| 1M voters | ~1.2s | ~316ms | ~148 MB |
Batch throughput: ~5 elections/sec at 500K voters; up to ~30 elections/sec at smaller 10K-50K configurations (FPTP, default behavior, after JIT warmup).
Testing
# Run all tests
pytest tests/ -v
# Run integration tests only
pytest tests/test_integration.py -v
# Run stress tests
python tests/stress_test.py
Test suite: 237 tests including:
- Property-based tests (Hypothesis) — random input generation
- Parameterized tests — all systems, presets, allocation methods
- Performance smoke tests — 1K, 10K voters timing
- Error handling — invalid inputs, edge cases
- Preset smoke tests — UK, US, Germany, India, etc.
- Invariant tests — seats non-negative, vote shares sum to 1, determinism with seeds
Documentation
- Usage Guide — Detailed API usage examples
- docs/ — Full documentation
- API Reference — Complete function/class documentation
- Country Presets — India, EU, and other country guides
- Advanced Topics — Voter psychology, performance tuning
Limitations
What ElectoralSim Is
- A simulation and electoral-system comparison toolkit
- A teaching and research prototyping tool
- A way to explore how different electoral systems and voter assumptions interact
What ElectoralSim Is Not
- ❌ An election forecasting model
- ❌ A calibrated public-opinion model (by default)
- ❌ A substitute for survey data or polling
- ❌ A validated behavioral model for all included countries
Known Limitations
- Country presets use synthetic party positions and valence values — they are structural demos, not calibrated to real election data.
- Voter psychology features (Big Five, Moral Foundations, affective polarization) are synthetically generated and not derived from survey instruments.
- 2D ideological space is a deliberate simplification; complex political systems (India, Brazil, EU) have dimensions (caste, region, language, religion) not captured.
- India simulator currently uses a specialized high-performance path separate from the generic
ElectionModel; improvements to the core engine do not automatically benefit India. - GPU acceleration is experimental and limited to utility computation and MNL sampling kernels; it has not been correctness-tested against CPU outputs.
Parallel Execution Caveat
On Linux, Numba's OpenMP threading layer conflicts with Python's default fork()-based multiprocessing. If using BatchRunner with n_jobs > 1, you may encounter BrokenProcessPool. Workarounds:
import multiprocessing
multiprocessing.set_start_method("spawn")
# or
import os; os.environ["OMP_NUM_THREADS"] = "1"
Tech Stack
| Component | Library |
|---|---|
| Agent-Based Modeling | Mesa |
| DataFrames | Polars |
| JIT Acceleration | Numba |
| GPU Support | CuPy |
| Social Networks | NetworkX |
| Visualization | Plotly, Matplotlib |
| Dashboard | Streamlit |
License
Apache License 2.0 - see LICENSE for details.
Acknowledgments
- Mesa for the agent-based modeling framework
- Political science research on spatial voting, opinion dynamics, and coalition theory
- Electoral data from various national election commissions
Built with love for computational political science
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file electoral_sim-0.1.1.tar.gz.
File metadata
- Download URL: electoral_sim-0.1.1.tar.gz
- Upload date:
- Size: 118.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0f8a9e800908a8145e4cc990cdb9fdbf18114b4e933d6a3381b36a15502b8f9f
|
|
| MD5 |
5177024d805461d740d17005b47fc6eb
|
|
| BLAKE2b-256 |
6af99bc53e2c9a6fefe9e057fe0f58943f893acd6050c8154ac01d8401d9118b
|
Provenance
The following attestation bundles were made for electoral_sim-0.1.1.tar.gz:
Publisher:
release.yml on Ayush12358/ElectoralSim
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
electoral_sim-0.1.1.tar.gz -
Subject digest:
0f8a9e800908a8145e4cc990cdb9fdbf18114b4e933d6a3381b36a15502b8f9f - Sigstore transparency entry: 1717567892
- Sigstore integration time:
-
Permalink:
Ayush12358/ElectoralSim@2075a66aa9e9910b3e406d0d10fc3610f02fe160 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Ayush12358
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2075a66aa9e9910b3e406d0d10fc3610f02fe160 -
Trigger Event:
push
-
Statement type:
File details
Details for the file electoral_sim-0.1.1-py3-none-any.whl.
File metadata
- Download URL: electoral_sim-0.1.1-py3-none-any.whl
- Upload date:
- Size: 106.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
810c58d7fe20fe32a85c44e5b701dcbd1ce75c0d52bbd9d1a454638e6a29e323
|
|
| MD5 |
d421fda8b8599c997f49ed59f45ddf2c
|
|
| BLAKE2b-256 |
9d1ebcc6dfea62a87cfe2ac358cd653d5870702a455d908fd350ece0d3c7b95f
|
Provenance
The following attestation bundles were made for electoral_sim-0.1.1-py3-none-any.whl:
Publisher:
release.yml on Ayush12358/ElectoralSim
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
electoral_sim-0.1.1-py3-none-any.whl -
Subject digest:
810c58d7fe20fe32a85c44e5b701dcbd1ce75c0d52bbd9d1a454638e6a29e323 - Sigstore transparency entry: 1717567999
- Sigstore integration time:
-
Permalink:
Ayush12358/ElectoralSim@2075a66aa9e9910b3e406d0d10fc3610f02fe160 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Ayush12358
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2075a66aa9e9910b3e406d0d10fc3610f02fe160 -
Trigger Event:
push
-
Statement type: