Make Wright method a variant of regular STV as an exclusion method

This commit is contained in:
RunasSudo 2021-01-03 18:57:56 +11:00
parent 2bcc52d85f
commit 61601f05cc
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
10 changed files with 47 additions and 82 deletions

View File

@ -66,10 +66,9 @@ Some STV counting rules provide, for example, that ‘no surplus shall be transf
## Method (-m/--method)
This dropdown allows you to select how ballots are transferred during surplus transfers or exclusions. The 2 recommended methods are:
This dropdown allows you to select how ballots are transferred during surplus transfers or exclusions. The recommended method is:
* Weighted inclusive Gregory: During surplus transfers, all applicable ballot papers of the transferring candidate are examined. Transfers are weighted according to the weights of the ballot papers.
* Wright STV: Same as weighted inclusive Gregory, but when a candidate is excluded, the count is reset from the beginning (minus the excluded candidate).
Other methods are supported, but not recommended:
@ -88,6 +87,7 @@ Other surplus transfer methods, such as non-fractional transfers (e.g. random sa
* Exclude in one round (default): When excluding a candidate, transfer all their ballot papers in one round.
* Exclude by parcel (by order): When excluding a candidate, transfer their ballot papers one parcel at a time, in their order each was received. Each parcel forms a separate round, i.e. if a transfer allows another candidate to meet the quota criterion, no further papers are transferred to that candidate.
* Exclude by value: When excluding a candidate, transfer their ballot papers in descending order of accumulated transfer value. Each transfer of all ballots of a certain transfer value forms a separate round.
* Wright method (re-iterate): When a candidate is excluded, the count is reset from the beginning (minus the excluded candidate).
## Ties (-t/--ties)

View File

@ -147,6 +147,7 @@
<option value="parcels_by_order">Exclude by parcel (by order)</option>
<!--<option value="parcels_by_value">Exclude by parcel (by value)</option>-->
<option value="by_value">Exclude by value</option>
<option value="wright">Wright method (re-iterate)</option>
</select>
</label>
<br>

View File

@ -83,9 +83,9 @@ function changePreset() {
document.getElementById('chkRoundVotes').checked = false;
document.getElementById('chkRoundWeights').checked = false;
document.getElementById('selSurplus').value = 'size';
document.getElementById('selTransfers').value = 'wright';
document.getElementById('selTransfers').value = 'wig';
document.getElementById('selPapers').value = 'both';
document.getElementById('selExclusion').value = 'one_round';
document.getElementById('selExclusion').value = 'wright';
document.getElementById('selTies').value = 'backwards_random';
} else if (document.getElementById('selPreset').value === 'prsa77') {
document.getElementById('selQuotaCriterion').value = 'geq';

View File

@ -46,8 +46,6 @@ onmessage = function(evt) {
counter = py.pyRCV2.method.base_stv.UIGSTVCounter(election, evt.data.data.options);
} else if (evt.data.data.transfers === 'eg') {
counter = py.pyRCV2.method.base_stv.EGSTVCounter(election, evt.data.data.options);
} else if (evt.data.data.transfers === 'wright') {
counter = py.pyRCV2.method.wright.WrightSTVCounter(election, evt.data.data.options);
} else {
counter = py.pyRCV2.method.base_stv.WIGSTVCounter(election, evt.data.data.options);
}

View File

@ -1,5 +1,5 @@
# pyRCV2: Preferential vote counting
# Copyright © 2020 Lee Yingtong Li (RunasSudo)
# Copyright © 2020–2021 Lee Yingtong Li (RunasSudo)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
@ -19,7 +19,6 @@ import pyRCV2.model
import pyRCV2.numbers
from pyRCV2.method.base_stv import UIGSTVCounter, WIGSTVCounter, EGSTVCounter
from pyRCV2.method.wright import WrightSTVCounter
from pyRCV2.ties import TiesBackwards, TiesPrompt, TiesRandom
import sys
@ -37,9 +36,9 @@ def add_parser(subparsers):
parser.add_argument('--round-votes', type=int, help='round votes to specified decimal places')
parser.add_argument('--round-weights', type=int, help='round ballot weights to specified decimal places')
parser.add_argument('--surplus-order', '-s', choices=['size', 'order'], default='size', help='whether to distribute surpluses by size or by order of election (default: size)')
parser.add_argument('--method', '-m', choices=['wig', 'uig', 'eg', 'wright'], default='wig', help='method of surpluses and exclusions (default: wig - weighted inclusive Gregory)')
parser.add_argument('--method', '-m', choices=['wig', 'uig', 'eg'], default='wig', help='method of transferring surpluses (default: wig - weighted inclusive Gregory)')
parser.add_argument('--transferable-only', action='store_true', help='examine only transferable papers during surplus distributions')
parser.add_argument('--exclusion', choices=['one_round', 'parcels_by_order', 'by_value'], default='one_round', help='whether to perform exclusions in one round or parcel by parcel (default: one_round)')
parser.add_argument('--exclusion', choices=['one_round', 'parcels_by_order', 'by_value', 'wright'], default='one_round', help='how to perform exclusions (default: one_round)')
parser.add_argument('--ties', '-t', action='append', choices=['backwards', 'prompt', 'random'], default=None, help='how to resolve ties (default: backwards then random)')
parser.add_argument('--random-seed', default=None, help='arbitrary string used to seed the RNG for random tie breaking')
@ -85,8 +84,6 @@ def main(args):
counter = UIGSTVCounter(election, vars(args))
elif args.method == 'eg':
counter = EGSTVCounter(election, vars(args))
elif args.method == 'wright':
counter = WrightSTVCounter(election, vars(args))
else:
counter = WIGSTVCounter(election, vars(args))

View File

@ -1,5 +1,5 @@
# pyRCV2: Preferential vote counting
# Copyright © 2020 Lee Yingtong Li (RunasSudo)
# Copyright © 2020–2021 Lee Yingtong Li (RunasSudo)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
@ -67,7 +67,7 @@ class BaseSTVCounter:
'quota_criterion': 'geq', # 'geq' or 'gt'
'surplus_order': 'size', # 'size' or 'order'
'papers': 'both', # 'both' or 'transferable'
'exclusion': 'one_round', # 'one_round', 'parcels_by_order' or 'by_value'
'exclusion': 'one_round', # 'one_round', 'parcels_by_order', 'by_value' or 'wright'
'ties': [], # List of tie strategies (e.g. TiesRandom)
'round_votes': None, # Number of decimal places or None
'round_weights': None, # Number of decimal places or None
@ -315,6 +315,37 @@ class BaseSTVCounter:
candidate_excluded, count_card = self.candidate_to_exclude()
count_card.state = CandidateState.EXCLUDING
# Handle Wright STV
if self.options['exclusion'] == 'wright':
count_card.state = CandidateState.EXCLUDED
# Reset the count
# Carry over certain candidate states
new_candidates = SafeDict()
for candidate, count_card in self.candidates.items():
new_count_card = CountCard()
if count_card.state == CandidateState.WITHDRAWN:
new_count_card.state = CandidateState.WITHDRAWN
elif count_card.state == CandidateState.EXCLUDED:
new_count_card.state = CandidateState.EXCLUDED
__pragma__('opov')
new_candidates[candidate] = new_count_card
__pragma__('noopov')
self.candidates = new_candidates
self.exhausted = CountCard()
self.loss_fraction = CountCard()
self.num_elected = 0
step_results = self.step_results # Carry over step results
result = self.reset()
self.step_results = step_results
result.comment = 'Exclusion of ' + candidate_excluded.name
return result
# Exclude this candidate
self.do_exclusion(candidate_excluded, count_card)

View File

@ -1,61 +0,0 @@
# pyRCV2: Preferential vote counting
# Copyright © 2020 Lee Yingtong Li (RunasSudo)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
__pragma__ = lambda x: None
from pyRCV2.method.base_stv import WIGSTVCounter
from pyRCV2.model import CandidateState, CountCard
from pyRCV2.safedict import SafeDict
class WrightSTVCounter(WIGSTVCounter):
"""
Wright STV implementation
"""
def exclude_candidate(self):
"""
Overrides BaseSTVCounter.exclude_candidate per Wright STV
"""
candidate_excluded, count_card = self.candidate_to_exclude()
count_card.state = CandidateState.EXCLUDED
# Reset the count
# Carry over certain candidate states
new_candidates = SafeDict()
for candidate, count_card in self.candidates.items():
new_count_card = CountCard()
if count_card.state == CandidateState.WITHDRAWN:
new_count_card.state = CandidateState.WITHDRAWN
elif count_card.state == CandidateState.EXCLUDED:
new_count_card.state = CandidateState.EXCLUDED
__pragma__('opov')
new_candidates[candidate] = new_count_card
__pragma__('noopov')
self.candidates = new_candidates
self.exhausted = CountCard()
self.loss_fraction = CountCard()
self.num_elected = 0
step_results = self.step_results # Carry over step results
result = self.reset()
self.step_results = step_results
result.comment = 'Exclusion of ' + candidate_excluded.name
return result

View File

@ -16,7 +16,7 @@
import pyRCV2.blt
import pyRCV2.model
import pyRCV2.method, pyRCV2.method.base_stv, pyRCV2.method.wright
import pyRCV2.method, pyRCV2.method.base_stv
import pyRCV2.numbers
import pyRCV2.random
import pyRCV2.ties

View File

@ -1,5 +1,5 @@
# pyRCV2: Preferential vote counting
# Copyright © 2020 Lee Yingtong Li (RunasSudo)
# Copyright © 2020–2021 Lee Yingtong Li (RunasSudo)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
@ -17,7 +17,6 @@
import pyRCV2.blt
import pyRCV2.numbers
import pyRCV2.method.base_stv
import pyRCV2.method.wright
from pyRCV2.model import CountCompleted
import json
@ -63,4 +62,4 @@ def maketst(numbers, counter_cls, options):
test_prsa1_scottish_py, test_prsa1_scottish_js = maketst('Fixed', 'pyRCV2.method.base_stv.WIGSTVCounter', {})
test_prsa1_stvc_py, test_prsa1_stvc_js = maketst('Rational', 'pyRCV2.method.base_stv.WIGSTVCounter', {'quota': 'droop_exact', 'quota_criterion': 'gt', 'prog_quota': True})
test_prsa1_senate_py, test_prsa1_senate_js = maketst('Fixed', 'pyRCV2.method.base_stv.UIGSTVCounter', {'surplus_order': 'order', 'exclusion': 'by_value'})
test_prsa1_wright_py, test_prsa1_wright_js = maketst('Fixed', 'pyRCV2.method.wright.WrightSTVCounter', {})
test_prsa1_wright_py, test_prsa1_wright_js = maketst('Fixed', 'pyRCV2.method.base_stv.WIGSTVCounter', {'exclusion': 'wright'})

View File

@ -1,5 +1,5 @@
# pyRCV2: Preferential vote counting
# Copyright © 2020 Lee Yingtong Li (RunasSudo)
# Copyright © 2020–2021 Lee Yingtong Li (RunasSudo)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
@ -19,7 +19,7 @@ from pytest_steps import test_steps
import pyRCV2.blt
import pyRCV2.numbers
from pyRCV2.method.wright import WrightSTVCounter
from pyRCV2.method.base_stv import WIGSTVCounter
from pyRCV2.model import CandidateState, CountCompleted
def count_until_exclude(counter):
@ -43,7 +43,7 @@ def test_csm15():
cands = {c.name: c for c in election.candidates}
counter = WrightSTVCounter(election)
counter = WIGSTVCounter(election, {'exclusion': 'wright'})
# Round 1
result = counter.reset()