Refactor/DRY Gregory method implementations

This commit is contained in:
RunasSudo 2021-01-13 00:25:51 +11:00
parent ad7efe3133
commit d23eee79bb
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A

View File

@ -48,38 +48,42 @@ def groupby(iterable, keyfunc):
groups.append(group) groups.append(group)
return groups return groups
class WIGSTVCounter(BaseSTVCounter): class BaseGregorySTVCounter(BaseSTVCounter):
""" """Abstract Gregory STV counter"""
Basic weighted inclusive Gregory STV counter
"""
def describe_options(self): def _do_gregory_surplus(self, is_weighted, candidate_surplus, count_card, surplus, parcels):
# WIG is the default next_preferences, total_ballots, total_votes, next_exhausted, exhausted_ballots, exhausted_votes = self.next_preferences(parcels)
return BaseSTVCounter.describe_options(self)
if is_weighted:
def do_surplus(self, candidate_surplus, count_card, surplus): total_units = total_votes
next_preferences, total_ballots, total_votes, next_exhausted, exhausted_ballots, exhausted_votes = self.next_preferences(count_card.parcels) else:
total_units = total_ballots
if self.options['papers'] == 'transferable': if self.options['papers'] == 'transferable':
__pragma__('opov') __pragma__('opov')
transferable_votes = total_votes - exhausted_votes if is_weighted:
transferable_units = total_votes - exhausted_votes
transferable_votes = transferable_units
else:
transferable_units = total_ballots - exhausted_ballots
transferable_votes = total_votes - exhausted_votes
__pragma__('noopov') __pragma__('noopov')
__pragma__('opov') __pragma__('opov')
if self.options['papers'] == 'transferable': if self.options['papers'] == 'transferable':
if transferable_votes > surplus: if transferable_votes > surplus:
if self.options['round_tvs'] is None: if self.options['round_tvs'] is None:
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + (surplus / transferable_votes).pp(2) + '.') self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + (surplus / transferable_units).pp(2) + '.')
else: else:
tv = self.round_tv(surplus / transferable_votes) tv = self.round_tv(surplus / transferable_units)
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + tv.pp(2) + '.') self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + tv.pp(2) + '.')
else: else:
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value received.') self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value received.')
else: else:
if self.options['round_tvs'] is None: if self.options['round_tvs'] is None:
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + (surplus / total_votes).pp(2) + '.') self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + (surplus / total_units).pp(2) + '.')
else: else:
tv = self.round_tv(surplus / total_votes) tv = self.round_tv(surplus / total_units)
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + tv.pp(2) + '.') self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + tv.pp(2) + '.')
__pragma__('noopov') __pragma__('noopov')
@ -88,6 +92,11 @@ class WIGSTVCounter(BaseSTVCounter):
num_ballots = x[1] num_ballots = x[1]
num_votes = x[2] num_votes = x[2]
if is_weighted:
num_units = num_votes
else:
num_units = num_ballots
new_parcel = [] new_parcel = []
if len(cand_ballots) > 0: if len(cand_ballots) > 0:
__pragma__('opov') __pragma__('opov')
@ -98,16 +107,16 @@ class WIGSTVCounter(BaseSTVCounter):
if self.options['papers'] == 'transferable': if self.options['papers'] == 'transferable':
if transferable_votes > surplus: if transferable_votes > surplus:
if self.options['round_tvs'] is None: if self.options['round_tvs'] is None:
self.candidates[candidate].transfers += self.round_votes((num_votes * surplus) / transferable_votes) self.candidates[candidate].transfers += self.round_votes((num_units * surplus) / transferable_units)
else: else:
self.candidates[candidate].transfers += self.round_votes(num_votes * tv) self.candidates[candidate].transfers += self.round_votes(num_units * tv)
else: else:
self.candidates[candidate].transfers += self.round_votes(num_votes) # Do not allow weight to increase self.candidates[candidate].transfers += self.round_votes(num_votes) # Do not allow weight to increase
else: else:
if self.options['round_tvs'] is None: if self.options['round_tvs'] is None:
self.candidates[candidate].transfers += self.round_votes((num_votes * surplus) / total_votes) self.candidates[candidate].transfers += self.round_votes((num_units * surplus) / total_units)
else: else:
self.candidates[candidate].transfers += self.round_votes(num_votes * tv) self.candidates[candidate].transfers += self.round_votes(num_units * tv)
__pragma__('noopov') __pragma__('noopov')
for ballot, ballot_value in cand_ballots: for ballot, ballot_value in cand_ballots:
@ -115,18 +124,30 @@ class WIGSTVCounter(BaseSTVCounter):
if self.options['papers'] == 'transferable': if self.options['papers'] == 'transferable':
if transferable_votes > surplus: if transferable_votes > surplus:
if self.options['round_tvs'] is None: if self.options['round_tvs'] is None:
new_value = (ballot_value * surplus) / transferable_votes if is_weighted:
new_value = (ballot_value * surplus) / transferable_units
else:
new_value = (ballot.value * surplus) / transferable_units
else: else:
tv = self.round_tv(surplus / transferable_votes) tv = self.round_tv(surplus / transferable_units)
new_value = ballot_value * tv if is_weighted:
new_value = ballot_value * tv
else:
new_value = ballot.value * tv
else: else:
new_value = ballot_value new_value = ballot_value
else: else:
if self.options['round_tvs'] is None: if self.options['round_tvs'] is None:
new_value = (ballot_value * surplus) / total_votes if is_weighted:
new_value = (ballot_value * surplus) / total_units
else:
new_value = (ballot.value * surplus) / total_units
else: else:
tv = self.round_tv(surplus / total_votes) tv = self.round_tv(surplus / total_units)
new_value = ballot_value * tv if is_weighted:
new_value = ballot_value * tv
else:
new_value = ballot.value * tv
new_parcel.append((ballot, self.round_weight(new_value))) new_parcel.append((ballot, self.round_weight(new_value)))
__pragma__('noopov') __pragma__('noopov')
@ -138,7 +159,10 @@ class WIGSTVCounter(BaseSTVCounter):
else: else:
self.exhausted.transfers += self.round_votes((surplus - transferable_votes)) self.exhausted.transfers += self.round_votes((surplus - transferable_votes))
else: else:
self.exhausted.transfers += self.round_votes((exhausted_votes * surplus) / total_votes) if is_weighted:
self.exhausted.transfers += self.round_votes((exhausted_votes * surplus) / total_votes)
else:
self.exhausted.transfers += self.round_votes((exhausted_ballots * surplus) / total_ballots)
__pragma__('noopov') __pragma__('noopov')
__pragma__('opov') __pragma__('opov')
@ -148,6 +172,7 @@ class WIGSTVCounter(BaseSTVCounter):
count_card.state = CandidateState.ELECTED count_card.state = CandidateState.ELECTED
def do_exclusion(self, candidates_excluded): def do_exclusion(self, candidates_excluded):
"""Implements BaseSTVCounter.do_exclusion"""
# Optimisation: Pre-sort exclusion ballots if applicable # Optimisation: Pre-sort exclusion ballots if applicable
# self._exclusion[1] -> list of ballots-per-stage, ballots-per-stage = List[Tuple[Candidate,List[Ballot+Value]]] # self._exclusion[1] -> list of ballots-per-stage, ballots-per-stage = List[Tuple[Candidate,List[Ballot+Value]]]
if self._exclusion is None: if self._exclusion is None:
@ -233,7 +258,20 @@ class WIGSTVCounter(BaseSTVCounter):
count_card.state = CandidateState.EXCLUDED count_card.state = CandidateState.EXCLUDED
self._exclusion = None self._exclusion = None
class UIGSTVCounter(WIGSTVCounter): class WIGSTVCounter(BaseGregorySTVCounter):
"""
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):
"""Implements BaseSTVCounter.do_surplus"""
self._do_gregory_surplus(True, candidate_surplus, count_card, surplus, count_card.parcels)
class UIGSTVCounter(BaseGregorySTVCounter):
""" """
Basic unweighted inclusive Gregory STV counter Basic unweighted inclusive Gregory STV counter
""" """
@ -242,101 +280,11 @@ class UIGSTVCounter(WIGSTVCounter):
"""Overrides BaseSTVCounter.describe_options""" """Overrides BaseSTVCounter.describe_options"""
return '--method uig ' + BaseSTVCounter.describe_options(self) return '--method uig ' + BaseSTVCounter.describe_options(self)
def __init__(self, *args):
WIGSTVCounter.__init__(self, *args)
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) """Implements BaseSTVCounter.do_surplus"""
self._do_gregory_surplus(False, candidate_surplus, count_card, surplus, count_card.parcels)
if self.options['papers'] == 'transferable':
__pragma__('opov')
transferable_ballots = total_ballots - exhausted_ballots
transferable_votes = total_votes - exhausted_votes
__pragma__('noopov')
__pragma__('opov')
if self.options['papers'] == 'transferable':
if transferable_votes > surplus:
if self.options['round_tvs'] is None:
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + (surplus / transferable_ballots).pp(2) + '.')
else:
tv = self.round_tv(surplus / transferable_ballots)
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + tv.pp(2) + '.')
else:
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value received.')
else:
if self.options['round_tvs'] is None:
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + (surplus / total_ballots).pp(2) + '.')
else:
tv = self.round_tv(surplus / total_ballots)
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + tv.pp(2) + '.')
__pragma__('noopov')
for candidate, x in next_preferences.items():
cand_ballots = x[0]
num_ballots = x[1]
num_votes = x[2]
new_parcel = []
if len(cand_ballots) > 0:
__pragma__('opov')
self.candidates[candidate].parcels.append(new_parcel)
__pragma__('noopov')
__pragma__('opov')
if self.options['papers'] == 'transferable':
if transferable_votes > surplus:
if self.options['round_tvs'] is None:
self.candidates[candidate].transfers += self.round_votes((num_ballots * surplus) / transferable_ballots)
else:
self.candidates[candidate].transfers += self.round_votes(num_ballots * tv)
else:
self.candidates[candidate].transfers += self.round_votes(num_votes)
else:
if self.options['round_tvs'] is None:
self.candidates[candidate].transfers += self.round_votes((num_ballots * surplus) / total_ballots)
else:
self.candidates[candidate].transfers += self.round_votes(num_ballots * tv)
__pragma__('noopov')
for ballot, ballot_value in cand_ballots:
__pragma__('opov')
if self.options['papers'] == 'transferable':
if transferable_votes > surplus:
if self.options['round_tvs'] is None:
new_value = (ballot.value * surplus) / transferable_ballots
else:
tv = self.round_tv(surplus / transferable_ballots)
new_value = ballot.value * tv
else:
new_value = ballot_value
else:
if self.options['round_tvs'] is None:
new_value = (ballot.value * surplus) / total_ballots
else:
tv = self.round_tv(surplus / total_ballots)
new_value = ballot.value * tv
new_parcel.append((ballot, self.round_weight(new_value)))
__pragma__('noopov')
__pragma__('opov')
if self.options['papers'] == 'transferable':
if transferable_votes > surplus:
pass # No ballots exhaust
else:
self.exhausted.transfers += self.round_votes(surplus - transferable_votes)
else:
self.exhausted.transfers += self.round_votes((exhausted_ballots * surplus) / total_ballots)
__pragma__('noopov')
__pragma__('opov')
count_card.transfers -= surplus
__pragma__('noopov')
count_card.state = CandidateState.ELECTED
class EGSTVCounter(UIGSTVCounter): class EGSTVCounter(BaseGregorySTVCounter):
""" """
Exclusive Gregory (last bundle) STV implementation Exclusive Gregory (last bundle) STV implementation
""" """
@ -346,95 +294,6 @@ class EGSTVCounter(UIGSTVCounter):
return '--method eg ' + BaseSTVCounter.describe_options(self) 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""" """Implements BaseSTVCounter.do_surplus"""
last_bundle = count_card.parcels[len(count_card.parcels)-1] last_bundle = count_card.parcels[len(count_card.parcels)-1]
next_preferences, total_ballots, total_votes, next_exhausted, exhausted_ballots, exhausted_votes = self.next_preferences([last_bundle]) self._do_gregory_surplus(False, candidate_surplus, count_card, surplus, [last_bundle])
if self.options['papers'] == 'transferable':
__pragma__('opov')
transferable_ballots = total_ballots - exhausted_ballots
transferable_votes = total_votes - exhausted_votes
__pragma__('noopov')
__pragma__('opov')
if self.options['papers'] == 'transferable':
if transferable_votes > surplus:
if self.options['round_tvs'] is None:
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + (surplus / transferable_ballots).pp(2) + '.')
else:
tv = self.round_tv(surplus / transferable_ballots)
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + tv.pp(2) + '.')
else:
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value received.')
else:
if self.options['round_tvs'] is None:
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + (surplus / total_ballots).pp(2) + '.')
else:
tv = self.round_tv(surplus / total_ballots)
self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + tv.pp(2) + '.')
__pragma__('noopov')
for candidate, x in next_preferences.items():
cand_ballots = x[0]
num_ballots = x[1]
num_votes = x[2]
new_parcel = []
if len(cand_ballots) > 0:
__pragma__('opov')
self.candidates[candidate].parcels.append(new_parcel)
__pragma__('noopov')
__pragma__('opov')
if self.options['papers'] == 'transferable':
if transferable_votes > surplus:
if self.options['round_tvs'] is None:
self.candidates[candidate].transfers += self.round_votes((num_ballots * surplus) / transferable_ballots)
else:
self.candidates[candidate].transfers += self.round_votes(num_ballots * tv)
else:
self.candidates[candidate].transfers += self.round_votes(num_votes)
else:
if self.options['round_tvs'] is None:
self.candidates[candidate].transfers += self.round_votes((num_ballots * surplus) / total_ballots)
else:
self.candidates[candidate].transfers += self.round_votes(num_ballots * tv)
__pragma__('noopov')
for ballot, ballot_value in cand_ballots:
__pragma__('opov')
if self.options['papers'] == 'transferable':
if transferable_votes > surplus:
if self.options['round_tvs'] is None:
new_value = (ballot.value * surplus) / transferable_ballots
else:
tv = self.round_tv(surplus / transferable_ballots)
new_value = ballot.value * tv
else:
new_value = ballot_value
else:
if self.options['round_tvs'] is None:
new_value = (ballot.value * surplus) / total_ballots
else:
tv = self.round_tv(surplus / total_ballots)
new_value = ballot.value * tv
new_parcel.append((ballot, self.round_weight(new_value)))
__pragma__('noopov')
__pragma__('opov')
if self.options['papers'] == 'transferable':
if transferable_votes > surplus:
pass # No ballots exhaust
else:
self.exhausted.transfers += self.round_votes((surplus - transferable_votes))
else:
self.exhausted.transfers += self.round_votes((exhausted_ballots * surplus) / total_ballots)
__pragma__('noopov')
__pragma__('opov')
count_card.transfers -= surplus
__pragma__('noopov')
count_card.state = CandidateState.ELECTED