Proportional Methods¶
The following implementations of proportional methods are based on “The Single Transferable Vote” by N. Tideman, “Better voting methods through technology” by N. Tideman and J. S. Richardson, “Generalizing Instant Runoff Voting to Allow Indifferences” by T. Delemazure and D. Peters, and the Scottish STV rules at https://www.legislation.gov.uk/ssi/2007/42/contents/made.
These implementations have not been thoroughly vetted, so please check them carefully before using them for important purposes.
Single Transferable Vote¶
STV-Scottish¶
- pref_voting.proportional_methods.stv_scottish(profile, num_seats=2, curr_cands=None, decimals=5, rng=None)[source]¶
- Scottish STV per SSI 2007/42 (https://www.legislation.gov.uk/ssi/2007/42):
Rule 46: Integer Droop quota q = floor(N/(k+1)) + 1
Rule 48(3): per-ballot transfer = truncate[(surplus x ballot_value) / total]. The legislation says “value when received”; this implementation uses current value for robustness in edge cases where truncation leaves a candidate still above quota, in which case this implementation may perform additional surplus transfers. In counts where each elected candidate’s surplus is transferred at most once, the ballot’s “value when received” equals its current value at the time of transfer, so the formulas coincide.
- Rule 49: If multiple surpluses, transfer largest first; if equal, use history tie-break,
else decide by lot.
Rule 50: Exclusions transfer at current transfer value.
Rule 51: Exclusion ties resolved by history tie-break, else decide by lot.
Rule 52: If continuing == vacancies remaining, elect them all; no further transfers.
Ballot ties (not allowed in actual Scottish STV) are supported by equal splitting of weight.
Note on small electorates: With very few voters (especially a single voter), Scottish STV may not elect the voter’s top-k ranked candidates. This is due to the integer Droop quota mechanics - with 1 voter and k seats, the quota is floor(1/(k+1)) + 1 = 1, so no candidate can reach quota, and the method falls back to eliminations rather than simply selecting top preferences. This is expected behavior per the statutory rules, not a bug.
- Parameters:
profile – Profile or ProfileWithTies
num_seats – int
curr_cands – iterable or None
decimals – int Truncation precision for Rule 48(3). Default 5.
rng – random.Random-like or None Source of randomness for “by lot” decisions. If None, uses Python’s random module.
- Returns:
Elected candidates (sorted ascending for determinism).
- Return type:
list
Warning
STV implementations have not yet been thoroughly vetted.
STV-NB¶
- pref_voting.proportional_methods.stv_nb(profile, num_seats=2, curr_cands=None, quota_rule='nb', mann_strict=False, drain_all=False, tie_break_key=None, *, ers_rounding=False)[source]¶
Single Transferable Vote - Newland-Britton (ERS) surplus rule (“NB”) with rational Droop quota.
Summary¶
Uses the NB (rational Droop) quota n/(k+1) and the ERS/Newland-Britton compensation rule. When a candidate exceeds quota, only ballot pieces that can transfer (i.e., have a next available preference among continuing candidates) are drained; pieces that cannot transfer are left untouched. The drain fraction alpha is chosen so the total drained from transferable pieces equals the surplus, with alpha <= 1 per piece. This offsets non-transferables rather than letting surplus “disappear.” If many ballots are non-transferable, an elected candidate may remain above quota after the surplus step. (In contrast, WIG drains the same fraction from all pieces, including those that cannot move; Meek lowers the effective quota via keep factors.)
Counting details¶
Quota: NB (rational Droop) quota = total_weight / (seats + 1).
If ers_rounding=True, quota is rounded up (to integer if >100, else to hundredth) per ERS practice. Optional “Mann strictness” (mann_strict=True) requires strictly more than the NB quota. - Surpluses: one at a time, largest surplus first among newly elected. - Recipients: transfers go only to continuing (unelected) candidates. - If no surplus: eliminate the current lowest and transfer at current weights; tie broken by tie_break_key. - Ballot ties: ties on ballots are supported by equal splitting of weight. - Last vacancies: if continuing == seats_remaining, elect them all.
References: Tideman (“The Single Transferable Vote”, 1995) on ERS compensation vs Meek; and Tideman & Richardson (“Better voting methods through technology: The refinement-manageability trade-off in the single transferable vote”, 2000).
- param profile:
A Profile or ProfileWithTies object containing voter rankings
- param num_seats:
Number of seats to fill
- type num_seats:
int
- param curr_cands:
List of candidates to consider, defaults to all candidates in profile
- param quota_rule:
Quota calculation rule, defaults to “nb” (rational Droop)
- type quota_rule:
str
- param mann_strict:
Whether to use strict Mann-style elimination, defaults to False
- type mann_strict:
bool
- param drain_all:
Whether to drain all ballots, defaults to False
- type drain_all:
bool
- param tie_break_key:
Function for tie-breaking, defaults to None
- param ers_rounding:
If True, use ERS manual count rounding as described by Tideman and Richardson (2000): quota rounded up (to integer if >100,
- param else to hundredth) and transfer values rounded down to hundredth. If False:
- param defaults to rational Droop.:
- returns:
List of elected candidates
- rtype:
list
Warning
STV implementations have not yet been thoroughly vetted.
STV-WIG¶
- pref_voting.proportional_methods.stv_wig(profile, num_seats=2, curr_cands=None, quota_rule='nb', tie_break_key=None)[source]¶
STV with Weighted Inclusive Gregory (WIG) surplus transfers.
Surpluses: drain the same fraction from every ballot in a winner’s pile; forward to next available continuing choices (exhaust otherwise); only surpluses of candidates elected in this stage are processed; previously elected winners are not revisited later. Elimination transfers at current weights. Transfers are exact (no ERS-style rounding).
Ballot ties are supported by equal splitting of weight.
- Quota options:
quota_rule=”nb” -> rational Droop: total_weight / (seats + 1)
quota_rule=”droop” -> integer Droop: floor(total_weight / (seats + 1)) + 1
Note: WIG + Droop is common in public counts that use inclusive Gregory.
References: Tideman (“The Single Transferable Vote”, 1995) and Tideman & Richardson (“Better voting methods through technology: The refinement-manageability trade-off in the single transferable vote”, 2000).
- Parameters:
profile – A Profile or ProfileWithTies object containing voter rankings
num_seats (int) – Number of seats to fill
curr_cands – List of candidates to consider, defaults to all candidates in profile
quota_rule (str) – Quota calculation rule, defaults to “nb” (rational Droop)
tie_break_key – Function for tie-breaking, defaults to None
- Returns:
List of elected candidates
- Return type:
list
Warning
STV implementations have not yet been thoroughly vetted.
STV-Last-Parcel¶
- pref_voting.proportional_methods.stv_last_parcel(profile, num_seats=2, curr_cands=None, quota_rule='nb', tie_break_key=None)[source]¶
Single Transferable Vote using the “last parcel” or “senatorial” transfer rule.
This is a variant of STV where surplus transfers work differently from the standard method. When a candidate has more votes than the quota (surplus), instead of transferring a proportion of all their votes, only the most recent “parcel” (bundle) of votes that put them over the quota is transferred. This simulates the practice used in some senatorial elections. Only surpluses of candidates elected in this stage are processed; previously elected winners are not revisited later. Only the NB (rational Droop) quota is implemented for this variant
The last parcel rule can produce different results than standard STV because it treats different bundles of votes differently based on when they arrived at the candidate.
Ballot ties are supported by equal splitting of weight.
References: Tideman (“The Single Transferable Vote”, 1995) and Tideman & Richardson (“Better voting methods through technology: The refinement-manageability trade-off in the single transferable vote”, 2000).
- Parameters:
profile – A Profile or ProfileWithTies object containing voter rankings
num_seats (int) – Number of seats to fill
curr_cands – List of candidates to consider, defaults to all candidates in profile
quota_rule (str) – Quota calculation rule, defaults to “nb” (rational Droop)
tie_break_key – Function for tie-breaking, defaults to None
- Returns:
List of elected candidates
- Return type:
list
Warning
STV implementations have not yet been thoroughly vetted.
STV-Meek¶
- pref_voting.proportional_methods.stv_meek(profile, num_seats=2, curr_cands=None, tol=1e-10, max_iter=2000, tie_break_key=None)[source]¶
Meek Single Transferable Vote using retention factors for surplus handling.
Based on Hill, Wichmann, Woodall (1987) ‘Algorithm 123: Single Transferable Vote by Meek’s Method’
The algorithm: 1. Start with all candidates as hopeful (keep = 1) 2. Iterate:
Compute tallies using current keep factors
Compute quota = (total - excess) / (k+1)
For elected candidates, adjust keep factors so their tally approaches quota
Check if any hopeful candidate has tally >= quota
If yes, mark them as elected
If no hopeful candidate elected and no more adjustments needed: - If #elected == k, done - Else, exclude the hopeful candidate with lowest tally (set keep = 0)
Return elected candidates
References: Hill, Wichmann, Woodall (1987) ‘Algorithm 123: Single Transferable Vote by Meek’s Method’, Tideman (“The Single Transferable Vote”, 1995) and Tideman & Richardson (“Better voting methods through technology: The refinement-manageability trade-off in the single transferable vote”, 2000).
- Parameters:
profile – A Profile or ProfileWithTies object containing voter rankings
num_seats (int) – Number of seats to fill
curr_cands – List of candidates to consider, defaults to all candidates in profile
tol (float) – Tolerance for convergence, defaults to 1e-10
max_iter (int) – Maximum number of iterations, defaults to 2000
tie_break_key – Function for tie-breaking, defaults to None
- Returns:
List of elected candidates
- Return type:
list
Warning
Meek STV implementation has not yet been thoroughly vetted for correctness.
STV-Warren¶
- pref_voting.proportional_methods.stv_warren(profile, num_seats=2, curr_cands=None, tol=1e-10, max_iter=2000, tie_break_key=None)[source]¶
Warren’s STV implementation based on additive prices.
Based on Hill & Warren (2005) “Meek versus Warren”, Voting Matters Issue 20, and Tideman (1995), Tideman & Richardson (2000).
Warren’s method uses additive “portions apportioned” (prices): - Each elected candidate has a price p_c - A voter contributes min(remaining_vote, p_c) to each elected candidate in preference order - The remaining vote after all elected candidates goes to the first hopeful candidate
The key difference from Meek: - Meek: Each candidate keeps a FRACTION of what’s passed to them (multiplicative) - Warren: Each candidate takes a FIXED PRICE from the remaining vote (additive)
The algorithm: 1. Start with all candidates as hopeful 2. Iterate:
Flow each ballot through candidates in preference order
At each elected candidate, deduct min(remaining, price) from the ballot
The remaining weight goes to the first hopeful candidate
Compute tallies for all candidates
Compute quota = (total - excess) / (k+1)
For elected candidates, adjust their prices so their tallies approach quota
Check if any hopeful candidate has tally >= quota
If yes, mark them as elected (with initial price = 1.0)
If no hopeful candidate elected and no more adjustments needed: - If #elected == k, done - Else, exclude the hopeful candidate with lowest tally
References: Hill & Warren (2005) “Meek versus Warren”, Voting Matters Issue 20, Tideman (“The Single Transferable Vote”, 1995) and Tideman & Richardson (“Better voting methods through technology: The refinement-manageability trade-off in the single transferable vote”, 2000).
- Parameters:
profile – A Profile or ProfileWithTies object containing voter rankings
num_seats (int) – Number of seats to fill
curr_cands – List of candidates to consider, defaults to all candidates in profile
tol (float) – Tolerance for convergence, defaults to 1e-10
max_iter (int) – Maximum number of iterations, defaults to 2000
tie_break_key – Function for tie-breaking, defaults to None
- Returns:
List of elected candidates
- Return type:
list
Warning
Warren STV implementation has not yet been thoroughly vetted for correctness.
Approval-STV¶
- pref_voting.proportional_methods.approval_stv(profile, num_seats=2, curr_cands=None, quota_rule='droop', select_tiebreak=None, elim_tiebreak=None, rng=None)[source]¶
Approval-STV (Delemazure & Peters 2024, https://arxiv.org/abs/2404.11407):
In each round, a ballot supports all candidates it ranks top among the remaining candidates. Let B_i be the remaining budget of ballot i (start at 1 per voter). Let q be the quota.
- Loop until k winners:
For every continuing candidate c, compute support S(c) = sum_{i: c in top_i} B_i.
If some c has enough support (strictly > q for Droop; >= q for Hare): Elect such a c (by default the one with largest S); charge supporters exactly q in total by multiplying each supporter’s budget by (S(c) - q)/S(c) (Gregory); remove c.
Otherwise eliminate a candidate with the smallest S(c); remove it.
Quotas¶
quota_rule=”droop” -> q = n / (k+1), elect if support > q quota_rule=”droop_int” -> q = floor(n / (k+1)) + 1, elect if support > q quota_rule=”hare” -> q = n / k, elect if support >= q
Notes
Matches the budget-flow pseudocode in Fig. 12 (Approval-STV) using Gregory charging.
Equals Approval-IRV when k=1 with Hare quota (but not with Droop; see Remark 5.1).
- param profile:
A Profile or ProfileWithTies object containing voter rankings
- param num_seats:
Number of seats to fill
- type num_seats:
int
- param curr_cands:
List of candidates to consider, defaults to all candidates in profile
- param quota_rule:
Quota rule to use, defaults to “droop”
- type quota_rule:
str
- param select_tiebreak:
Function for tie-breaking, defaults to None
- param elim_tiebreak:
Function for tie-breaking, defaults to None
- param rng:
Random number generator, defaults to None
- returns:
List of elected candidates
- rtype:
list
Warning
Approval-STV implementation has not yet been thoroughly vetted.
CPO-STV¶
- pref_voting.proportional_methods.cpo_stv(profile, num_seats=2, curr_cands=None, inpair_surplus='meek', fallback_vm=<pref_voting.voting_method.VotingMethod object>, rng=None)[source]¶
CPO-STV (Comparison of Pairs of Outcomes) - a Condorcet-consistent proportional method.
Unlike traditional STV which eliminates candidates sequentially, CPO-STV considers all possible committees (combinations) of the required size and compares them pairwise.
For any two k-member sets A and B, restrict each ballot to S = A union B, allocate the ballot to its highest ranked available candidate in S, and then transfer only the surpluses of candidates in the intersection I = A intersect B (never from candidates who appear in only one of the two compared sets). The margin of A vs. B is the sum of votes for A’s members minus the sum for B’s.
Within each A vs B comparison, the quota is q = U/(k+1), where k = |A| and U is the total weight currently credited to candidates in S (i.e., not yet exhausted relative to S). When, at the point of transfer, a ballot has no remaining ranked candidate in S, its remaining weight is treated as exhausted for this comparison, which reduces U on subsequent iterations.
The winning committee is the one that beats all other possible committees in these pairwise comparisons. This makes CPO-STV “Condorcet-consistent” - if there’s a committee that is majority-preferred to every other committee, CPO-STV will find it. If there is no such Condorcet committee, then the fallback voting method is used to pick the winning committee based on the pairwise margins between committees.
This method is computationally intensive as it must examine C(candidates, seats) committees.
References: Tideman (“The Single Transferable Vote”, 1995) and Tideman & Richardson (“Better voting methods through technology: The refinement-manageability trade-off in the single transferable vote”, 2000).
- Parameters:
profile – A Profile or ProfileWithTies object containing voter rankings
num_seats (int) – Number of seats to fill
curr_cands – List of candidates to consider, defaults to all candidates in profile
inpair_surplus (str) – Surplus handling method for pairwise comparisons, defaults to “meek”
fallback_vm – Fallback voting method for tie-breaking, defaults to minimax
rng – Random number generator for tie-breaking, defaults to Python’s random module
- Returns:
List of elected candidates forming the winning committee
- Return type:
list
Warning
This implementation of CPO-STV has not yet been thoroughly vetted.