Skip to content

Primary Elections

Primary election and candidate selection systems. Supports closed and open primaries, plus valence-based candidate field generation.

closed_primary

Closed primary: only voters registered with a given party may vote.

from electoral_sim.systems.primary import closed_primary

result = closed_primary(
    voter_utilities=utilities,
    party_affiliation=affiliations,
    party_id=0,
)

Parameters: | Parameter | Type | Description | |-----------|------|-------------| | voter_utilities | np.ndarray | (n_voters, n_candidates) utility matrix | | party_affiliation | np.ndarray | (n_voters,) party ID for each voter | | party_id | int | Party conducting the primary |

Returns: Dict with keys: | Key | Type | Description | |-----|------|-------------| | winner | int | Candidate index with highest total utility among party voters (-1 if none) | | vote_counts | np.ndarray | Total utility scores per candidate |

Example:

import numpy as np

# 100 voters, 4 candidates in Party 0's primary
utilities = np.random.normal(50, 10, (100, 4))
party_affiliation = np.random.choice([0, 1], 100)

result = closed_primary(utilities, party_affiliation, party_id=0)
winner = result["winner"]
print(f"Winner: Candidate {winner}")


open_primary

Open primary: any voter can participate regardless of party affiliation.

from electoral_sim.systems.primary import open_primary

result = open_primary(voter_utilities=utilities)

Parameters: | Parameter | Type | Description | |-----------|------|-------------| | voter_utilities | np.ndarray | (n_voters, n_candidates) utility matrix |

Returns: Dict with keys: | Key | Type | Description | |-----|------|-------------| | winner | int | Candidate with highest total utility across all voters | | vote_counts | np.ndarray | Total utility scores per candidate |

Example:

utilities = np.random.normal(50, 10, (1000, 3))
result = open_primary(utilities)
print(f"Candidate {result['winner']} wins the open primary")


candidate_selection

Generate candidate valence scores for each party's candidate field. The highest-valence candidate becomes the general election nominee.

from electoral_sim.systems.primary import candidate_selection

valence_matrix = candidate_selection(
    n_parties=5,
    n_candidates_per_party=3,
    base_valence=50.0,
    rng=np.random.default_rng(42),
)

Parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | n_parties | int | required | Number of parties | | n_candidates_per_party | int | 3 | Candidates per party primary | | base_valence | float | 50.0 | Mean candidate valence (clipped 0-100) | | rng | np.random.Generator \| None | None | Random generator |

Returns: (n_parties, n_candidates_per_party) valence matrix, clipped to [0, 100].

Example:

import numpy as np

# Generate candidate fields for 5 parties
valence = candidate_selection(5, n_candidates_per_party=4)

# Each party's general-election nominee is its highest-valence candidate
nominees = valence.max(axis=1)
for i, v in enumerate(nominees):
    print(f"Party {i} nominee valence: {v:.1f}")