Make Wright method a variant of regular STV as an exclusion method
This commit is contained in:
parent
2bcc52d85f
commit
61601f05cc
@ -66,10 +66,9 @@ Some STV counting rules provide, for example, that ‘no surplus shall be transf
|
|||||||
|
|
||||||
## Method (-m/--method)
|
## 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.
|
* 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:
|
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 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 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.
|
* 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)
|
## Ties (-t/--ties)
|
||||||
|
|
||||||
|
@ -147,6 +147,7 @@
|
|||||||
<option value="parcels_by_order">Exclude by parcel (by order)</option>
|
<option value="parcels_by_order">Exclude by parcel (by order)</option>
|
||||||
<!--<option value="parcels_by_value">Exclude by parcel (by value)</option>-->
|
<!--<option value="parcels_by_value">Exclude by parcel (by value)</option>-->
|
||||||
<option value="by_value">Exclude by value</option>
|
<option value="by_value">Exclude by value</option>
|
||||||
|
<option value="wright">Wright method (re-iterate)</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<br>
|
<br>
|
||||||
|
@ -83,9 +83,9 @@ function changePreset() {
|
|||||||
document.getElementById('chkRoundVotes').checked = false;
|
document.getElementById('chkRoundVotes').checked = false;
|
||||||
document.getElementById('chkRoundWeights').checked = false;
|
document.getElementById('chkRoundWeights').checked = false;
|
||||||
document.getElementById('selSurplus').value = 'size';
|
document.getElementById('selSurplus').value = 'size';
|
||||||
document.getElementById('selTransfers').value = 'wright';
|
document.getElementById('selTransfers').value = 'wig';
|
||||||
document.getElementById('selPapers').value = 'both';
|
document.getElementById('selPapers').value = 'both';
|
||||||
document.getElementById('selExclusion').value = 'one_round';
|
document.getElementById('selExclusion').value = 'wright';
|
||||||
document.getElementById('selTies').value = 'backwards_random';
|
document.getElementById('selTies').value = 'backwards_random';
|
||||||
} else if (document.getElementById('selPreset').value === 'prsa77') {
|
} else if (document.getElementById('selPreset').value === 'prsa77') {
|
||||||
document.getElementById('selQuotaCriterion').value = 'geq';
|
document.getElementById('selQuotaCriterion').value = 'geq';
|
||||||
|
@ -46,8 +46,6 @@ onmessage = function(evt) {
|
|||||||
counter = py.pyRCV2.method.base_stv.UIGSTVCounter(election, evt.data.data.options);
|
counter = py.pyRCV2.method.base_stv.UIGSTVCounter(election, evt.data.data.options);
|
||||||
} else if (evt.data.data.transfers === 'eg') {
|
} else if (evt.data.data.transfers === 'eg') {
|
||||||
counter = py.pyRCV2.method.base_stv.EGSTVCounter(election, evt.data.data.options);
|
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 {
|
} else {
|
||||||
counter = py.pyRCV2.method.base_stv.WIGSTVCounter(election, evt.data.data.options);
|
counter = py.pyRCV2.method.base_stv.WIGSTVCounter(election, evt.data.data.options);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# pyRCV2: Preferential vote counting
|
# 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
|
# 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
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -19,7 +19,6 @@ import pyRCV2.model
|
|||||||
import pyRCV2.numbers
|
import pyRCV2.numbers
|
||||||
|
|
||||||
from pyRCV2.method.base_stv import UIGSTVCounter, WIGSTVCounter, EGSTVCounter
|
from pyRCV2.method.base_stv import UIGSTVCounter, WIGSTVCounter, EGSTVCounter
|
||||||
from pyRCV2.method.wright import WrightSTVCounter
|
|
||||||
from pyRCV2.ties import TiesBackwards, TiesPrompt, TiesRandom
|
from pyRCV2.ties import TiesBackwards, TiesPrompt, TiesRandom
|
||||||
|
|
||||||
import sys
|
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-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('--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('--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('--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('--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')
|
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))
|
counter = UIGSTVCounter(election, vars(args))
|
||||||
elif args.method == 'eg':
|
elif args.method == 'eg':
|
||||||
counter = EGSTVCounter(election, vars(args))
|
counter = EGSTVCounter(election, vars(args))
|
||||||
elif args.method == 'wright':
|
|
||||||
counter = WrightSTVCounter(election, vars(args))
|
|
||||||
else:
|
else:
|
||||||
counter = WIGSTVCounter(election, vars(args))
|
counter = WIGSTVCounter(election, vars(args))
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# pyRCV2: Preferential vote counting
|
# 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
|
# 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
|
# 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'
|
'quota_criterion': 'geq', # 'geq' or 'gt'
|
||||||
'surplus_order': 'size', # 'size' or 'order'
|
'surplus_order': 'size', # 'size' or 'order'
|
||||||
'papers': 'both', # 'both' or 'transferable'
|
'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)
|
'ties': [], # List of tie strategies (e.g. TiesRandom)
|
||||||
'round_votes': None, # Number of decimal places or None
|
'round_votes': None, # Number of decimal places or None
|
||||||
'round_weights': 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()
|
candidate_excluded, count_card = self.candidate_to_exclude()
|
||||||
count_card.state = CandidateState.EXCLUDING
|
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
|
# Exclude this candidate
|
||||||
self.do_exclusion(candidate_excluded, count_card)
|
self.do_exclusion(candidate_excluded, count_card)
|
||||||
|
|
||||||
|
@ -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
|
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import pyRCV2.blt
|
import pyRCV2.blt
|
||||||
import pyRCV2.model
|
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.numbers
|
||||||
import pyRCV2.random
|
import pyRCV2.random
|
||||||
import pyRCV2.ties
|
import pyRCV2.ties
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# pyRCV2: Preferential vote counting
|
# 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
|
# 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
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
@ -17,7 +17,6 @@
|
|||||||
import pyRCV2.blt
|
import pyRCV2.blt
|
||||||
import pyRCV2.numbers
|
import pyRCV2.numbers
|
||||||
import pyRCV2.method.base_stv
|
import pyRCV2.method.base_stv
|
||||||
import pyRCV2.method.wright
|
|
||||||
from pyRCV2.model import CountCompleted
|
from pyRCV2.model import CountCompleted
|
||||||
|
|
||||||
import json
|
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_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_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_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'})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# pyRCV2: Preferential vote counting
|
# 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
|
# 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
|
# 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.blt
|
||||||
import pyRCV2.numbers
|
import pyRCV2.numbers
|
||||||
from pyRCV2.method.wright import WrightSTVCounter
|
from pyRCV2.method.base_stv import WIGSTVCounter
|
||||||
from pyRCV2.model import CandidateState, CountCompleted
|
from pyRCV2.model import CandidateState, CountCompleted
|
||||||
|
|
||||||
def count_until_exclude(counter):
|
def count_until_exclude(counter):
|
||||||
@ -43,7 +43,7 @@ def test_csm15():
|
|||||||
|
|
||||||
cands = {c.name: c for c in election.candidates}
|
cands = {c.name: c for c in election.candidates}
|
||||||
|
|
||||||
counter = WrightSTVCounter(election)
|
counter = WIGSTVCounter(election, {'exclusion': 'wright'})
|
||||||
|
|
||||||
# Round 1
|
# Round 1
|
||||||
result = counter.reset()
|
result = counter.reset()
|
||||||
|
Reference in New Issue
Block a user