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> <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>

View File

@ -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&nbsp;' + countCard.order_elected; elTd.innerHTML = 'ELECTED&nbsp;' + 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);
} }
} }

View File

@ -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;

View File

@ -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 {

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('--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']

View File

@ -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
# ----------------- # -----------------

View File

@ -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"""

View File

@ -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: