Implement random breaking of ties
This commit is contained in:
parent
e3edc80f2b
commit
06ab133615
@ -103,15 +103,15 @@
|
|||||||
</label>
|
</label>
|
||||||
<br>
|
<br>
|
||||||
<label>
|
<label>
|
||||||
Ties (NYI):
|
Ties:
|
||||||
<select id="selTies">
|
<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>
|
<option value="random">Random</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Random seed:
|
Random seed:
|
||||||
<input type="text" id="txtSeed" value="Not yet implemented">
|
<input type="text" id="txtSeed" value="">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -119,6 +119,7 @@
|
|||||||
|
|
||||||
<script src="http://peterolson.github.com/BigRational.js/BigInt_BigRat.min.js"></script>
|
<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/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="bundle.js"></script>
|
||||||
<script src="index.js"></script>
|
<script src="index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -278,6 +278,14 @@ async function clickCount() {
|
|||||||
'surplus_order': document.getElementById('selSurplus').value,
|
'surplus_order': document.getElementById('selSurplus').value,
|
||||||
'ties': document.getElementById('selTies').value
|
'ties': document.getElementById('selTies').value
|
||||||
},
|
},
|
||||||
|
'seed': document.getElementById('txtSeed').value,
|
||||||
'data': text
|
'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/>.
|
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) {
|
onmessage = function(evt) {
|
||||||
// Set settings
|
// Set settings
|
||||||
@ -50,12 +50,10 @@ onmessage = function(evt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (evt.data.options['ties'] === 'backwards_random') {
|
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') {
|
} else if (evt.data.options['ties'] === 'random') {
|
||||||
counter.options['ties'] = [py.pyRCV2.ties.TiesRandom()];
|
counter.options['ties'] = [py.pyRCV2.ties.TiesRandom(evt.data.seed)];
|
||||||
} //else if (evt.data.options['ties'] === 'prompt') {
|
}
|
||||||
// counter.options['ties'] = [py.pyRCV2.ties.TiesPrompt()];
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
let result = counter.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.method.wright import WrightSTVCounter
|
||||||
from pyRCV2.ties import TiesBackwards, TiesPrompt, TiesRandom
|
from pyRCV2.ties import TiesBackwards, TiesPrompt, TiesRandom
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
def add_parser(subparsers):
|
def add_parser(subparsers):
|
||||||
parser = subparsers.add_parser('stv', help='single transferable vote')
|
parser = subparsers.add_parser('stv', help='single transferable vote')
|
||||||
parser.add_argument('file', help='path to BLT file')
|
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('--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('--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('--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):
|
def print_step(result):
|
||||||
print(result.comment)
|
print(result.comment)
|
||||||
@ -92,7 +95,10 @@ def main(args):
|
|||||||
elif t == 'prompt':
|
elif t == 'prompt':
|
||||||
counter.options['ties'].append(TiesPrompt())
|
counter.options['ties'].append(TiesPrompt())
|
||||||
elif t == 'random':
|
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
|
# Reset
|
||||||
result = counter.reset()
|
result = counter.reset()
|
||||||
|
@ -19,7 +19,6 @@ __pragma__ = lambda x: None
|
|||||||
from pyRCV2.model import CandidateState, CountCard, CountCompleted, CountStepResult
|
from pyRCV2.model import CandidateState, CountCard, CountCompleted, CountStepResult
|
||||||
from pyRCV2.numbers import Num, Rational
|
from pyRCV2.numbers import Num, Rational
|
||||||
from pyRCV2.safedict import SafeDict
|
from pyRCV2.safedict import SafeDict
|
||||||
from pyRCV2.ties import TiesBackwards, TiesPrompt, TiesRandom
|
|
||||||
|
|
||||||
class STVException(Exception):
|
class STVException(Exception):
|
||||||
pass
|
pass
|
||||||
@ -40,7 +39,7 @@ class BaseSTVCounter:
|
|||||||
'quota': 'droop', # 'droop', 'droop_exact', 'hare' or 'hare_exact'
|
'quota': 'droop', # 'droop', 'droop_exact', 'hare' or 'hare_exact'
|
||||||
'quota_criterion': 'geq', # 'geq' or 'gt'
|
'quota_criterion': 'geq', # 'geq' or 'gt'
|
||||||
'surplus_order': 'size', # 'size' or 'order'
|
'surplus_order': 'size', # 'size' or 'order'
|
||||||
'ties': [TiesBackwards(), TiesRandom()]
|
'ties': []
|
||||||
}
|
}
|
||||||
|
|
||||||
if options is not None:
|
if options is not None:
|
||||||
@ -335,7 +334,10 @@ class BaseSTVCounter:
|
|||||||
if len(l) == 1:
|
if len(l) == 1:
|
||||||
return l[0]
|
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:
|
if len(tied) == 1:
|
||||||
return tied[0]
|
return tied[0]
|
||||||
@ -356,7 +358,10 @@ class BaseSTVCounter:
|
|||||||
if len(l) == 1:
|
if len(l) == 1:
|
||||||
return l[0]
|
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:
|
if len(tied) == 1:
|
||||||
return tied[0]
|
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
|
is_py = True
|
||||||
__pragma__('noskip')
|
__pragma__('noskip')
|
||||||
|
|
||||||
|
from pyRCV2.random import SHARandom
|
||||||
|
|
||||||
class TiesPrompt:
|
class TiesPrompt:
|
||||||
"""Prompt the user to break ties"""
|
"""Prompt the user to break ties"""
|
||||||
|
|
||||||
@ -71,8 +73,13 @@ class TiesBackwards:
|
|||||||
raise Exception('Not yet implemented')
|
raise Exception('Not yet implemented')
|
||||||
|
|
||||||
class TiesRandom:
|
class TiesRandom:
|
||||||
|
def __init__(self, seed):
|
||||||
|
self.random = SHARandom(seed)
|
||||||
|
|
||||||
def choose_lowest(self, l):
|
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):
|
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.model
|
||||||
import pyRCV2.method, pyRCV2.method.base_stv, pyRCV2.method.wright
|
import pyRCV2.method, pyRCV2.method.base_stv, pyRCV2.method.wright
|
||||||
import pyRCV2.numbers
|
import pyRCV2.numbers
|
||||||
|
import pyRCV2.random
|
||||||
import pyRCV2.ties
|
import pyRCV2.ties
|
||||||
|
|
||||||
__pragma__('js', '{}', 'export {pyRCV2, isinstance, repr, str};')
|
__pragma__('js', '{}', 'export {pyRCV2, isinstance, repr, str};')
|
||||||
|
Reference in New Issue
Block a user