def test_candidates_with_surplus_is_ordered(self):
        """
        We want the method under test to return a list of candidates whose
        total votes exceed the quota, and that list of candidates to be
        ordered, highest votes first.

        With 3 seats available and the following votes:

            Mars: 3
            Bounty: 6
            Galaxy: 8
            Crunchie: 2

        We have a quota of 5 and expect Galaxy and Bounty to be returned as
        candidates with surplus, in that order.
        """

        votes = (('Mars', ), ('Mars', ), ('Mars', ), ('Bounty', ),
                 ('Bounty', ), ('Bounty', ), ('Bounty', ), ('Bounty', ),
                 ('Bounty', ), ('Galaxy', ), ('Galaxy', ), ('Galaxy', ),
                 ('Galaxy', ), ('Galaxy', ), ('Galaxy', ), ('Galaxy', ),
                 ('Galaxy', ), ('Crunchie', ), ('Crunchie', ))
        candidates = ('Mars', 'Bounty', 'Galaxy', 'Crunchie')
        seats = 3

        expected_candidates = ['Galaxy', 'Bounty']

        stv_round = Round(seats, candidates, votes)
        stv_round._provisionally_elect_candidates()
        candidates_with_surplus = map(lambda c: c.candidate_id,
                                      stv_round._candidates_with_surplus())

        self.assertEqual(expected_candidates, candidates_with_surplus)
    def test_elected_candidates_returns_the_correct_order(self):
        """
        Check that candidates with more votes are returned ahead of candidates
        with fewer votes
        """

        vacancies = 2
        candidates = ('Back Bacon', 'Streaky Bacon', 'Peanut')
        votes = (
            ('Peanut', ),
            ('Back Bacon', ),
            ('Back Bacon', ),
            ('Back Bacon', ),
            ('Streaky Bacon', ),
            ('Streaky Bacon', ),
            ('Streaky Bacon', ),
            ('Streaky Bacon', ),
        )

        expected_order = ['Streaky Bacon', 'Back Bacon']

        stv_round = Round(vacancies, candidates, votes)
        stv_round._provisionally_elect_candidates()

        self.assertEqual(expected_order, stv_round.elected_candidates())
    def test_provisionally_elect_candidates_auto_fills_vacancies(self):
        """
        If there are as many remaining vacancies as remaining candidates then
        all remaining candidates should be elected even if they don't meet the
        quota.

        In this example, the quota is 3 votes and we expected candidate C to be
        elected even though they only have 2 votes.
        """

        votes = (('A', ), ('A', ), ('A', ), ('A', ), ('B', ), ('B', ), ('B', ),
                 ('C', ), ('C', ))
        candidates = ('A', 'B', 'C')
        vacancies = 3

        stv_round = Round(vacancies, candidates, votes)
        stv_round._provisionally_elect_candidates()

        expected_totals = {
            'provisionally_elected': {
                'A': 4,
                'B': 3,
                'C': 2
            },
            'continuing': {},
            'excluded': {}
        }

        self.assertEqual(expected_totals, stv_round.results())
    def test_reallocate_fractional_votes(self):
        """
        This is the case where a candidates second preferences are split
        between other candidates so fractions of votes are reallocated
        """
        votes = [
            ['Amy', 'James'],
            ['Amy', 'James'],
            ['Amy', 'James'],
            ['Amy', 'James'],
            ['Amy', 'David'],
            ['Amy', 'David'],
            ['Amy', 'David'],
        ]
        candidates = ['Amy', 'James', 'David']
        vacancies = 2

        expected_result = {
            'provisionally_elected': {
                'Amy': 3
            },
            'continuing': {
                'James': Fraction(16, 7),
                'David': Fraction(12, 7)
            },
            'excluded': {}
        }

        stv_round = Round(vacancies, candidates, votes)
        stv_round._provisionally_elect_candidates()
        stv_round._reassign_votes_from_candidate_with_highest_surplus()

        self.assertEqual(expected_result, stv_round.results())
    def test_provisionally_elect_candidates(self):
        """
        Tests that candidates at or above the quota are marked as provisionally elected
        """

        votes = (('A', ), ('A', ), ('A', ), ('A', ), ('B', ), ('B', ), ('B', ),
                 ('C', ))
        candidates = ('A', 'B', 'C')
        vacancies = 2

        stv_round = Round(vacancies, candidates, votes)
        stv_round._provisionally_elect_candidates()

        expected_totals = {
            'provisionally_elected': {
                'A': 4,
                'B': 3
            },
            'continuing': {
                'C': 1
            },
            'excluded': {}
        }

        self.assertEqual(expected_totals, stv_round.results())
    def test_reallocation_of_votes_skips_provisionally_elected_candidates(
            self):
        """
        When multiple candidates have been provisionally elected their surplus
        votes needs to be reallocated for each of them in turn. When the
        reallocation happens it should skip over the other candidates that have
        already been provisionally elected.
        """

        vacancies = 3
        candidates = ('Anna', 'Amy', 'Steve', 'Norm', 'Dom')
        votes = 5 * (('Anna', 'Norm', 'Amy'), ) + \
                3 * (('Norm', 'Amy'), ) + \
                2 * (('Amy', ), )

        # Both Anna and Norm should be elected initially
        stv_round = Round(vacancies, candidates, votes)
        stv_round._provisionally_elect_candidates()

        expected_results = {
            'provisionally_elected': {
                'Anna': 5,
                'Norm': 3
            },
            'continuing': {
                'Amy': 2,
                'Dom': 0,
                'Steve': 0
            },
            'excluded': {}
        }

        self.assertEqual(expected_results, stv_round.results())

        # When we reallocate Anna's votes they should go straight to Amy,
        # rather than going to Norm
        stv_round._reassign_votes_from_candidate_with_highest_surplus()

        expected_results = {
            'provisionally_elected': {
                'Anna': 3,
                'Norm': 3
            },
            'continuing': {
                'Amy': 4,
                'Dom': 0,
                'Steve': 0
            },
            'excluded': {}
        }

        self.assertEqual(expected_results, stv_round.results())
    def test_exhausted_votes_are_not_reallocated(self):
        """
        A vote with no further preferences for other candidates shouldn't be
        reallocated. In this example Anna starts with 9 votes which means she
        has 3 surplus votes above the quota of 6. Second preference choices for
        Anna's votes are split equally between no preference, Steve and Norm.
        Steve and Norm should get 1 vote each and the final vote should
        disappear.
        """

        votes = (
            ('Anna', ),
            ('Anna', ),
            ('Anna', ),
            ('Anna', 'Steve'),
            ('Anna', 'Steve'),
            ('Anna', 'Steve'),
            ('Anna', 'Norm'),
            ('Anna', 'Norm'),
            ('Anna', 'Norm'),
            ('Steve', ),
            ('Steve', ),
            ('Steve', ),
            ('Steve', ),
            ('Norm', ),
            ('Norm', ),
            ('Norm', ),
        )

        candidates = ('Anna', 'Steve', 'Norm')
        vacancies = 2

        expected_results = {
            'provisionally_elected': {
                'Anna': 6
            },
            'continuing': {
                'Steve': 5,
                'Norm': 4
            },
            'excluded': {}
        }

        stv_round = Round(vacancies, candidates, votes)
        stv_round._provisionally_elect_candidates()
        stv_round._reassign_votes_from_candidate_with_highest_surplus()

        self.assertEqual(expected_results, stv_round.results())
    def test_reallocate_surplus_votes(self):
        """
        This test is where only one of the candidates has met the quota.
        We are testing that their surplus votes are redistributed correctly.
        """
        votes = [
            ['Green', 'Blue', 'Yellow', 'Red'],
            ['Green', 'Blue', 'Yellow', 'Red'],
            ['Green', 'Blue', 'Yellow', 'Red'],
            ['Green', 'Blue', 'Yellow', 'Red'],
            ['Green', 'Blue', 'Red', 'Yellow'],
            ['Green', 'Blue', 'Red', 'Yellow'],
            ['Green', 'Yellow', 'Red', 'Blue'],
            ['Green', 'Yellow', 'Red', 'Blue'],
            ['Green', 'Yellow', 'Blue', 'Red'],
            ['Red', 'Blue', 'Yellow', 'Green'],
            ['Red', 'Blue', 'Green', 'Yellow'],
            ['Blue', 'Green', 'Red', 'Yellow'],
            ['Blue', 'Yellow', 'Red', 'Green'],
            ['Yellow', 'Blue', 'Red', 'Green'],
            ['Yellow', 'Green', 'Red', 'Blue'],
            ['Yellow', 'Red', 'Green', 'Blue'],
            ['Yellow', 'Blue', 'Green', 'Red'],
        ]
        candidates = ('Green', 'Blue', 'Yellow', 'Red')
        vacancies = 2

        expected_reallocated_totals = {
            'provisionally_elected': {
                'Green': 6
            },
            'continuing': {
                'Red': 2,
                'Blue': 4,
                'Yellow': 5
            },
            'excluded': {}
        }

        stv_round = Round(vacancies, candidates, votes)
        stv_round._provisionally_elect_candidates()
        stv_round._reassign_votes_from_candidate_with_highest_surplus()

        self.assertEqual(expected_reallocated_totals, stv_round.results())
Ejemplo n.º 9
0
    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())