diff --git a/html/index.html b/html/index.html index 60d7f84..ecea932 100644 --- a/html/index.html +++ b/html/index.html @@ -40,6 +40,7 @@ + @@ -92,10 +93,11 @@
diff --git a/html/index.js b/html/index.js index 8c797d6..c84a18a 100644 --- a/html/index.js +++ b/html/index.js @@ -50,11 +50,21 @@ function changePreset() { document.getElementById('selQuotaCriterion').value = 'geq'; document.getElementById('selQuota').value = 'droop'; document.getElementById('chkProgQuota').checked = false; - document.getElementById('chkBulkExclusion').checked = false; + document.getElementById('chkBulkExclusion').checked = true; document.getElementById('selNumbers').value = 'int'; document.getElementById('selSurplus').value = 'order'; document.getElementById('selTransfers').value = 'uig'; document.getElementById('selTies').value = 'backwards_random'; + } else if (document.getElementById('selPreset').value === 'wright') { + document.getElementById('selQuotaCriterion').value = 'geq'; + document.getElementById('selQuota').value = 'droop'; + document.getElementById('chkProgQuota').checked = false; + document.getElementById('chkBulkExclusion').checked = true; + document.getElementById('selNumbers').value = 'fixed'; + document.getElementById('txtDP').value = '5'; + document.getElementById('selSurplus').value = 'size'; + document.getElementById('selTransfers').value = 'wright'; + document.getElementById('selTies').value = 'backwards_random'; } } diff --git a/html/worker.js b/html/worker.js index 7c688aa..ea335de 100644 --- a/html/worker.js +++ b/html/worker.js @@ -41,10 +41,12 @@ onmessage = function(evt) { // Create counter let counter; - if (evt.data.transfers === 'wig') { - counter = py.pyRCV2.method.base_stv.BaseWIGSTVCounter(election, evt.data.options); - } else { + if (evt.data.transfers === 'uig') { counter = py.pyRCV2.method.base_stv.BaseUIGSTVCounter(election, evt.data.options); + } else if (evt.data.transfers === 'wright') { + counter = py.pyRCV2.method.wright.WrightSTVCounter(election, evt.data.options); + } else { + counter = py.pyRCV2.method.base_stv.BaseWIGSTVCounter(election, evt.data.options); } // Reset diff --git a/pyRCV2/method/base_stv.py b/pyRCV2/method/base_stv.py index ccf5fae..1e69150 100644 --- a/pyRCV2/method/base_stv.py +++ b/pyRCV2/method/base_stv.py @@ -192,11 +192,7 @@ class BaseSTVCounter: Exclude the lowest ranked hopeful """ - hopefuls = [(c, cc) for c, cc in self.candidates.items() if cc.state == CandidateState.HOPEFUL] - hopefuls.sort(lambda x: x[1].votes) - - # TODO: Handle ties - candidate_excluded, count_card = hopefuls[0] + candidate_excluded, count_card = self.candidate_to_exclude() count_card.state = CandidateState.EXCLUDED # Exclude this candidate @@ -221,6 +217,19 @@ class BaseSTVCounter: ) __pragma__('noopov') + def candidate_to_exclude(self): + """ + Determine the candidate to exclude + """ + + hopefuls = [(c, cc) for c, cc in self.candidates.items() if cc.state == CandidateState.HOPEFUL] + hopefuls.sort(lambda x: x[1].votes) + + # TODO: Handle ties + candidate_excluded, count_card = hopefuls[0] + + return candidate_excluded, count_card + def do_exclusion(self, candidate_excluded, count_card): """ Exclude the given candidate and transfer the votes diff --git a/pyRCV2/method/wright.py b/pyRCV2/method/wright.py new file mode 100644 index 0000000..c3ef75a --- /dev/null +++ b/pyRCV2/method/wright.py @@ -0,0 +1,56 @@ +# 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 . + +from pyRCV2.method.base_stv import BaseWIGSTVCounter +from pyRCV2.model import CandidateState, CountCard +from pyRCV2.safedict import SafeDict + +class WrightSTVCounter(BaseWIGSTVCounter): + """ + 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 + 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 + + result = self.reset() + result.comment = 'Exclusion of ' + candidate_excluded.name + + return result diff --git a/pyRCV2/transcrypt.py b/pyRCV2/transcrypt.py index 20795f0..aa09038 100644 --- a/pyRCV2/transcrypt.py +++ b/pyRCV2/transcrypt.py @@ -16,7 +16,7 @@ import pyRCV2.blt import pyRCV2.model -import pyRCV2.method, pyRCV2.method.base_stv +import pyRCV2.method, pyRCV2.method.base_stv, pyRCV2.method.wright import pyRCV2.numbers __pragma__('js', '{}', 'export {pyRCV2, isinstance, repr, str};')