From d23eee79bb53613007f9672b14fb230645debb3d Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Wed, 13 Jan 2021 00:25:51 +1100 Subject: [PATCH] Refactor/DRY Gregory method implementations --- pyRCV2/method/gregory.py | 281 ++++++++++----------------------------- 1 file changed, 70 insertions(+), 211 deletions(-) diff --git a/pyRCV2/method/gregory.py b/pyRCV2/method/gregory.py index 8a47100..740173c 100644 --- a/pyRCV2/method/gregory.py +++ b/pyRCV2/method/gregory.py @@ -48,38 +48,42 @@ def groupby(iterable, keyfunc): groups.append(group) return groups -class WIGSTVCounter(BaseSTVCounter): - """ - Basic weighted inclusive Gregory STV counter - """ +class BaseGregorySTVCounter(BaseSTVCounter): + """Abstract 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) + def _do_gregory_surplus(self, is_weighted, candidate_surplus, count_card, surplus, parcels): + next_preferences, total_ballots, total_votes, next_exhausted, exhausted_ballots, exhausted_votes = self.next_preferences(parcels) + + if is_weighted: + total_units = total_votes + else: + total_units = total_ballots if self.options['papers'] == 'transferable': __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__('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_votes).pp(2) + '.') + self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + (surplus / transferable_units).pp(2) + '.') 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) + '.') 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_votes).pp(2) + '.') + self.logs.append('Surplus of ' + candidate_surplus.name + ' distributed at value ' + (surplus / total_units).pp(2) + '.') 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) + '.') __pragma__('noopov') @@ -88,6 +92,11 @@ class WIGSTVCounter(BaseSTVCounter): num_ballots = x[1] num_votes = x[2] + if is_weighted: + num_units = num_votes + else: + num_units = num_ballots + new_parcel = [] if len(cand_ballots) > 0: __pragma__('opov') @@ -98,16 +107,16 @@ class WIGSTVCounter(BaseSTVCounter): if self.options['papers'] == 'transferable': if transferable_votes > surplus: 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: - self.candidates[candidate].transfers += self.round_votes(num_votes * tv) + self.candidates[candidate].transfers += self.round_votes(num_units * tv) else: self.candidates[candidate].transfers += self.round_votes(num_votes) # Do not allow weight to increase else: 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: - self.candidates[candidate].transfers += self.round_votes(num_votes * tv) + self.candidates[candidate].transfers += self.round_votes(num_units * tv) __pragma__('noopov') for ballot, ballot_value in cand_ballots: @@ -115,18 +124,30 @@ class WIGSTVCounter(BaseSTVCounter): if self.options['papers'] == 'transferable': if transferable_votes > surplus: 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: - tv = self.round_tv(surplus / transferable_votes) - new_value = ballot_value * tv + tv = self.round_tv(surplus / transferable_units) + if is_weighted: + new_value = ballot_value * tv + else: + new_value = ballot.value * tv else: new_value = ballot_value else: 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: - tv = self.round_tv(surplus / total_votes) - new_value = ballot_value * tv + tv = self.round_tv(surplus / total_units) + if is_weighted: + new_value = ballot_value * tv + else: + new_value = ballot.value * tv new_parcel.append((ballot, self.round_weight(new_value))) __pragma__('noopov') @@ -138,7 +159,10 @@ class WIGSTVCounter(BaseSTVCounter): else: self.exhausted.transfers += self.round_votes((surplus - transferable_votes)) 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__('opov') @@ -148,6 +172,7 @@ class WIGSTVCounter(BaseSTVCounter): count_card.state = CandidateState.ELECTED def do_exclusion(self, candidates_excluded): + """Implements BaseSTVCounter.do_exclusion""" # Optimisation: Pre-sort exclusion ballots if applicable # self._exclusion[1] -> list of ballots-per-stage, ballots-per-stage = List[Tuple[Candidate,List[Ballot+Value]]] if self._exclusion is None: @@ -233,7 +258,20 @@ class WIGSTVCounter(BaseSTVCounter): count_card.state = CandidateState.EXCLUDED 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 """ @@ -242,101 +280,11 @@ class UIGSTVCounter(WIGSTVCounter): """Overrides BaseSTVCounter.describe_options""" return '--method uig ' + BaseSTVCounter.describe_options(self) - def __init__(self, *args): - WIGSTVCounter.__init__(self, *args) - 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) - - 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 + """Implements BaseSTVCounter.do_surplus""" + self._do_gregory_surplus(False, candidate_surplus, count_card, surplus, count_card.parcels) -class EGSTVCounter(UIGSTVCounter): +class EGSTVCounter(BaseGregorySTVCounter): """ Exclusive Gregory (last bundle) STV implementation """ @@ -346,95 +294,6 @@ class EGSTVCounter(UIGSTVCounter): return '--method eg ' + BaseSTVCounter.describe_options(self) 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] - next_preferences, total_ballots, total_votes, next_exhausted, exhausted_ballots, exhausted_votes = self.next_preferences([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 + self._do_gregory_surplus(False, candidate_surplus, count_card, surplus, [last_bundle])