Implement backwards tie breaking

This commit is contained in:
RunasSudo 2020-12-24 01:36:39 +11:00
parent 06ab133615
commit 503fed26d1
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
6 changed files with 73 additions and 10 deletions

View File

@ -105,7 +105,7 @@
<label>
Ties:
<select id="selTies">
<option value="backwards_random" selected>Backwards then random (NYI)</option>
<option value="backwards_random" selected>Backwards then random</option>
<option value="random">Random</option>
</select>
</label>

View File

@ -50,7 +50,7 @@ onmessage = function(evt) {
}
if (evt.data.options['ties'] === 'backwards_random') {
counter.options['ties'] = [py.pyRCV2.ties.TiesBackwards(), py.pyRCV2.ties.TiesRandom(evt.data.seed)];
counter.options['ties'] = [py.pyRCV2.ties.TiesBackwards(counter), py.pyRCV2.ties.TiesRandom(evt.data.seed)];
} else if (evt.data.options['ties'] === 'random') {
counter.options['ties'] = [py.pyRCV2.ties.TiesRandom(evt.data.seed)];
}

View File

@ -91,7 +91,7 @@ def main(args):
counter.options['ties'] = []
for t in args.ties:
if t == 'backwards':
counter.options['ties'].append(TiesBackwards())
counter.options['ties'].append(TiesBackwards(counter))
elif t == 'prompt':
counter.options['ties'].append(TiesPrompt())
elif t == 'random':

View File

@ -62,7 +62,8 @@ class BaseSTVCounter:
def reset(self):
"""
Public function:
Reset the count and perform the first step
Perform the first step (distribute first preferences)
Does not reset the states of candidates, etc.
"""
# Distribute first preferences
@ -83,7 +84,7 @@ class BaseSTVCounter:
self.elect_meeting_quota()
__pragma__('opov')
return CountStepResult(
result = CountStepResult(
'First preferences',
self.candidates,
self.exhausted,
@ -92,6 +93,9 @@ class BaseSTVCounter:
self.quota
)
__pragma__('noopov')
self.step_results = [result]
return result
def step(self):
"""
@ -151,7 +155,7 @@ class BaseSTVCounter:
self.num_elected += 1
__pragma__('opov')
return CountStepResult(
result = CountStepResult(
'Bulk election',
self.candidates,
self.exhausted,
@ -160,6 +164,9 @@ class BaseSTVCounter:
self.quota
)
__pragma__('noopov')
self.step_results.append(result)
return result
def distribute_surpluses(self):
"""
@ -200,7 +207,7 @@ class BaseSTVCounter:
self.elect_meeting_quota()
__pragma__('opov')
return CountStepResult(
result = CountStepResult(
'Surplus of ' + candidate_surplus.name,
self.candidates,
self.exhausted,
@ -209,6 +216,9 @@ class BaseSTVCounter:
self.quota
)
__pragma__('noopov')
self.step_results.append(result)
return result
def do_surplus(self, candidate_surplus, count_card, surplus):
"""
@ -237,7 +247,7 @@ class BaseSTVCounter:
self.elect_meeting_quota()
__pragma__('opov')
return CountStepResult(
result = CountStepResult(
'Exclusion of ' + candidate_excluded.name,
self.candidates,
self.exhausted,
@ -246,6 +256,9 @@ class BaseSTVCounter:
self.quota
)
__pragma__('noopov')
self.step_results.append(result)
return result
def candidate_to_exclude(self):
"""

View File

@ -39,6 +39,9 @@ class Ballot:
def __init__(self, value, preferences):
self.value = value
self.preferences = preferences
def clone(self):
return Ballot(self.value, self.preferences)
class Election:
"""
@ -73,6 +76,16 @@ class CountCard:
"""Roll over previous round transfers in preparation for next round"""
self.orig_votes = self.votes
self.transfers = Num('0')
def clone(self):
"""Return a clone of this count card (including cloning ballots) as a record of this stage"""
result = CountCard()
result.orig_votes = self.orig_votes
result.transfers = self.transfers
result.ballots = [b.clone() for b in self.ballots]
result.state = self.state
result.order_elected = self.order_elected
return result
class CountCompleted:
pass
@ -87,3 +100,14 @@ class CountStepResult:
self.total = total
self.quota = quota
def clone(self):
"""Return a clone of this result as a record of this stage"""
candidates = SafeDict()
for c, cc in self.candidates.items():
__pragma__('opov')
candidates[c] = cc.clone()
__pragma__('noopov')
return CountStepResult(self.comment, candidates, self.exhausted.clone(), self.loss_fraction.clone(), self.total, self.quota)

View File

@ -65,14 +65,40 @@ class TiesPrompt:
def choose_highest(self, l):
return self.choose_lowest(l)
# FIXME: This is untested!
class TiesBackwards:
"""
Break ties based on the candidate who had the highest/lowest total at the end
of the most recent stage where one candidate had a higher/lower total than
all other tied candidates, if such a stage exists
"""
def __init__(self, counter):
self.counter = counter
def choose_lowest(self, l):
raise Exception('Not yet implemented')
for result in reversed(self.counter.step_results):
__pragma__('opov')
l2 = [(x, result.candidates[x[0]].votes) for x in l]
l2.sort(key=lambda x: x[1])
if l2[0][1] < l2[1][1]: # Did one candidate have fewer votes than the others?
return l2[0][0]
__pragma__('noopov')
return None
def choose_highest(self, l):
raise Exception('Not yet implemented')
for result in reversed(self.counter.step_results):
__pragma__('opov')
l2 = [(x, result.candidates[x[0]].votes) for x in l]
l2.sort(key=lambda x: x[1], reverse=True)
if l2[0][1] > l2[1][1]: # Did one candidate have more votes than the others?
return l2[0][0]
__pragma__('noopov')
return None
class TiesRandom:
"""Break ties randomly, using the SHARandom deterministic RNG"""
def __init__(self, seed):
self.random = SHARandom(seed)