Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
22b0496
Borda rule and manipulatopn heuristics algorithms-
leorasc Dec 6, 2022
e4b0c2c
undo changes
leorasc Dec 6, 2022
3f35ad5
Borda rule and manipulatopn heuristics algorithms-
leorasc Dec 6, 2022
1d13531
modified: py3votecore/borda.py
leorasc Dec 7, 2022
141b54c
Borda voting rule implemented and Largest and Average Fit.
leorasc Dec 25, 2022
5796eee
modified: py3votecore/borda_manipulation_heurestics_methods.py
leorasc Dec 26, 2022
dd6873d
modified: py3votecore/borda_manipulation_heurestics_methods.py
leorasc Dec 26, 2022
5185e44
modified: py3votecore/borda.py
leorasc Dec 27, 2022
865bc68
modified: py3votecore/borda.py
leorasc Dec 28, 2022
1ecea1e
Fixed conditions in the algorithm
leorasc Jan 2, 2023
f38508e
The heurestics modified to return a successfull manipulation if such …
leorasc Jan 6, 2023
3cc4925
modified: py3votecore/borda.py
leorasc Jan 15, 2023
c47cba5
modified: py3votecore/borda.py
leorasc Jan 15, 2023
1377cbf
Merge pull request #1 from leorasc/working_branch
leorasc Jan 15, 2023
50915b1
changes of parameters in test file, and some notes edited.
leorasc Jan 15, 2023
47b8218
modified: py3votecore/borda_manipulation_heurestics_methods.py
leorasc Jan 15, 2023
f55282c
modified: py3votecore/borda_manipulation_heurestics_methods.py
leorasc Jan 15, 2023
0d79235
Merge pull request #2 from leorasc/working_branch
leorasc Jan 15, 2023
433c06e
Added requirements and fixed a test.
leorasc Jan 16, 2023
f81648d
Merge pull request #3 from leorasc/working_branch
leorasc Jan 16, 2023
6af08d4
Added description of the added Borda votting rule and version update
leorasc Jan 16, 2023
9d1ddb1
Merge pull request #4 from leorasc/working_branch
leorasc Jan 16, 2023
be0253a
modified: README.rst
leorasc Jan 16, 2023
6faa9c1
Create requirements.txt
leorasc Jan 17, 2023
e2d3e62
Delete requirement.txt
leorasc Jan 17, 2023
b67743c
Class documentation and doctest added.
leorasc Jan 18, 2023
c157b8d
Merge branch 'master' of https://github.com/leorasc/python-vote-core
leorasc Jan 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,25 @@ Methods implemented
* Plurality (aka first-past-the-post or fptp)
* Instant-Runoff Voting (aka IRV)
* Schulze Method (aka Beatpath)
* Borda

* Multiple Winner Methods

* Plurality at large (aka block voting)
* Single Transferable Vote (aka STV)
* Schulze STV
* Borda at large

* Ordering Methods

* Schulze Proportional Representation
* Schulze Nonproportional Representation

* Manipulation Algorithms

* Average Fit (coalition manipulation heurestic for Borda votting rule)
* Largest Fit (coalition manipulation heurestic for Borda votting rule)

Basic Usage
-----------

Expand Down
36 changes: 36 additions & 0 deletions py3votecore/borda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright (C) 2023, Leora Schmerler
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name should be added to CONTRIBUTORS at the root of the repo.

#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from .abstract_classes import AbstractSingleWinnerVotingSystem
from .borda_at_large import BordaAtLarge

class Borda(AbstractSingleWinnerVotingSystem):

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add class documentation and doctest.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added. Thank you

def __init__(self, ballots, tie_breaker=None):
"""
The constructer accepts ballots of voters and a tie breaker if given.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed it does expect ballots and a tie breaker, but the format would be useful to know for anyone consuming this class. You should amend the comment to include a brief description of the expected format of the ballots.

>>> Borda([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}]) # doctest:+ELLIPSIS
<...>
>>> Borda([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], tie_breaker=["A", "B", "C", "D"]) # doctest:+ELLIPSIS
<...>
"""
super(Borda, self).__init__(ballots, BordaAtLarge, tie_breaker=tie_breaker)


