Implement backwards tie breaking
This commit is contained in:
parent
06ab133615
commit
503fed26d1
@ -105,7 +105,7 @@
|
|||||||
<label>
|
<label>
|
||||||
Ties:
|
Ties:
|
||||||
<select id="selTies">
|
<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>
|
<option value="random">Random</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
@ -50,7 +50,7 @@ onmessage = function(evt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (evt.data.options['ties'] === 'backwards_random') {
|
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') {
|
} else if (evt.data.options['ties'] === 'random') {
|
||||||
counter.options['ties'] = [py.pyRCV2.ties.TiesRandom(evt.data.seed)];
|
counter.options['ties'] = [py.pyRCV2.ties.TiesRandom(evt.data.seed)];
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ def main(args):
|
|||||||
counter.options['ties'] = []
|
counter.options['ties'] = []
|
||||||
for t in args.ties:
|
for t in args.ties:
|
||||||
if t == 'backwards':
|
if t == 'backwards':
|
||||||
counter.options['ties'].append(TiesBackwards())
|
counter.options['ties'].append(TiesBackwards(counter))
|
||||||
elif t == 'prompt':
|
elif t == 'prompt':
|
||||||
counter.options['ties'].append(TiesPrompt())
|
counter.options['ties'].append(TiesPrompt())
|
||||||
elif t == 'random':
|
elif t == 'random':
|
||||||
|
@ -62,7 +62,8 @@ class BaseSTVCounter:
|
|||||||
def reset(self):
|
def reset(self):
|
||||||
"""
|
"""
|
||||||
Public function:
|
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
|
# Distribute first preferences
|
||||||
@ -83,7 +84,7 @@ class BaseSTVCounter:
|
|||||||
self.elect_meeting_quota()
|
self.elect_meeting_quota()
|
||||||
|
|
||||||
__pragma__('opov')
|
__pragma__('opov')
|
||||||
return CountStepResult(
|
result = CountStepResult(
|
||||||
'First preferences',
|
'First preferences',
|
||||||
self.candidates,
|
self.candidates,
|
||||||
self.exhausted,
|
self.exhausted,
|
||||||
@ -93,6 +94,9 @@ class BaseSTVCounter:
|
|||||||
)
|
)
|
||||||
__pragma__('noopov')
|
__pragma__('noopov')
|
||||||
|
|
||||||
|
self.step_results = [result]
|
||||||
|
return result
|
||||||
|
|
||||||
def step(self):
|
def step(self):
|
||||||
"""
|
"""
|
||||||
Public function:
|
Public function:
|
||||||
@ -151,7 +155,7 @@ class BaseSTVCounter:
|
|||||||
self.num_elected += 1
|
self.num_elected += 1
|
||||||
|
|
||||||
__pragma__('opov')
|
__pragma__('opov')
|
||||||
return CountStepResult(
|
result = CountStepResult(
|
||||||
'Bulk election',
|
'Bulk election',
|
||||||
self.candidates,
|
self.candidates,
|
||||||
self.exhausted,
|
self.exhausted,
|
||||||
@ -161,6 +165,9 @@ class BaseSTVCounter:
|
|||||||
)
|
)
|
||||||
__pragma__('noopov')
|
__pragma__('noopov')
|
||||||
|
|
||||||
|
self.step_results.append(result)
|
||||||
|
return result
|
||||||
|
|
||||||
def distribute_surpluses(self):
|
def distribute_surpluses(self):
|
||||||
"""
|
"""
|
||||||
Distribute surpluses, if any
|
Distribute surpluses, if any
|
||||||
@ -200,7 +207,7 @@ class BaseSTVCounter:
|
|||||||
self.elect_meeting_quota()
|
self.elect_meeting_quota()
|
||||||
|
|
||||||
__pragma__('opov')
|
__pragma__('opov')
|
||||||
return CountStepResult(
|
result = CountStepResult(
|
||||||
'Surplus of ' + candidate_surplus.name,
|
'Surplus of ' + candidate_surplus.name,
|
||||||
self.candidates,
|
self.candidates,
|
||||||
self.exhausted,
|
self.exhausted,
|
||||||
@ -210,6 +217,9 @@ class BaseSTVCounter:
|
|||||||
)
|
)
|
||||||
__pragma__('noopov')
|
__pragma__('noopov')
|
||||||
|
|
||||||
|
self.step_results.append(result)
|
||||||
|
return result
|
||||||
|
|
||||||
def do_surplus(self, candidate_surplus, count_card, surplus):
|
def do_surplus(self, candidate_surplus, count_card, surplus):
|
||||||
"""
|
"""
|
||||||
Transfer the surplus of the given candidate
|
Transfer the surplus of the given candidate
|
||||||
@ -237,7 +247,7 @@ class BaseSTVCounter:
|
|||||||
self.elect_meeting_quota()
|
self.elect_meeting_quota()
|
||||||
|
|
||||||
__pragma__('opov')
|
__pragma__('opov')
|
||||||
return CountStepResult(
|
result = CountStepResult(
|
||||||
'Exclusion of ' + candidate_excluded.name,
|
'Exclusion of ' + candidate_excluded.name,
|
||||||
self.candidates,
|
self.candidates,
|
||||||
self.exhausted,
|
self.exhausted,
|
||||||
@ -247,6 +257,9 @@ class BaseSTVCounter:
|
|||||||
)
|
)
|
||||||
__pragma__('noopov')
|
__pragma__('noopov')
|
||||||
|
|
||||||
|
self.step_results.append(result)
|
||||||
|
return result
|
||||||
|
|
||||||
def candidate_to_exclude(self):
|
def candidate_to_exclude(self):
|
||||||
"""
|
"""
|
||||||
Determine the candidate to exclude
|
Determine the candidate to exclude
|
||||||
|
@ -40,6 +40,9 @@ class Ballot:
|
|||||||
self.value = value
|
self.value = value
|
||||||
self.preferences = preferences
|
self.preferences = preferences
|
||||||
|
|
||||||
|
def clone(self):
|
||||||
|
return Ballot(self.value, self.preferences)
|
||||||
|
|
||||||
class Election:
|
class Election:
|
||||||
"""
|
"""
|
||||||
Represents a BLT election
|
Represents a BLT election
|
||||||
@ -74,6 +77,16 @@ class CountCard:
|
|||||||
self.orig_votes = self.votes
|
self.orig_votes = self.votes
|
||||||
self.transfers = Num('0')
|
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:
|
class CountCompleted:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -87,3 +100,14 @@ class CountStepResult:
|
|||||||
|
|
||||||
self.total = total
|
self.total = total
|
||||||
self.quota = quota
|
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)
|
||||||
|
@ -65,14 +65,40 @@ class TiesPrompt:
|
|||||||
def choose_highest(self, l):
|
def choose_highest(self, l):
|
||||||
return self.choose_lowest(l)
|
return self.choose_lowest(l)
|
||||||
|
|
||||||
|
# FIXME: This is untested!
|
||||||
class TiesBackwards:
|
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):
|
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):
|
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:
|
class TiesRandom:
|
||||||
|
"""Break ties randomly, using the SHARandom deterministic RNG"""
|
||||||
|
|
||||||
def __init__(self, seed):
|
def __init__(self, seed):
|
||||||
self.random = SHARandom(seed)
|
self.random = SHARandom(seed)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user