Skip to content

Commit

Permalink
better overhang, WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Šimbera committed May 20, 2024
1 parent f9b1c3a commit ea22667
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ list there for some suggestions.

How to add features:

1. Fork it (https://github.com/simberaj/pysynth/fork)
1. Fork it (<https://github.com/simberaj/votelib/fork>)
2. Create your feature branch (`git checkout -b feature/feature-name`)
3. Commit your changes (`git commit -am "feature-name added"`)
4. Push to the branch (`git push origin feature/feature-name`)
Expand Down
53 changes: 53 additions & 0 deletions tests/evaluate/test_mixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import votelib.evaluate.core
import votelib.evaluate.cardinal
import votelib.evaluate.condorcet
import votelib.evaluate.proportional


SMITH_SCORE = votelib.evaluate.core.Conditioned(
Expand Down Expand Up @@ -58,3 +59,55 @@ def test_smith_score(votes, expected_winner):
assert winner == expected_winner


PROPORT_EVAL = votelib.evaluate.proportional.LargestRemainder(
quota_function='hare'
)


@pytest.mark.parametrize("system, result", [
(
PROPORT_EVAL,
{'A': 54, 'B': 34, 'C': 7, 'D': 5},
),
(
votelib.evaluate.core.AdjustedSeatCount(
votelib.evaluate.core.AllowOverhang(PROPORT_EVAL),
PROPORT_EVAL,
),
{'A': 54, 'B': 41, 'C': 13, 'D': 5},
),
(
votelib.evaluate.core.AdjustedSeatCount(
votelib.evaluate.core.LevelOverhang(PROPORT_EVAL),
PROPORT_EVAL,
),
{'A': 63, 'B': 60, 'C': 19, 'D': 5},
)
])
def test_compensatory_systems(system, result):
# https://en.wikipedia.org/wiki/Additional_member_system
elect_res = {
'A': 54,
'B': 11,
'C': 0,
'D': 5,
}
party_vote = {
'A': 43,
'B': 41,
'C': 13,
'D': 3,
}

class MockEvaluator:
def evaluate(self, *args, **kwargs):
return elect_res

total_evaluator = votelib.evaluate.core.MultistageDistributor([
MockEvaluator(),
system
])
total_res = total_evaluator.evaluate(
party_vote, 100
)
assert total_res == result
2 changes: 2 additions & 0 deletions votelib/evaluate/condorcet.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,8 @@ def _lock_pairs(cls,
if not cls._is_path(locked_pairs, pair[1], pair[0]):
logging.info('locking %s over %s', *pair)
locked_pairs.append(pair)
else:
logging.info('disregarding %s over %s: conflict with previously locked pairs', *pair)
return locked_pairs

@staticmethod
Expand Down
14 changes: 13 additions & 1 deletion votelib/evaluate/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,9 +586,12 @@ class LevelOverhang:
results from the second round votes to determine which seats are
overhang. Usually will be the same or very similar to the evaluator
used in the second round directly.
:param max_multiple: How many overhang and leveling seats in total at most
can be awarded before an infeasibility error is raised.
"""
def __init__(self, evaluator: Distributor):
def __init__(self, evaluator: Distributor, max_multiple: float = 1.0):
self.evaluator = evaluator
self.max_multiple = max_multiple

def calculate(self,
votes: Dict[Any, int],
Expand Down Expand Up @@ -623,11 +626,20 @@ def calculate(self,
nonprop_drop += prev_gain
adj_count = n_seats - nonprop_drop
pmins = list(lowest_allowed.items())
print(prop_result)
print(lowest_allowed)
print(nonprop_drop)
print(adj_count, prop_result)
print(max_seats)
while any(prop_result[party] < minimum for party, minimum in pmins):
adj_count += 1
if adj_count > (self.max_multiple + 1) * n_seats:
raise VotingSystemError('would award too many leveling seats')
print(votes, adj_count, max_seats)
prop_result = self.evaluator.evaluate(
votes, adj_count, max_seats=max_seats,
)
print(' ', prop_result)
return adj_count + nonprop_drop - n_seats


Expand Down
23 changes: 23 additions & 0 deletions votelib/evaluate/proportional.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,26 @@ def evaluate(self,
:param max_seats: Maximum number of seats that the given
candidate/party can obtain in total (including previous gains).
"""
if prev_gains:
noprev_result = self.evaluate(
votes, n_seats, max_seats=max_seats
)
overhung_result = {
cand: n_prev for cand, n_prev in prev_gains.items()
if n_prev > noprev_result.get(cand, 0)
}
print(overhung_result)
if overhung_result:
rest_votes = {
cand: n_votes for cand, n_votes in votes.items()
if cand not in overhung_result
}
rest_n_seats = n_seats - sum(overhung_result.values())
rest_result = self.evaluate(
rest_votes, rest_n_seats, max_seats=max_seats
)
print(rest_result)
return votelib.util.sum_dicts(overhung_result, rest_result)
quota_val = self.quota_function(
sum(votes.values()), n_seats
)
Expand Down Expand Up @@ -257,6 +277,9 @@ def evaluate(self,
))
total_awarded = sum(selected.values()) + sum(prev_gains.values())
if total_awarded > n_seats:
if prev_gains:

print(prev_gains, selected, votelib.util.sum_dicts(selected, prev_gains))
if self.on_overaward == 'ignore':
return selected
elif self.on_overaward == 'error':
Expand Down

0 comments on commit ea22667

Please sign in to comment.