if __name__ == '__main__':
import doctest
(failures, tests) = doctest.testmod(report=True)
print("{} failures, {} tests".format(failures, tests))

100 changes: 100 additions & 0 deletions py3votecore/borda_at_large.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Copyright (C) 2023, Leora Schmerler
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from .abstract_classes import MultipleWinnerVotingSystem
from .common_functions import matching_keys, unique_permutations
import copy

class BordaAtLarge(MultipleWinnerVotingSystem):

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add class documentation and doctest.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added. Thank you

def __init__(self, ballots: list, tie_breaker=None, required_winners: int=1):
"""
The constructer accepts ballots of voters, a tie breaker if given and the number of required winners.
>>> BordaAtLarge([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], required_winners = 2) # doctest:+ELLIPSIS
<...>
>>> BordaAtLarge([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], tie_breaker=["A", "B", "C", "D"] ,required_winners = 2) # doctest:+ELLIPSIS
<...>
"""
super(BordaAtLarge, self).__init__(ballots, tie_breaker=tie_breaker, required_winners=required_winners)

def calculate_results(self):
"""
calculate_results accepts an instance of Borda, and is called from the constructor.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically you would not state that it accepts anything if the only argument is the class instance itself. This should be reworded to describe what the function actually does.

>>> BordaAtLarge([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], tie_breaker=["A", "B", "C", "D"] ,required_winners = 2).calculate_results() # doctest:+ELLIPSIS
...
>>> BordaAtLarge([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], required_winners = 2).calculate_results() # doctest:+ELLIPSIS
...
"""

# Standardize the ballot format and extract the candidates
self.candidates = set()
for ballot in self.ballots:

# Convert a single candidate ballots into ballot lists
if not isinstance(ballot["ballot"], list):
ballot["ballot"] = [ballot["ballot"]]

# Ensure no ballot has an excess of votes
if len(ballot["ballot"]) < self.required_winners:
raise Exception("A ballot contained too many candidates")

# Add all candidates on the ballot to the set
self.candidates.update(set(ballot["ballot"]))

# Sum up all votes for each candidate
self.tallies = dict.fromkeys(self.candidates, 0)
for ballot in self.ballots:
for i in range(len(ballot["ballot"])):
self.tallies[ballot["ballot"][i]] += (ballot["count"]*(len(self.candidates)-1-i))
tallies = copy.deepcopy(self.tallies)

# Determine which candidates win
winning_candidates = set()
while len(winning_candidates) < self.required_winners:

# Find the remaining candidates with the most votes
largest_tally = max(tallies.values())
top_candidates = matching_keys(tallies, largest_tally)

# Reduce the found candidates if there are too many
if len(top_candidates | winning_candidates) > self.required_winners:
self.tied_winners = top_candidates.copy()
while len(top_candidates | winning_candidates) > self.required_winners:
top_candidates.remove(self.break_ties(top_candidates, True))

# Move the top candidates into the winning pile
winning_candidates |= top_candidates
for candidate in top_candidates:
del tallies[candidate]

self.winners = winning_candidates

def as_dict(self):
"""
as_dict accepts an instance of Borda, and returns a dict of the BordaAtLarge.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, reword docstring.

>>> BordaAtLarge([{ "count":3, "ballot":["A", "B", "C", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], tie_breaker=["A", "B", "C", "D"] ,required_winners = 2).as_dict() # doctest:+ELLIPSIS
{...}
>>> BordaAtLarge([{ "count":3, "ballot":["A", "C", "B", "D"]},{ "count":2, "ballot":["D", "B", "A", "C"]},{ "count":2, "ballot":["D", "B", "C", "A"]}], required_winners = 2).as_dict() # doctest:+ELLIPSIS
{...}
"""
data = super(BordaAtLarge, self).as_dict()
data["tallies"] = self.tallies
return data

if __name__ == '__main__':
import doctest
(failures, tests) = doctest.testmod(report=True)
print("{} failures, {} tests".format(failures, tests))

Loading