Implement random breaking of ties
This commit is contained in:
parent
e3edc80f2b
commit
06ab133615
@ -103,15 +103,15 @@
|
||||
</label>
|
||||
<br>
|
||||
<label>
|
||||
Ties (NYI):
|
||||
Ties:
|
||||
<select id="selTies">
|
||||
<option value="backwards_random" selected>Backwards then random</option>
|
||||
<option value="backwards_random" selected>Backwards then random (NYI)</option>
|
||||
<option value="random">Random</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Random seed:
|
||||
<input type="text" id="txtSeed" value="Not yet implemented">
|
||||
<input type="text" id="txtSeed" value="">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@ -119,6 +119,7 @@
|
||||
|
||||
<script src="http://peterolson.github.com/BigRational.js/BigInt_BigRat.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/big.js@6.0.0/big.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sjcl@1.0.8/sjcl.min.js"></script>
|
||||
<script src="bundle.js"></script>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
|
@ -278,6 +278,14 @@ async function clickCount() {
|
||||
'surplus_order': document.getElementById('selSurplus').value,
|
||||
'ties': document.getElementById('selTies').value
|
||||
},
|
||||
'seed': document.getElementById('txtSeed').value,
|
||||
'data': text
|
||||
});
|
||||
}
|
||||
|
||||
// Provide a default seed
|
||||
if (document.getElementById('txtSeed').value === '') {
|
||||
function pad(x) { if (x < 10) { return '0' + x; } return '' + x; }
|
||||
let d = new Date();
|
||||
document.getElementById('txtSeed').value = d.getFullYear() + pad(d.getMonth() + 1) + pad(d.getDate());
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
importScripts('http://peterolson.github.com/BigRational.js/BigInt_BigRat.min.js', 'https://cdn.jsdelivr.net/npm/big.js@6.0.0/big.min.js', 'bundle.js');
|
||||
importScripts('http://peterolson.github.com/BigRational.js/BigInt_BigRat.min.js', 'https://cdn.jsdelivr.net/npm/big.js@6.0.0/big.min.js', 'https://cdn.jsdelivr.net/npm/sjcl@1.0.8/sjcl.min.js', 'bundle.js');
|
||||
|
||||
onmessage = function(evt) {
|
||||
// Set settings
|
||||
@ -50,12 +50,10 @@ onmessage = function(evt) {
|
||||
}
|
||||
|
||||
if (evt.data.options['ties'] === 'backwards_random') {
|
||||
counter.options['ties'] = [py.pyRCV2.ties.TiesBackwards(), py.pyRCV2.ties.TiesRandom()];
|
||||
counter.options['ties'] = [py.pyRCV2.ties.TiesBackwards(), py.pyRCV2.ties.TiesRandom(evt.data.seed)];
|
||||
} else if (evt.data.options['ties'] === 'random') {
|
||||
counter.options['ties'] = [py.pyRCV2.ties.TiesRandom()];
|
||||
} //else if (evt.data.options['ties'] === 'prompt') {
|
||||
// counter.options['ties'] = [py.pyRCV2.ties.TiesPrompt()];
|
||||
//}
|
||||
counter.options['ties'] = [py.pyRCV2.ties.TiesRandom(evt.data.seed)];
|
||||
}
|
||||
|
||||
// Reset
|
||||
let result = counter.reset();
|
||||
|
@ -22,6 +22,8 @@ from pyRCV2.method.base_stv import BaseUIGSTVCounter, BaseWIGSTVCounter
|
||||
from pyRCV2.method.wright import WrightSTVCounter
|
||||
from pyRCV2.ties import TiesBackwards, TiesPrompt, TiesRandom
|
||||
|
||||
import sys
|
||||
|
||||
def add_parser(subparsers):
|
||||
parser = subparsers.add_parser('stv', help='single transferable vote')
|
||||
parser.add_argument('file', help='path to BLT file')
|
||||
@ -34,6 +36,7 @@ def add_parser(subparsers):
|
||||
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', 'wright'], default='wig', help='method of surpluses and exclusions (default: wig - weighted inclusive Gregory)')
|
||||
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')
|
||||
|
||||
def print_step(result):
|
||||
print(result.comment)
|
||||
@ -92,7 +95,10 @@ def main(args):
|
||||
elif t == 'prompt':
|
||||
counter.options['ties'].append(TiesPrompt())
|
||||
elif t == 'random':
|
||||
counter.options['ties'].append(TiesRandom())
|
||||
if args.random_seed is None:
|
||||
print('A --random-seed is required to use random tie breaking')
|
||||
sys.exit(1)
|
||||
counter.options['ties'].append(TiesRandom(args.random_seed))
|
||||
|
||||
# Reset
|
||||
result = counter.reset()
|
||||
|
@ -19,7 +19,6 @@ __pragma__ = lambda x: None
|
||||
from pyRCV2.model import CandidateState, CountCard, CountCompleted, CountStepResult
|
||||
from pyRCV2.numbers import Num, Rational
|
||||
from pyRCV2.safedict import SafeDict
|
||||
from pyRCV2.ties import TiesBackwards, TiesPrompt, TiesRandom
|
||||
|
||||
class STVException(Exception):
|
||||
pass
|
||||
@ -40,7 +39,7 @@ class BaseSTVCounter:
|
||||
'quota': 'droop', # 'droop', 'droop_exact', 'hare' or 'hare_exact'
|
||||
'quota_criterion': 'geq', # 'geq' or 'gt'
|
||||
'surplus_order': 'size', # 'size' or 'order'
|
||||
'ties': [TiesBackwards(), TiesRandom()]
|
||||
'ties': []
|
||||
}
|
||||
|
||||
if options is not None:
|
||||
@ -335,7 +334,10 @@ class BaseSTVCounter:
|
||||
if len(l) == 1:
|
||||
return l[0]
|
||||
|
||||
tied = [(c, cc) for c, cc in l if cc.votes == l[0][1].votes]
|
||||
__pragma__('opov')
|
||||
# Do not use (c, cc) for c, cc in ... as this will break equality in JS
|
||||
tied = [x for x in l if x[1].votes == l[0][1].votes]
|
||||
__pragma__('noopov')
|
||||
|
||||
if len(tied) == 1:
|
||||
return tied[0]
|
||||
@ -356,7 +358,10 @@ class BaseSTVCounter:
|
||||
if len(l) == 1:
|
||||
return l[0]
|
||||
|
||||
tied = [(c, cc) for c, cc in l if cc.votes == l[0][1].votes]
|
||||
__pragma__('opov')
|
||||
# Do not use (c, cc) for c, cc in ... as this will break equality in JS
|
||||
tied = [x for x in l if x[1].votes == l[0][1].votes]
|
||||
__pragma__('noopov')
|
||||
|
||||
if len(tied) == 1:
|
||||
return tied[0]
|
||||
|
28
pyRCV2/random/__init__.py
Normal file
28
pyRCV2/random/__init__.py
Normal file
@ -0,0 +1,28 @@
|
||||
# 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
|
||||
is_py = False
|
||||
__pragma__('skip')
|
||||
is_py = True
|
||||
__pragma__('noskip')
|
||||
|
||||
if is_py:
|
||||
__pragma__('skip')
|
||||
from pyRCV2.random.sharandom_py import SHARandom
|
||||
__pragma__('noskip')
|
||||
else:
|
||||
from pyRCV2.random.sharandom_js import SHARandom
|
32
pyRCV2/random/sharandom_js.py
Normal file
32
pyRCV2/random/sharandom_js.py
Normal file
@ -0,0 +1,32 @@
|
||||
# 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/>.
|
||||
|
||||
class SHARandom:
|
||||
MAX_VAL = bigInt(2).pow(256).subtract(1)
|
||||
|
||||
def __init__(self, seed):
|
||||
self.seed = seed
|
||||
self.ctr = 0
|
||||
|
||||
def next(self, modulus):
|
||||
val = bigInt(sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(self.seed + ',' + str(self.ctr))), 16)
|
||||
self.ctr += 1
|
||||
|
||||
if val.greaterOrEquals(SHARandom.MAX_VAL.divide(modulus).multiply(modulus)):
|
||||
# Discard this value to avoid bias
|
||||
return self.next(modulus)
|
||||
|
||||
return val.mod(modulus)
|
36
pyRCV2/random/sharandom_py.py
Normal file
36
pyRCV2/random/sharandom_py.py
Normal file
@ -0,0 +1,36 @@
|
||||
# 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/>.
|
||||
|
||||
import hashlib
|
||||
|
||||
class SHARandom:
|
||||
MAX_VAL = 2 ** 256 - 1
|
||||
|
||||
def __init__(self, seed):
|
||||
self.seed = seed
|
||||
self.ctr = 0
|
||||
|
||||
def next(self, modulus):
|
||||
c = hashlib.sha256()
|
||||
c.update((self.seed + ',' + str(self.ctr)).encode('utf-8'))
|
||||
self.ctr += 1
|
||||
val = int.from_bytes(c.digest(), byteorder='big', signed=False)
|
||||
|
||||
if val >= (SHARandom.MAX_VAL // modulus) * modulus:
|
||||
# Discard this value to avoid bias
|
||||
return self.next(modulus)
|
||||
|
||||
return val % modulus
|
@ -20,6 +20,8 @@ __pragma__('skip')
|
||||
is_py = True
|
||||
__pragma__('noskip')
|
||||
|
||||
from pyRCV2.random import SHARandom
|
||||
|
||||
class TiesPrompt:
|
||||
"""Prompt the user to break ties"""
|
||||
|
||||
@ -71,8 +73,13 @@ class TiesBackwards:
|
||||
raise Exception('Not yet implemented')
|
||||
|
||||
class TiesRandom:
|
||||
def __init__(self, seed):
|
||||
self.random = SHARandom(seed)
|
||||
|
||||
def choose_lowest(self, l):
|
||||
raise Exception('Not yet implemented')
|
||||
l.sort(key=lambda x: x[0].name)
|
||||
return l[self.random.next(len(l))]
|
||||
|
||||
def choose_highest(self, l):
|
||||
raise Exception('Not yet implemented')
|
||||
l.sort(key=lambda x: x[0].name)
|
||||
return l[self.random.next(len(l))]
|
||||
|
@ -18,6 +18,7 @@ import pyRCV2.blt
|
||||
import pyRCV2.model
|
||||
import pyRCV2.method, pyRCV2.method.base_stv, pyRCV2.method.wright
|
||||
import pyRCV2.numbers
|
||||
import pyRCV2.random
|
||||
import pyRCV2.ties
|
||||
|
||||
__pragma__('js', '{}', 'export {pyRCV2, isinstance, repr, str};')
|
||||
|
Reference in New Issue
Block a user