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:

  1. Compute tallies using current keep factors

  2. Compute quota = (total - excess) / (k+1)

  3. For elected candidates, adjust keep factors so their tally approaches quota

  4. Check if any hopeful candidate has tally >= quota

  5. If yes, mark them as elected

  6. 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)

  1. 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:

  1. Flow each ballot through candidates in preference order

  2. At each elected candidate, deduct min(remaining, price) from the ballot

  3. The remaining weight goes to the first hopeful candidate

  4. Compute tallies for all candidates

  5. Compute quota = (total - excess) / (k+1)

  6. For elected candidates, adjust their prices so their tallies approach quota

  7. Check if any hopeful candidate has tally >= quota

  8. If yes, mark them as elected (with initial price = 1.0)

  9. 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:
  1. For every continuing candidate c, compute support S(c) = sum_{i: c in top_i} B_i.

  2. 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.

  3. 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.