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_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())
コード例 #5
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())