From 235c17b33a2c14812b90f549939b707935402c84 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Sat, 9 Jan 2021 23:58:25 +1100 Subject: [PATCH] Add detailed commentary to JS report sheet --- html/index.html | 4 +++ html/index.js | 31 +++++++++++++++++++++++ html/main.css | 6 +++++ html/worker.js | 17 ++++++++++--- pyRCV2/cli/stv.py | 8 +++--- pyRCV2/method/base_stv.py | 53 +++++++++++++++++++++++++++++++++++++++ pyRCV2/method/gregory.py | 12 +++++++++ pyRCV2/method/meek.py | 11 +++++--- 8 files changed, 131 insertions(+), 11 deletions(-) diff --git a/html/index.html b/html/index.html index 4978ba9..b594102 100644 --- a/html/index.html +++ b/html/index.html @@ -218,6 +218,10 @@
+ + diff --git a/html/index.js b/html/index.js index 0ab3341..4157de9 100644 --- a/html/index.js +++ b/html/index.js @@ -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 ' + evt.data.options + '.'; + } + 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 ' + 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); } } diff --git a/html/main.css b/html/main.css index 05f74a5..8ef5c86 100644 --- a/html/main.css +++ b/html/main.css @@ -33,6 +33,12 @@ body { padding-bottom: 0.5em; } +@media print { + .menudiv { + display: none; + } +} + .menudiv .subheading { font-size: 0.8em; font-weight: bold; diff --git a/html/worker.js b/html/worker.js index 5e1ac8b..c2397ed 100644 --- a/html/worker.js +++ b/html/worker.js @@ -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 { diff --git a/pyRCV2/cli/stv.py b/pyRCV2/cli/stv.py index 9bc6d65..e97040a 100644 --- a/pyRCV2/cli/stv.py +++ b/pyRCV2/cli/stv.py @@ -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'] diff --git a/pyRCV2/method/base_stv.py b/pyRCV2/method/base_stv.py index 4634473..b3456c1 100644 --- a/pyRCV2/method/base_stv.py +++ b/pyRCV2/method/base_stv.py @@ -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 # ----------------- diff --git a/pyRCV2/method/gregory.py b/pyRCV2/method/gregory.py index 32de54d..fb2ade5 100644 --- a/pyRCV2/method/gregory.py +++ b/pyRCV2/method/gregory.py @@ -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""" diff --git a/pyRCV2/method/meek.py b/pyRCV2/method/meek.py index e27fa60..8530dda 100644 --- a/pyRCV2/method/meek.py +++ b/pyRCV2/method/meek.py @@ -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: