Add detailed commentary to JS report sheet

This commit is contained in:
RunasSudo 2021-01-09 23:58:25 +11:00
parent c3c2fed444
commit 235c17b33a
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
8 changed files with 131 additions and 11 deletions

View File

@ -218,6 +218,10 @@
<div id="resultLogs"></div>
<script>
var pyRCV2version = 'GITVERSION';
</script>
<script src="vendor/BigInt_BigRat-a5f89e2.min.js"></script>
<script src="vendor/big-6.0.0.min.js"></script>
<script src="vendor/sjcl-1.0.8.min.js"></script>

View File

@ -293,6 +293,22 @@ async function clickCount() {
// Result logs
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:';
divResultLogs.appendChild(elP);
olLogs = document.createElement('ol');
@ -405,6 +421,7 @@ async function clickCount() {
if (evt.data.type === 'done') {
let result = evt.data.result;
let winners = [];
// 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) {
elTd.classList.add('elected');
elTd.innerHTML = 'ELECTED&nbsp;' + countCard.order_elected;
winners.append([candidate, countCard]);
}
elTd.style.borderTop = '1px solid black';
elTr1.appendChild(elTd);
}
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);
}
}

View File

@ -33,6 +33,12 @@ body {
padding-bottom: 0.5em;
}
@media print {
.menudiv {
display: none;
}
}
.menudiv .subheading {
font-size: 0.8em;
font-weight: bold;

View File

@ -37,9 +37,10 @@ onmessage = function(evt) {
ppDPs = evt.data.data.ppDPs;
let election = py.pyRCV2.blt.readBLT(evt.data.data.data);
postMessage({'type': 'init', 'election': {
'candidates': election.candidates.map(c => c.py_name)
}});
let total_ballots = py.pyRCV2.numbers.Num(0);
for (let b of election.ballots) {
total_ballots.__iadd__(b.value);
}
// Create counter
if (evt.data.data.transfers === 'uig') {
@ -63,6 +64,16 @@ onmessage = function(evt) {
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
stage = 1;
try {

View File

@ -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('--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('--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-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')
@ -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('--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('--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('--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')
@ -116,8 +116,8 @@ def main(args):
else:
counter = WIGSTVCounter(election, vars(args))
if args.no_round_quota:
counter.options['round_quota'] = None
#if args.no_round_quota:
# counter.options['round_quota'] = None
if args.ties is None:
args.ties = ['prompt']

View File

@ -17,8 +17,10 @@
__pragma__ = lambda x: None
from pyRCV2.model import CandidateState, CountCard, CountCompleted, CountStepResult
import pyRCV2.numbers
from pyRCV2.numbers import Num
from pyRCV2.safedict import SafeDict
import pyRCV2.ties
class STVException(Exception):
def __init__(self, message):
@ -535,6 +537,57 @@ class BaseSTVCounter:
self.elect_meeting_quota() # Repeat as the vote required for election may have changed
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
# -----------------

View File

@ -53,6 +53,10 @@ class WIGSTVCounter(BaseSTVCounter):
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):
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
"""
def describe_options(self):
"""Overrides BaseSTVCounter.describe_options"""
return '--method uig ' + BaseSTVCounter.describe_options(self)
def __init__(self, *args):
WIGSTVCounter.__init__(self, *args)
@ -301,6 +309,10 @@ class EGSTVCounter(UIGSTVCounter):
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):
"""Overrides UIGSTVCounter.do_surplus"""

View File

@ -38,12 +38,15 @@ class MeekCountCard(CountCard):
return result
class MeekSTVCounter(BaseSTVCounter):
def describe_options(self):
"""Overrides BaseSTVCounter.describe_options"""
return '--method meek ' + BaseSTVCounter.describe_options(self)
def __init__(self, *args):
BaseSTVCounter.__init__(self, *args)
self.candidates = SafeDict([(c, MeekCountCard()) for c in self.election.candidates])
self._quota_tolerance_ub = Num('1.0001')
self._quota_tolerance_lb = Num('0.9999')
self._quota_tolerance = Num('1.0001')
def reset(self):
if self.options['quota_mode'] != 'progressive':
@ -149,7 +152,7 @@ class MeekSTVCounter(BaseSTVCounter):
# Do surpluses need to be distributed?
__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')
if len(has_surplus) > 0:
@ -171,7 +174,7 @@ class MeekSTVCounter(BaseSTVCounter):
self.compute_quota()
__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')
if num_iterations == 1: