def test_all_vacancies_filled_should_be_true_after_round_runs(self): """ Before a round has run it should report that all vacancies have not been filled. After the round runs it should report that all vacancies have been filled. """ vacancies = 2 candidates = ('Red', 'Green', 'Blue') votes = 4 * (('Red', ), ) + \ 3 * (('Green', ), ) stv_round = Round(vacancies, candidates, votes) self.assertFalse(stv_round.all_vacancies_filled()) stv_round.run() self.assertTrue(stv_round.all_vacancies_filled())
def test_all_vacancies_filled_should_be_false_after_incomplete_round(self): """ After a round runs that fails to elect enough candidates the round should report that it has not filled all vacancies. """ vacancies = 2 candidates = ('Red', 'Green', 'Blue', 'Yellow', 'Mauve') votes = 5 * (('Red', ), ) + \ 4 * (('Green', ), ) + \ 3 * (('Blue', ), ) + \ 2 * (('Yellow', ), ) + \ 1 * (('Mauve', ), ) stv_round = Round(vacancies, candidates, votes) stv_round.run() self.assertFalse(stv_round.all_vacancies_filled())
def test_candidates_should_be_elected_once_there_is_one_per_vacanc(self): """ As soon as there are the same number of remaining candidates as vacancies the election is completed with all of the remaining candidates elected as winners. Note that this makes it possible for a candidate to be elected even without enough votes to reach the quota, as in this example. Surplus votes for candidates A and B become exhausted votes because there is no further preference to reallocate them to. Candidate D is then excluded because they have the fewest votes. Since this leaves only three candidates for three vacancies candidate C is declared elected, even though they have only one vote compared to the quota of three. """ vacancies = 3 candidates = ('A', 'B', 'C', 'D') votes = 5 * (('A', 'B'), ) + \ 4 * (('B', 'A'), ) + \ 1 * (('C', ), ) expected_results = { 'provisionally_elected': { 'A': 3, 'B': 3, 'C': 1 }, 'continuing': {}, 'excluded': { 'D': 0 }, } stv = Round(vacancies, candidates, votes) stv.run() self.assertEqual(expected_results, stv.results()) self.assertTrue(stv.all_vacancies_filled())
def test_reallocate_candidate_reaching_quota(self): """ This test steps through the run round algorithm, demonstrating each step. Only one candidate reaches the quota initially, but reallocation causes another to reach quota and require reallocation of their now surplus votes. Note that this reallocation of Mars's now surplus votes requires their devaluing to have been recorded, i.e. it's not 8 at the end of the first iteration, it's 11 votes worth 8/11ths each. """ votes = [ ['Galaxy', 'Mars', 'Crunchie'], ['Galaxy', 'Mars', 'Crunchie'], ['Galaxy', 'Mars', 'Crunchie'], ['Galaxy', 'Mars', 'Crunchie'], ['Galaxy', 'Mars', 'Crunchie'], ['Galaxy', 'Mars', 'Crunchie'], ['Galaxy', 'Mars', 'Crunchie'], ['Galaxy', 'Mars', 'Crunchie'], ['Galaxy', 'Mars', 'Crunchie'], ['Galaxy', 'Mars', 'Crunchie'], ['Galaxy', 'Mars', 'Bounty'], ] vacancies = 3 candidates = ['Mars', 'Bounty', 'Galaxy', 'Crunchie'] # At this stage, we have calculated initial totals but # done no more work with the votes stv_round = Round(vacancies, candidates, votes) initial_totals = { 'provisionally_elected': {}, 'continuing': { 'Mars': 0, 'Bounty': 0, 'Galaxy': 11, 'Crunchie': 0, }, 'excluded': {} } self.assertEqual(initial_totals, stv_round.results()) # Now we run the first pass of calculating which # candidates are provisionally elected stv_round._provisionally_elect_candidates() first_provisional_election_totals = { 'provisionally_elected': { 'Galaxy': 11, }, 'continuing': { 'Mars': 0, 'Bounty': 0, 'Crunchie': 0, }, 'excluded': {} } self.assertEqual(first_provisional_election_totals, stv_round.results()) # Now we reassign Galaxy's surplus votes to the # next preferences of votes for Galaxy. # Note that the 8 for Mars is actually 11 votes each worth 8/11ths. stv_round._reassign_votes_from_candidate_with_highest_surplus() first_reallocation_totals = { 'provisionally_elected': { 'Galaxy': 3, }, 'continuing': { 'Mars': 8, 'Bounty': 0, 'Crunchie': 0, }, 'excluded': {} } self.assertEqual(first_reallocation_totals, stv_round.results()) # After reallocation, we run a provisional election again to see if any # new candidates have reached the quota stv_round._provisionally_elect_candidates() second_provisional_election_totals = { 'provisionally_elected': { 'Galaxy': 3, 'Mars': 8, }, 'continuing': { 'Bounty': 0, 'Crunchie': 0, }, 'excluded': {} } self.assertEqual(second_provisional_election_totals, stv_round.results()) # Now Mars has reached the quota, we need to reallocate # Mars's surplus votes to the next preferences of votes # for Mars. # However, votes that have been allocated to Mars from Galaxy # are not worth 1, they are worth 8/11 because of their # previous reallocation from Galaxy. stv_round._reassign_votes_from_candidate_with_highest_surplus() second_reallocation_totals = { 'provisionally_elected': { 'Galaxy': 3, 'Mars': 3, }, 'continuing': { 'Bounty': Fraction(5,11), 'Crunchie': 4 + Fraction(6, 11) }, 'excluded': {} } self.assertEqual(second_reallocation_totals, stv_round.results()) # We now provisonally elect candidates again to see if anyone else has # reached the quota - Crunchie has stv_round._provisionally_elect_candidates() third_provisional_election_total = { 'provisionally_elected': { 'Galaxy': 3, 'Mars': 3, 'Crunchie': 4 + Fraction(6, 11) }, 'continuing': { 'Bounty': Fraction(5,11), }, 'excluded': {} } self.assertEqual(third_provisional_election_total, stv_round.results()) # Now that we have three provisionally elected candidates for the three # vacancies, the election is over self.assertTrue(stv_round.all_vacancies_filled())