diff --git a/html/index.html b/html/index.html
index 4d13131..4978ba9 100644
--- a/html/index.html
+++ b/html/index.html
@@ -216,6 +216,8 @@
+
+
diff --git a/html/index.js b/html/index.js
index c1d3cd6..0ab3341 100644
--- a/html/index.js
+++ b/html/index.js
@@ -183,10 +183,14 @@ async function clickCount() {
let tblResults = document.getElementById('result');
tblResults.innerHTML = '';
let candMap = {}; // candidate name -> rows
+ let divResultLogs = document.getElementById('resultLogs');
+ divResultLogs.innerHTML = '';
// Step election
let worker = new Worker('worker.js');
- let election, elComment, elExhausted1, elExhausted2, elLTF1, elLTF2, elTotal, elQuota, elVRE;
+ let election;
+ let trComment, trExhausted1, trExhausted2, trLTF1, trLTF2, trTotal, trQuota, trVRE;
+ let olLogs;
worker.onmessage = function(evt) {
if (evt.data.type === 'require_input') {
@@ -204,10 +208,10 @@ async function clickCount() {
election = evt.data.election;
// Comment row
- elComment = document.createElement('tr');
+ trComment = document.createElement('tr');
let elTd = document.createElement('td');
- elComment.appendChild(elTd);
- tblResults.appendChild(elComment);
+ trComment.appendChild(elTd);
+ tblResults.appendChild(trComment);
// Candidates
for (let candidate of election.candidates) {
@@ -227,65 +231,72 @@ async function clickCount() {
}
// Exhausted votes row
- elExhausted1 = document.createElement('tr');
- elExhausted1.classList.add('info');
- elExhausted2 = document.createElement('tr');
- elExhausted2.classList.add('info');
+ trExhausted1 = document.createElement('tr');
+ trExhausted1.classList.add('info');
+ trExhausted2 = document.createElement('tr');
+ trExhausted2.classList.add('info');
elTd = document.createElement('td');
elTd.setAttribute('rowspan', '2');
elTd.style.borderTop = '1px solid black';
elTd.innerText = 'Exhausted';
- elExhausted1.appendChild(elTd);
+ trExhausted1.appendChild(elTd);
- tblResults.appendChild(elExhausted1);
- tblResults.appendChild(elExhausted2);
+ tblResults.appendChild(trExhausted1);
+ tblResults.appendChild(trExhausted2);
// Loss to fraction row
- elLTF1 = document.createElement('tr');
- elLTF1.classList.add('info');
- elLTF2 = document.createElement('tr');
- elLTF2.classList.add('info');
+ trLTF1 = document.createElement('tr');
+ trLTF1.classList.add('info');
+ trLTF2 = document.createElement('tr');
+ trLTF2.classList.add('info');
elTd = document.createElement('td');
elTd.setAttribute('rowspan', '2');
elTd.style.borderTop = '1px solid black';
elTd.innerText = 'Loss to fraction';
- elLTF1.appendChild(elTd);
+ trLTF1.appendChild(elTd);
- tblResults.appendChild(elLTF1);
- tblResults.appendChild(elLTF2);
+ tblResults.appendChild(trLTF1);
+ tblResults.appendChild(trLTF2);
// Total row
- elTotal = document.createElement('tr');
- elTotal.classList.add('info');
+ trTotal = document.createElement('tr');
+ trTotal.classList.add('info');
elTd = document.createElement('td');
elTd.style.borderTop = '1px solid black';
elTd.innerText = 'Total';
- elTotal.appendChild(elTd);
- tblResults.appendChild(elTotal);
+ trTotal.appendChild(elTd);
+ tblResults.appendChild(trTotal);
// Quota row
- elQuota = document.createElement('tr');
- elQuota.classList.add('info');
+ trQuota = document.createElement('tr');
+ trQuota.classList.add('info');
elTd = document.createElement('td');
elTd.style.borderTop = '1px solid black';
elTd.style.borderBottom = '1px solid black';
elTd.innerText = 'Quota';
- elQuota.appendChild(elTd);
- tblResults.appendChild(elQuota);
+ trQuota.appendChild(elTd);
+ tblResults.appendChild(trQuota);
// Vote required for election row
if (document.getElementById('selQuotaMode').value === 'ers97') {
- elVRE = document.createElement('tr');
- elVRE.classList.add('info');
+ trVRE = document.createElement('tr');
+ trVRE.classList.add('info');
elTd = document.createElement('td');
elTd.style.borderTop = '1px solid black';
elTd.style.borderBottom = '1px solid black';
elTd.innerText = 'Vote required for election';
- elVRE.appendChild(elTd);
- tblResults.appendChild(elVRE);
+ trVRE.appendChild(elTd);
+ tblResults.appendChild(trVRE);
}
+
+ // Result logs
+ let elP = document.createElement('p');
+ elP.innerText = 'Stage comments:';
+ divResultLogs.appendChild(elP);
+ olLogs = document.createElement('ol');
+ divResultLogs.appendChild(olLogs);
}
if (evt.data.type === 'result') {
@@ -294,7 +305,7 @@ async function clickCount() {
// Display results
elTd = document.createElement('td');
elTd.innerText = result.stage + '. ' + result.comment;
- elComment.appendChild(elTd);
+ trComment.appendChild(elTd);
for (let [candidate, countCard] of result.candidates) {
[elTr1, elTr2] = candMap[candidate];
@@ -342,31 +353,31 @@ async function clickCount() {
elTd.classList.add('count');
elTd.style.borderTop = '1px solid black';
elTd.innerHTML = ppVotes(result.exhausted.transfers);
- elExhausted1.appendChild(elTd);
+ trExhausted1.appendChild(elTd);
elTd = document.createElement('td');
elTd.classList.add('count');
elTd.innerHTML = ppVotes(result.exhausted.votes);
- elExhausted2.appendChild(elTd);
+ trExhausted2.appendChild(elTd);
// Display loss to fraction
elTd = document.createElement('td');
elTd.classList.add('count');
elTd.style.borderTop = '1px solid black';
elTd.innerHTML = ppVotes(result.loss_fraction.transfers);
- elLTF1.appendChild(elTd);
+ trLTF1.appendChild(elTd);
elTd = document.createElement('td');
elTd.classList.add('count');
elTd.innerHTML = ppVotes(result.loss_fraction.votes);
- elLTF2.appendChild(elTd);
+ trLTF2.appendChild(elTd);
// Display total
elTd = document.createElement('td');
elTd.classList.add('count');
elTd.style.borderTop = '1px solid black';
elTd.innerHTML = ppVotes(result.total);
- elTotal.appendChild(elTd);
+ trTotal.appendChild(elTd);
// Display quota
elTd = document.createElement('td');
@@ -374,7 +385,7 @@ async function clickCount() {
elTd.style.borderTop = '1px solid black';
elTd.style.borderBottom = '1px solid black';
elTd.innerHTML = ppVotes(result.quota);
- elQuota.appendChild(elTd);
+ trQuota.appendChild(elTd);
// Display vote required for election
if (result.vote_required_election !== null) {
@@ -383,8 +394,13 @@ async function clickCount() {
elTd.style.borderTop = '1px solid black';
elTd.style.borderBottom = '1px solid black';
elTd.innerHTML = ppVotes(result.vote_required_election);
- elVRE.appendChild(elTd);
+ trVRE.appendChild(elTd);
}
+
+ // Result logs
+ let elLi = document.createElement('li');
+ elLi.innerText = result.logs.join(' ');
+ olLogs.appendChild(elLi);
}
if (evt.data.type === 'done') {
diff --git a/html/worker.js b/html/worker.js
index 20030c2..5e1ac8b 100644
--- a/html/worker.js
+++ b/html/worker.js
@@ -124,6 +124,7 @@ function resultToJS(result) {
return {
'stage': stage,
'comment': result.comment,
+ 'logs': result.logs,
'candidates': result.candidates.py_items().map(([c, cc]) => [c.py_name, {
'transfers': cc.transfers.pp(ppDPs),
'votes': cc.votes.pp(ppDPs),
diff --git a/pyRCV2/cli/stv.py b/pyRCV2/cli/stv.py
index 27e8998..9bc6d65 100644
--- a/pyRCV2/cli/stv.py
+++ b/pyRCV2/cli/stv.py
@@ -54,6 +54,9 @@ def add_parser(subparsers):
def print_step(args, stage, result):
print('{}. {}'.format(stage, result.comment))
+ if result.logs:
+ print(' '.join(result.logs))
+
results = list(result.candidates.items())
if args.sort_votes:
results.sort(key=lambda x: x[1].votes, reverse=True)
@@ -61,7 +64,10 @@ def print_step(args, stage, result):
for candidate, count_card in results:
state = None
if count_card.state == pyRCV2.model.CandidateState.ELECTED or count_card.state == pyRCV2.model.CandidateState.PROVISIONALLY_ELECTED or count_card.state == pyRCV2.model.CandidateState.DISTRIBUTING_SURPLUS:
- state = 'ELECTED {}'.format(count_card.order_elected)
+ if args.method == 'meek':
+ state = 'ELECTED {} (kv = {})'.format(count_card.order_elected, count_card.keep_value.pp(args.pp_decimals))
+ else:
+ state = 'ELECTED {}'.format(count_card.order_elected)
elif count_card.state == pyRCV2.model.CandidateState.EXCLUDED or count_card.state == pyRCV2.model.CandidateState.EXCLUDING:
state = 'Excluded {}'.format(-count_card.order_elected)
elif count_card.state == pyRCV2.model.CandidateState.WITHDRAWN:
diff --git a/pyRCV2/method/base_stv.py b/pyRCV2/method/base_stv.py
index c6611e4..c11af83 100644
--- a/pyRCV2/method/base_stv.py
+++ b/pyRCV2/method/base_stv.py
@@ -60,6 +60,7 @@ class BaseSTVCounter:
self.total_orig = sum((b.value for b in self.election.ballots), Num('0'))
+ self.logs = []
self.step_results = []
self.num_elected = 0
self.num_excluded = 0
@@ -80,26 +81,14 @@ class BaseSTVCounter:
self._exclusion = None # Optimisation to avoid re-collating/re-sorting ballots
self.distribute_first_preferences()
+ self.logs.append('First preferences distributed.')
self.quota = None
self.vote_required_election = None # For ERS97
self.compute_quota()
self.elect_meeting_quota()
- __pragma__('opov')
- result = CountStepResult(
- 'First preferences',
- self.candidates,
- self.exhausted,
- self.loss_fraction,
- self.total + self.exhausted.votes + self.loss_fraction.votes,
- self.quota,
- self.vote_required_election,
- )
- __pragma__('noopov')
-
- self.step_results = [result]
- return result
+ return self.make_result('First preferences')
def distribute_first_preferences(self):
"""
@@ -173,6 +162,7 @@ class BaseSTVCounter:
__pragma__('opov')
return CountCompleted(
'Count complete',
+ self.logs,
self.candidates,
self.exhausted,
self.loss_fraction,
@@ -187,26 +177,18 @@ class BaseSTVCounter:
# Include EXCLUDING to avoid interrupting an exclusion
if len(self.election.candidates) - self.num_excluded + sum(1 for c, cc in self.candidates.items() if cc.state == CandidateState.EXCLUDING) <= self.election.seats:
# Declare elected all remaining candidates
- for candidate, count_card in self.candidates.items():
- if count_card.state == CandidateState.HOPEFUL:
- count_card.state = CandidateState.PROVISIONALLY_ELECTED
- self.num_elected += 1
- count_card.order_elected = self.num_elected
+ candidates_elected = [(c, cc) for c, cc in self.candidates.items() if cc.state == CandidateState.HOPEFUL]
+ if len(candidates_elected) == 1:
+ self.logs.append(candidates_elected[0][0].name + ' is elected to fill the remaining vacancy.')
+ else:
+ self.logs.append(self.pretty_join([c.name for c, cc in candidates_elected]) + ' are elected to fill the remaining vacancies.')
- __pragma__('opov')
- result = CountStepResult(
- 'Bulk election',
- self.candidates,
- self.exhausted,
- self.loss_fraction,
- self.total + self.exhausted.votes + self.loss_fraction.votes,
- self.quota,
- self.vote_required_election,
- )
- __pragma__('noopov')
+ for candidate, count_card in candidates_elected:
+ count_card.state = CandidateState.PROVISIONALLY_ELECTED
+ self.num_elected += 1
+ count_card.order_elected = self.num_elected
- self.step_results.append(result)
- return result
+ return self.make_result('Bulk election')
def can_defer_surpluses(self, has_surplus):
"""
@@ -235,6 +217,7 @@ class BaseSTVCounter:
__pragma__('opov')
# Can defer surpluses
+ self.logs.append('Distribution of surpluses totalling ' + total_surpluses.pp(2) + ' votes will be deferred.')
return True
def distribute_surpluses(self):
@@ -287,6 +270,7 @@ class BaseSTVCounter:
__pragma__('noopov')
# Transfer surplus
+ self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed.')
self.do_surplus(candidate_surplus, count_card, surplus)
# Declare elected any candidates meeting the quota as a result of surpluses
@@ -294,20 +278,7 @@ class BaseSTVCounter:
self.elect_meeting_quota()
- __pragma__('opov')
- result = CountStepResult(
- 'Surplus of ' + candidate_surplus.name,
- self.candidates,
- self.exhausted,
- self.loss_fraction,
- self.total + self.exhausted.votes + self.loss_fraction.votes,
- self.quota,
- self.vote_required_election,
- )
- __pragma__('noopov')
-
- self.step_results.append(result)
- return result
+ return self.make_result('Surplus of ' + candidate_surplus.name)
def do_surplus(self, candidate_surplus, count_card, surplus):
"""
@@ -333,20 +304,7 @@ class BaseSTVCounter:
self.num_elected += 1
count_card.order_elected = self.num_elected
- __pragma__('opov')
- result = CountStepResult(
- 'Bulk election',
- self.candidates,
- self.exhausted,
- self.loss_fraction,
- self.total + self.exhausted.votes + self.loss_fraction.votes,
- self.quota,
- self.vote_required_election,
- )
- __pragma__('noopov')
-
- self.step_results.append(result)
- return result
+ return self.make_result('Bulk election')
def exclude_candidates(self):
"""
@@ -400,20 +358,7 @@ class BaseSTVCounter:
self.elect_meeting_quota()
- __pragma__('opov')
- result = CountStepResult(
- 'Exclusion of ' + ', '.join([c.name for c, cc in candidates_excluded]),
- self.candidates,
- self.exhausted,
- self.loss_fraction,
- self.total + self.exhausted.votes + self.loss_fraction.votes,
- self.quota,
- self.vote_required_election,
- )
- __pragma__('noopov')
-
- self.step_results.append(result)
- return result
+ return self.make_result('Exclusion of ' + ', '.join([c.name for c, cc in candidates_excluded]))
def candidates_to_bulk_exclude(self, hopefuls):
"""
@@ -460,6 +405,7 @@ class BaseSTVCounter:
# Continue current exclusion if applicable
if self._exclusion is not None:
+ self.logs.append('Continuing exclusion of ' + self.pretty_join([c.name for c, cc in self._exclusion[0]]) + '.')
__pragma__('opov')
return self._exclusion[0]
__pragma__('noopov')
@@ -477,8 +423,14 @@ class BaseSTVCounter:
candidates_excluded = self.candidates_to_bulk_exclude(hopefuls)
- if len(candidates_excluded) == 0:
+ if len(candidates_excluded) > 0:
+ if len(candidates_excluded) == 1:
+ self.logs.append('No surpluses to distribute, so ' + candidates_excluded[0][0].name + ' is excluded.')
+ else:
+ self.logs.append('No surpluses to distribute, so ' + self.pretty_join([c.name for c, cc in candidates_excluded]) + ' are excluded.')
+ else:
candidates_excluded = [self.choose_lowest(hopefuls)]
+ self.logs.append('No surpluses to distribute, so ' + candidates_excluded[0][0].name + ' is excluded.')
return candidates_excluded
@@ -516,17 +468,22 @@ class BaseSTVCounter:
else:
# Round up (preserving the original quota if exact)
self.quota = self.quota.round(self.options['round_quota'], self.quota.ROUND_UP)
+
+ self.logs.append(self.total.pp(2) + ' usable votes, so the quota is ' + self.quota.pp(2) + '.')
__pragma__('noopov')
if self.options['quota_mode'] == 'ers97' and self.num_elected < self.election.seats:
# Calculate the total active vote
__pragma__('opov')
+ orig_vre = self.vote_required_election
total_active_vote = \
sum((cc.votes for c, cc in self.candidates.items() if cc.state == CandidateState.HOPEFUL or cc.state == CandidateState.EXCLUDING), Num('0')) + \
sum((cc.votes - self.quota for c, cc in self.candidates.items() if cc.votes > self.quota), Num('0'))
self.vote_required_election = total_active_vote / Num(self.election.seats - self.num_elected + 1)
if self.options['round_votes'] is not None:
self.vote_required_election = self.vote_required_election.round(self.options['round_votes'], self.vote_required_election.ROUND_UP)
+ if (orig_vre is None or self.vote_required_election != orig_vre) and self.vote_required_election < self.quota:
+ self.logs.append('Total active vote is ' + total_active_vote.pp(2) + ', so the vote required for election is ' + self.vote_required_election.pp(2) + '.')
__pragma__('noopov')
def meets_quota(self, count_card):
@@ -555,6 +512,10 @@ class BaseSTVCounter:
if len(meets_quota) > 0:
meets_quota.sort(key=lambda x: x[1].votes, reverse=True)
+ if len(meets_quota) == 1:
+ self.logs.append(meets_quota[0][0].name + ' meets the quota and is elected.')
+ else:
+ self.logs.append(self.pretty_join([c.name for c, cc in meets_quota]) + ' meet the quota and are elected.')
# Declare elected any candidate who meets the quota
while len(meets_quota) > 0:
@@ -566,11 +527,11 @@ class BaseSTVCounter:
count_card.order_elected = self.num_elected
meets_quota.remove(x)
-
- if self.options['quota_mode'] == 'ers97':
- self.compute_quota()
- self.elect_meeting_quota() # Repeat as the vote required for election may have changed
- return
+
+ if self.options['quota_mode'] == 'ers97':
+ self.compute_quota()
+ self.elect_meeting_quota() # Repeat as the vote required for election may have changed
+ return
# -----------------
# UTILITY FUNCTIONS
@@ -671,3 +632,29 @@ class BaseSTVCounter:
if self.options['round_tvs'] is None:
return num
return num.round(self.options['round_tvs'], num.ROUND_DOWN)
+
+ def make_result(self, comment):
+ __pragma__('opov')
+ result = CountStepResult(
+ comment,
+ self.logs,
+ self.candidates,
+ self.exhausted,
+ self.loss_fraction,
+ self.total + self.exhausted.votes + self.loss_fraction.votes,
+ self.quota,
+ self.vote_required_election,
+ )
+ __pragma__('noopov')
+ self.logs = []
+ self.step_results.append(result)
+ return result
+
+ def pretty_join(self, strs):
+ if len(strs) == 0:
+ return ''
+ if len(strs) == 1:
+ return strs[0]
+ if len(strs) == 2:
+ return strs[0] + ' and ' + strs[1]
+ return ', '.join(strs[0:-1]) + ' and ' + strs[len(strs)-1]
diff --git a/pyRCV2/method/gregory.py b/pyRCV2/method/gregory.py
index 90c7ca5..32de54d 100644
--- a/pyRCV2/method/gregory.py
+++ b/pyRCV2/method/gregory.py
@@ -155,14 +155,18 @@ class WIGSTVCounter(BaseSTVCounter):
else:
raise STVException('Invalid exclusion mode')
- #print([[bv / b.value for c, bb in stage for b, bv in bb] for stage in self._exclusion[1]])
-
this_exclusion = self._exclusion[1][0]
self._exclusion[1].remove(this_exclusion)
# Transfer votes
next_preferences, total_ballots, total_votes, next_exhausted, exhausted_ballots, exhausted_votes = self.next_preferences([bb for c, bb in this_exclusion])
+
+ if self.options['exclusion'] != 'one_round':
+ __pragma__('opov')
+ self.logs.append('Transferring ' + total_ballots.pp(0) + ' ballot papers, totalling ' + total_votes.pp(2) + ' votes, received at value ' + (this_exclusion[0][1][0][1] / this_exclusion[0][1][0][0].value).pp(2) + '.')
+ __pragma__('noopov')
+
for candidate, x in next_preferences.items():
cand_ballots, num_ballots, num_votes = x[0], x[1], x[2]
@@ -199,6 +203,9 @@ class WIGSTVCounter(BaseSTVCounter):
__pragma__('noopov')
if len(self._exclusion[1]) == 0:
+ if self.options['exclusion'] != 'one_round':
+ self.logs.append('Exclusion complete.')
+
for candidate_excluded, count_card in candidates_excluded:
__pragma__('opov')
count_card.transfers -= count_card.votes
diff --git a/pyRCV2/method/meek.py b/pyRCV2/method/meek.py
index e6d9669..e27fa60 100644
--- a/pyRCV2/method/meek.py
+++ b/pyRCV2/method/meek.py
@@ -17,7 +17,7 @@
__pragma__ = lambda x: None
from pyRCV2.method.base_stv import BaseSTVCounter, STVException
-from pyRCV2.model import CandidateState, CountCard, CountStepResult
+from pyRCV2.model import CandidateState, CountCard
from pyRCV2.numbers import Num
from pyRCV2.safedict import SafeDict
@@ -56,9 +56,25 @@ class MeekSTVCounter(BaseSTVCounter):
raise STVException('Meek method is incompatible with --transferable-only')
if self.options['exclusion'] != 'one_round':
raise STVException('Meek method requires --exclusion one_round')
+ if self.options['round_votes'] is not None:
+ raise STVException('Meek method is incompatible with --round-votes')
if self.options['round_tvs'] is not None:
raise STVException('Meek method is incompatible with --round-tvs')
- return BaseSTVCounter.reset(self)
+ if self.options['round_weights'] is not None:
+ raise STVException('Meek method is incompatible with --round-weights')
+
+ self._exclusion = None # Optimisation to avoid re-collating/re-sorting ballots
+
+ self.distribute_first_preferences()
+ self.logs.append('First preferences distributed.')
+
+ self.quota = None
+ self.vote_required_election = None # For ERS97
+ self.compute_quota()
+ self.logs.append(self.total.pp(2) + ' usable votes, so the quota is ' + self.quota.pp(2) + '.')
+ self.elect_meeting_quota()
+
+ return self.make_result('First preferences')
def distribute_first_preferences(self):
"""
@@ -137,7 +153,11 @@ class MeekSTVCounter(BaseSTVCounter):
__pragma__('noopov')
if len(has_surplus) > 0:
+ num_iterations = 0
+ orig_quota = self.quota
while len(has_surplus) > 0:
+ num_iterations += 1
+
# Recompute keep values
for candidate, count_card in has_surplus:
__pragma__('opov')
@@ -154,24 +174,21 @@ class MeekSTVCounter(BaseSTVCounter):
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]
__pragma__('noopov')
+ if num_iterations == 1:
+ self.logs.append('Surpluses distributed, requiring 1 iteration.')
+ else:
+ self.logs.append('Surpluses distributed, requiring ' + str(num_iterations) + ' iterations.')
+
+ self.logs.append('Keep values of elected candidates are: ' + ', '.join([c.name + ' (' + cc.keep_value.pp(2) + ')' for c, cc in self.candidates.items() if cc.state == CandidateState.ELECTED]) + '.')
+
+ if self.quota != orig_quota:
+ self.logs.append(self.total.pp(2) + ' usable votes, so the quota is ' + self.quota.pp(2) + '.')
+
# Declare elected any candidates meeting the quota as a result of surpluses
# NB: We could do this earlier, but this shows the flow of the election more clearly in the count sheet
self.elect_meeting_quota()
- __pragma__('opov')
- result = CountStepResult(
- 'Surpluses distributed',
- self.candidates,
- self.exhausted,
- self.loss_fraction,
- self.total + self.exhausted.votes + self.loss_fraction.votes,
- self.quota,
- self.vote_required_election,
- )
- __pragma__('noopov')
-
- self.step_results.append(result)
- return result
+ return self.make_result('Surpluses distributed')
def do_exclusion(self, candidates_excluded):
"""
@@ -194,6 +211,10 @@ class MeekSTVCounter(BaseSTVCounter):
if len(meets_quota) > 0:
meets_quota.sort(key=lambda x: x[1].votes, reverse=True)
+ if len(meets_quota) == 1:
+ self.logs.append(meets_quota[0][0].name + ' meets the quota and is elected.')
+ else:
+ self.logs.append(self.pretty_join([c.name for c, cc in meets_quota]) + ' meet the quota and are elected.')
# Declare elected any candidate who meets the quota
while len(meets_quota) > 0:
@@ -205,3 +226,33 @@ class MeekSTVCounter(BaseSTVCounter):
count_card.order_elected = self.num_elected
meets_quota.remove(x)
+
+ def compute_quota(self):
+ """
+ Overrides BaseSTVCounter.compute_quota
+ Do not log quota changes
+ """
+
+ __pragma__('opov')
+ self.total = sum((cc.votes for c, cc in self.candidates.items()), Num('0'))
+ self.loss_fraction.transfers += (self.total_orig - self.total - self.exhausted.votes) - self.loss_fraction.votes
+
+ if self.options['quota'] == 'droop' or self.options['quota'] == 'droop_exact':
+ self.quota = self.total / Num(self.election.seats + 1)
+ elif self.options['quota'] == 'hare' or self.options['quota'] == 'hare_exact':
+ self.quota = self.total / Num(self.election.seats)
+ else:
+ raise STVException('Invalid quota option')
+
+ if self.options['round_quota'] is not None:
+ if self.options['quota'] == 'droop' or self.options['quota'] == 'hare':
+ # Increment to next available increment
+ factor = Num(10).__pow__(self.options['round_quota'])
+ __pragma__('opov')
+ self.quota = ((self.quota * factor).__floor__() + Num(1)) / factor
+ __pragma__('noopov')
+ else:
+ # Round up (preserving the original quota if exact)
+ self.quota = self.quota.round(self.options['round_quota'], self.quota.ROUND_UP)
+
+ __pragma__('noopov')
diff --git a/pyRCV2/model.py b/pyRCV2/model.py
index 7a70c68..80935d5 100644
--- a/pyRCV2/model.py
+++ b/pyRCV2/model.py
@@ -103,8 +103,9 @@ class CountCard:
__pragma__('noopov')
class CountStepResult:
- def __init__(self, comment, candidates, exhausted, loss_fraction, total, quota, vote_required_election):
+ def __init__(self, comment, logs, candidates, exhausted, loss_fraction, total, quota, vote_required_election):
self.comment = comment
+ self.logs = logs
self.candidates = candidates # SafeDict: Candidate -> CountCard
self.exhausted = exhausted # CountCard