Add detailed commentary to JS report sheet
This commit is contained in:
parent
c3c2fed444
commit
235c17b33a
@ -218,6 +218,10 @@
|
|||||||
|
|
||||||
<div id="resultLogs"></div>
|
<div id="resultLogs"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var pyRCV2version = 'GITVERSION';
|
||||||
|
</script>
|
||||||
|
|
||||||
<script src="vendor/BigInt_BigRat-a5f89e2.min.js"></script>
|
<script src="vendor/BigInt_BigRat-a5f89e2.min.js"></script>
|
||||||
<script src="vendor/big-6.0.0.min.js"></script>
|
<script src="vendor/big-6.0.0.min.js"></script>
|
||||||
<script src="vendor/sjcl-1.0.8.min.js"></script>
|
<script src="vendor/sjcl-1.0.8.min.js"></script>
|
||||||
|
@ -293,6 +293,22 @@ async function clickCount() {
|
|||||||
|
|
||||||
// Result logs
|
// Result logs
|
||||||
let elP = document.createElement('p');
|
let elP = document.createElement('p');
|
||||||
|
let filePath = document.getElementById('bltFile').value;
|
||||||
|
filePath = filePath.substring(Math.max(filePath.lastIndexOf('\\'), filePath.lastIndexOf('/')) + 1);
|
||||||
|
if (pyRCV2version === 'GITVERSION') {
|
||||||
|
elP.innerText = 'Count computed by pyRCV2 (development version).';
|
||||||
|
} else {
|
||||||
|
elP.innerText = 'Count computed by pyRCV2 (revision ' + pyRCV2version + ').';
|
||||||
|
}
|
||||||
|
elP.innerText += ' Read ' + evt.data.total_ballots + ' ballots from ‘' + filePath + '’ for election ‘' + election.name + '’.';
|
||||||
|
if (evt.data.options === '') {
|
||||||
|
elP.innerText += ' Counting using default options.';
|
||||||
|
} else {
|
||||||
|
elP.innerHTML += ' Counting using options <span style="font-family:monospace;">' + evt.data.options + '</span>.';
|
||||||
|
}
|
||||||
|
divResultLogs.appendChild(elP);
|
||||||
|
|
||||||
|
elP = document.createElement('p');
|
||||||
elP.innerText = 'Stage comments:';
|
elP.innerText = 'Stage comments:';
|
||||||
divResultLogs.appendChild(elP);
|
divResultLogs.appendChild(elP);
|
||||||
olLogs = document.createElement('ol');
|
olLogs = document.createElement('ol');
|
||||||
@ -405,6 +421,7 @@ async function clickCount() {
|
|||||||
|
|
||||||
if (evt.data.type === 'done') {
|
if (evt.data.type === 'done') {
|
||||||
let result = evt.data.result;
|
let result = evt.data.result;
|
||||||
|
let winners = [];
|
||||||
|
|
||||||
// Display results
|
// Display results
|
||||||
|
|
||||||
@ -421,12 +438,26 @@ async function clickCount() {
|
|||||||
} else if (countCard.state === py.pyRCV2.model.CandidateState.ELECTED || countCard.state === py.pyRCV2.model.CandidateState.PROVISIONALLY_ELECTED || countCard.state === py.pyRCV2.model.CandidateState.DISTRIBUTING_SURPLUS) {
|
} else if (countCard.state === py.pyRCV2.model.CandidateState.ELECTED || countCard.state === py.pyRCV2.model.CandidateState.PROVISIONALLY_ELECTED || countCard.state === py.pyRCV2.model.CandidateState.DISTRIBUTING_SURPLUS) {
|
||||||
elTd.classList.add('elected');
|
elTd.classList.add('elected');
|
||||||
elTd.innerHTML = 'ELECTED ' + countCard.order_elected;
|
elTd.innerHTML = 'ELECTED ' + countCard.order_elected;
|
||||||
|
winners.append([candidate, countCard]);
|
||||||
}
|
}
|
||||||
elTd.style.borderTop = '1px solid black';
|
elTd.style.borderTop = '1px solid black';
|
||||||
elTr1.appendChild(elTd);
|
elTr1.appendChild(elTd);
|
||||||
}
|
}
|
||||||
|
|
||||||
elTd.style.borderBottom = '1px solid black';
|
elTd.style.borderBottom = '1px solid black';
|
||||||
|
|
||||||
|
let elP = document.createElement('p');
|
||||||
|
elP.innerText = 'Count complete. The winning candidates are, in order of election:'
|
||||||
|
divResultLogs.appendChild(elP);
|
||||||
|
|
||||||
|
winners.sort(function(x1, x2) { return x1[1].order_elected - x2[1].order_elected; });
|
||||||
|
let elOl = document.createElement('ol');
|
||||||
|
for (let [candidate, countCard] of winners) {
|
||||||
|
let elLi = document.createElement('li');
|
||||||
|
elLi.innerText = candidate;
|
||||||
|
elOl.appendChild(elLi);
|
||||||
|
}
|
||||||
|
divResultLogs.appendChild(elOl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,12 @@ body {
|
|||||||
padding-bottom: 0.5em;
|
padding-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
.menudiv {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.menudiv .subheading {
|
.menudiv .subheading {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -37,9 +37,10 @@ onmessage = function(evt) {
|
|||||||
ppDPs = evt.data.data.ppDPs;
|
ppDPs = evt.data.data.ppDPs;
|
||||||
|
|
||||||
let election = py.pyRCV2.blt.readBLT(evt.data.data.data);
|
let election = py.pyRCV2.blt.readBLT(evt.data.data.data);
|
||||||
postMessage({'type': 'init', 'election': {
|
let total_ballots = py.pyRCV2.numbers.Num(0);
|
||||||
'candidates': election.candidates.map(c => c.py_name)
|
for (let b of election.ballots) {
|
||||||
}});
|
total_ballots.__iadd__(b.value);
|
||||||
|
}
|
||||||
|
|
||||||
// Create counter
|
// Create counter
|
||||||
if (evt.data.data.transfers === 'uig') {
|
if (evt.data.data.transfers === 'uig') {
|
||||||
@ -63,6 +64,16 @@ onmessage = function(evt) {
|
|||||||
counter.options['ties'] = [tiesPrompt];
|
counter.options['ties'] = [tiesPrompt];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postMessage({
|
||||||
|
'type': 'init',
|
||||||
|
'election': {
|
||||||
|
'name': election.py_name,
|
||||||
|
'candidates': election.candidates.map(c => c.py_name)
|
||||||
|
},
|
||||||
|
'total_ballots': total_ballots.pp(0),
|
||||||
|
'options': counter.describe_options(),
|
||||||
|
});
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
stage = 1;
|
stage = 1;
|
||||||
try {
|
try {
|
||||||
|
@ -36,7 +36,7 @@ def add_parser(subparsers):
|
|||||||
parser.add_argument('--defer-surpluses', action='store_true', help='defer surplus transfers if possible')
|
parser.add_argument('--defer-surpluses', action='store_true', help='defer surplus transfers if possible')
|
||||||
parser.add_argument('--numbers', '-n', choices=['fixed', 'rational', 'native'], default='fixed', help='numbers mode (default: fixed)')
|
parser.add_argument('--numbers', '-n', choices=['fixed', 'rational', 'native'], default='fixed', help='numbers mode (default: fixed)')
|
||||||
parser.add_argument('--decimals', type=int, default=5, help='decimal places if --numbers fixed (default: 5)')
|
parser.add_argument('--decimals', type=int, default=5, help='decimal places if --numbers fixed (default: 5)')
|
||||||
parser.add_argument('--no-round-quota', action='store_true', help='do not round the quota')
|
#parser.add_argument('--no-round-quota', action='store_true', help='do not round the quota')
|
||||||
parser.add_argument('--round-quota', type=int, help='round quota to specified decimal places')
|
parser.add_argument('--round-quota', type=int, help='round quota to specified decimal places')
|
||||||
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-tvs', type=int, help='round transfer values to specified decimal places')
|
parser.add_argument('--round-tvs', type=int, help='round transfer values to specified decimal places')
|
||||||
@ -45,7 +45,7 @@ def add_parser(subparsers):
|
|||||||
parser.add_argument('--method', '-m', choices=['wig', 'uig', 'eg', 'meek'], default='wig', help='method of transferring surpluses (default: wig - weighted inclusive Gregory)')
|
parser.add_argument('--method', '-m', choices=['wig', 'uig', 'eg', 'meek'], 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', 'wright'], default='one_round', help='how to perform exclusions (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', 'forwards', 'prompt', 'random'], default=None, help='how to resolve ties (default: backwards then random)')
|
parser.add_argument('--ties', '-t', action='append', choices=['backwards', 'forwards', 'prompt', 'random'], default=None, help='how to resolve ties (default: prompt)')
|
||||||
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')
|
||||||
parser.add_argument('--hide-excluded', action='store_true', help='hide excluded candidates from results report')
|
parser.add_argument('--hide-excluded', action='store_true', help='hide excluded candidates from results report')
|
||||||
parser.add_argument('--sort-votes', action='store_true', help='sort candidates by votes in results report')
|
parser.add_argument('--sort-votes', action='store_true', help='sort candidates by votes in results report')
|
||||||
@ -116,8 +116,8 @@ def main(args):
|
|||||||
else:
|
else:
|
||||||
counter = WIGSTVCounter(election, vars(args))
|
counter = WIGSTVCounter(election, vars(args))
|
||||||
|
|
||||||
if args.no_round_quota:
|
#if args.no_round_quota:
|
||||||
counter.options['round_quota'] = None
|
# counter.options['round_quota'] = None
|
||||||
|
|
||||||
if args.ties is None:
|
if args.ties is None:
|
||||||
args.ties = ['prompt']
|
args.ties = ['prompt']
|
||||||
|
@ -17,8 +17,10 @@
|
|||||||
__pragma__ = lambda x: None
|
__pragma__ = lambda x: None
|
||||||
|
|
||||||
from pyRCV2.model import CandidateState, CountCard, CountCompleted, CountStepResult
|
from pyRCV2.model import CandidateState, CountCard, CountCompleted, CountStepResult
|
||||||
|
import pyRCV2.numbers
|
||||||
from pyRCV2.numbers import Num
|
from pyRCV2.numbers import Num
|
||||||
from pyRCV2.safedict import SafeDict
|
from pyRCV2.safedict import SafeDict
|
||||||
|
import pyRCV2.ties
|
||||||
|
|
||||||
class STVException(Exception):
|
class STVException(Exception):
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
@ -535,6 +537,57 @@ class BaseSTVCounter:
|
|||||||
self.elect_meeting_quota() # Repeat as the vote required for election may have changed
|
self.elect_meeting_quota() # Repeat as the vote required for election may have changed
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def describe_options(self):
|
||||||
|
result = []
|
||||||
|
if self.options['quota'] != 'droop':
|
||||||
|
result.append('--quota ' + self.options['quota'])
|
||||||
|
if self.options['quota_criterion'] != 'geq':
|
||||||
|
result.append('--quota-criterion ' + self.options['quota_criterion'])
|
||||||
|
if self.options['quota_mode'] != 'static':
|
||||||
|
result.append('--quota-mode ' + self.options['quota_mode'])
|
||||||
|
if not self.options['bulk_elect']:
|
||||||
|
result.append('--no-bulk-elect')
|
||||||
|
if self.options['bulk_exclude']:
|
||||||
|
result.append('--bulk-exclude')
|
||||||
|
if self.options['defer_surpluses']:
|
||||||
|
result.append('--defer-surpluses')
|
||||||
|
if pyRCV2.numbers._numclass is pyRCV2.numbers.Rational:
|
||||||
|
result.append('--numbers rational')
|
||||||
|
elif pyRCV2.numbers._numclass is pyRCV2.numbers.Native:
|
||||||
|
result.append('--numbers native')
|
||||||
|
else:
|
||||||
|
# Fixed
|
||||||
|
if pyRCV2.numbers.get_dps() != 5:
|
||||||
|
result.append('--decimals ' + str(pyRCV2.numbers.get_dps()))
|
||||||
|
if self.options['round_quota'] is not None:
|
||||||
|
result.append('--round-quota ' + str(self.options['round_quota']))
|
||||||
|
if self.options['round_votes'] is not None:
|
||||||
|
result.append('--round-votes ' + str(self.options['round_votes']))
|
||||||
|
if self.options['round_tvs'] is not None:
|
||||||
|
result.append('--round-tvs ' + str(self.options['round_tvs']))
|
||||||
|
if self.options['round_weights'] is not None:
|
||||||
|
result.append('--round-weights ' + str(self.options['round_weights']))
|
||||||
|
if self.options['surplus_order'] != 'size':
|
||||||
|
result.append('--surplus-order ' + self.options['surplus_order'])
|
||||||
|
if self.options['papers'] == 'transferable':
|
||||||
|
result.append('--transferable-only')
|
||||||
|
if self.options['exclusion'] != 'one_round':
|
||||||
|
result.append('--exclusion ' + self.options['exclusion'])
|
||||||
|
if len(self.options['ties']) == 0 and isinstance(self.options['ties'][0], pyRCV2.ties.TiesPrompt):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for t in self.options['ties']:
|
||||||
|
if isinstance(t, pyRCV2.ties.TiesBackwards):
|
||||||
|
result.append('--ties backwards')
|
||||||
|
elif isinstance(t, pyRCV2.ties.TiesForwards):
|
||||||
|
result.append('--ties forwards')
|
||||||
|
elif isinstance(t, pyRCV2.ties.TiesRandom):
|
||||||
|
result.append('--ties random')
|
||||||
|
result.append('--random-seed ' + t.random.seed)
|
||||||
|
elif isinstance(t, pyRCV2.ties.TiesPrompt):
|
||||||
|
result.append('--ties prompt')
|
||||||
|
return ' '.join(result)
|
||||||
|
|
||||||
# -----------------
|
# -----------------
|
||||||
# UTILITY FUNCTIONS
|
# UTILITY FUNCTIONS
|
||||||
# -----------------
|
# -----------------
|
||||||
|
@ -53,6 +53,10 @@ class WIGSTVCounter(BaseSTVCounter):
|
|||||||
Basic weighted inclusive Gregory STV counter
|
Basic weighted inclusive Gregory STV counter
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def describe_options(self):
|
||||||
|
# WIG is the default
|
||||||
|
return BaseSTVCounter.describe_options(self)
|
||||||
|
|
||||||
def do_surplus(self, candidate_surplus, count_card, surplus):
|
def do_surplus(self, candidate_surplus, count_card, surplus):
|
||||||
next_preferences, total_ballots, total_votes, next_exhausted, exhausted_ballots, exhausted_votes = self.next_preferences(count_card.parcels)
|
next_preferences, total_ballots, total_votes, next_exhausted, exhausted_ballots, exhausted_votes = self.next_preferences(count_card.parcels)
|
||||||
|
|
||||||
@ -218,6 +222,10 @@ class UIGSTVCounter(WIGSTVCounter):
|
|||||||
Basic unweighted inclusive Gregory STV counter
|
Basic unweighted inclusive Gregory STV counter
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def describe_options(self):
|
||||||
|
"""Overrides BaseSTVCounter.describe_options"""
|
||||||
|
return '--method uig ' + BaseSTVCounter.describe_options(self)
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
WIGSTVCounter.__init__(self, *args)
|
WIGSTVCounter.__init__(self, *args)
|
||||||
|
|
||||||
@ -301,6 +309,10 @@ class EGSTVCounter(UIGSTVCounter):
|
|||||||
Exclusive Gregory (last bundle) STV implementation
|
Exclusive Gregory (last bundle) STV implementation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def describe_options(self):
|
||||||
|
"""Overrides BaseSTVCounter.describe_options"""
|
||||||
|
return '--method eg ' + BaseSTVCounter.describe_options(self)
|
||||||
|
|
||||||
def do_surplus(self, candidate_surplus, count_card, surplus):
|
def do_surplus(self, candidate_surplus, count_card, surplus):
|
||||||
"""Overrides UIGSTVCounter.do_surplus"""
|
"""Overrides UIGSTVCounter.do_surplus"""
|
||||||
|
|
||||||
|
@ -38,12 +38,15 @@ class MeekCountCard(CountCard):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
class MeekSTVCounter(BaseSTVCounter):
|
class MeekSTVCounter(BaseSTVCounter):
|
||||||
|
def describe_options(self):
|
||||||
|
"""Overrides BaseSTVCounter.describe_options"""
|
||||||
|
return '--method meek ' + BaseSTVCounter.describe_options(self)
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
BaseSTVCounter.__init__(self, *args)
|
BaseSTVCounter.__init__(self, *args)
|
||||||
self.candidates = SafeDict([(c, MeekCountCard()) for c in self.election.candidates])
|
self.candidates = SafeDict([(c, MeekCountCard()) for c in self.election.candidates])
|
||||||
|
|
||||||
self._quota_tolerance_ub = Num('1.0001')
|
self._quota_tolerance = Num('1.0001')
|
||||||
self._quota_tolerance_lb = Num('0.9999')
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
if self.options['quota_mode'] != 'progressive':
|
if self.options['quota_mode'] != 'progressive':
|
||||||
@ -149,7 +152,7 @@ class MeekSTVCounter(BaseSTVCounter):
|
|||||||
|
|
||||||
# Do surpluses need to be distributed?
|
# Do surpluses need to be distributed?
|
||||||
__pragma__('opov')
|
__pragma__('opov')
|
||||||
has_surplus = [(c, cc) for c, cc in self.candidates.items() if cc.state == CandidateState.ELECTED and cc.votes / self.quota > self._quota_tolerance_ub]
|
has_surplus = [(c, cc) for c, cc in self.candidates.items() if cc.state == CandidateState.ELECTED and cc.votes / self.quota > self._quota_tolerance]
|
||||||
__pragma__('noopov')
|
__pragma__('noopov')
|
||||||
|
|
||||||
if len(has_surplus) > 0:
|
if len(has_surplus) > 0:
|
||||||
@ -171,7 +174,7 @@ class MeekSTVCounter(BaseSTVCounter):
|
|||||||
self.compute_quota()
|
self.compute_quota()
|
||||||
|
|
||||||
__pragma__('opov')
|
__pragma__('opov')
|
||||||
has_surplus = [(c, cc) for c, cc in self.candidates.items() if cc.state == CandidateState.ELECTED and cc.votes / self.quota > self._quota_tolerance_ub]
|
has_surplus = [(c, cc) for c, cc in self.candidates.items() if cc.state == CandidateState.ELECTED and cc.votes / self.quota > self._quota_tolerance]
|
||||||
__pragma__('noopov')
|
__pragma__('noopov')
|
||||||
|
|
||||||
if num_iterations == 1:
|
if num_iterations == 1:
|
||||||
|
Reference in New Issue
Block a user