Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cumulative voting #106

Closed
wants to merge 10 commits into from
Closed

Cumulative voting #106

wants to merge 10 commits into from

Conversation

cdonnay
Copy link
Collaborator

@cdonnay cdonnay commented Nov 8, 2023

This PR adds functionality for points based ballots. I felt like this deserved a new class, as it really is distinct from full rankings.

  • add PointBallot class
  • adapt PreferenceProfile to handle new ballot class
  • add Cumulative ballot generator class to create ballots for cumulative style elections.
  • add a HighestScore election that takes a PreferenceProfile of PointBallots and chooses the $m$ candidates with highest scores.

@jamesturk @drdeford @jgibson517 @jennjwang @ziglaser

@cdonnay
Copy link
Collaborator Author

cdonnay commented Nov 8, 2023

Looks like creating this new PointBallot class has thrown a lot of type errors in mypy. Would be happy to chat about the best way to address that!

@jgibson517
Copy link
Member

Hi @cdonnay, I view the Ballot object as more of data structure then an object that dictates what type of ranking system is used. Instead I might lean towards adding an optional points attribute to the existing class.

A couple thoughts here: When we first designed the class, I think we didn't think users would really need to touch it much, but I don't think that's really the case anymore. So I think if we give a little more flexibility in how we initialize a Ballot, we won't need a second class.

I think an optional points method in the form of dictionary or vector can live in Ballot class. I also like in your PointBallot you default the weights to 1 if a user does not specify, we should adopt that into the Ballot object. Since the rest of ballot functionality is not exposed to the user (i.e users don't adjust ballot attributes when running elections), I think its more streamlined to have a single Python object do the work. Happy to chat about this more too, if helpful!!

Copy link
Collaborator

@jamesturk jamesturk left a comment

Choose a reason for hiding this comment

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

Left some inline comments, overall I'd agree with @jgibson517 that it'd make sense to combine these into one type to avoid the duplication. The places where they differ could be smoothed out by adding additional helper methods onto Ballot itself, or within Cumulative

src/votekit/ballot_generator.py Show resolved Hide resolved
tiebreak=self.tiebreak)

if ranking != untied_ranking:
print("A tie was broken.")
Copy link
Collaborator

Choose a reason for hiding this comment

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

probably want to avoid print here, maybe logging or adding some other way of checking this after the fact if desired

@@ -13,7 +14,7 @@ class Election(ABC):
"""

def __init__(self, profile: PreferenceProfile, ballot_ties: bool = True):
if ballot_ties:
if ballot_ties and isinstance(profile.ballots[0], Ballot):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think a comment here to clarify this logic would be helpful

weight = ballot.weight / num_ballots
else:

if isinstance(self.ballots[0], Ballot):
Copy link
Collaborator

Choose a reason for hiding this comment

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

a lot of shared code between these two, moot if they merge, but I'd refactor this to avoid duplication if not

writer.writeheader()
for ballot in self.ballots:
writer.writerow({"weight": ballot.weight, "ranking": ballot.ranking})
if isinstance(self.ballots[0], Ballot):
Copy link
Collaborator

Choose a reason for hiding this comment

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

same as above comment, lots of duplication here when the two types can probably share most of the logic

@jamesturk
Copy link
Collaborator

re: mypy errors
If this does go forward as two separate classes, PointBallot should probably inherit from Ballot which will help with the type issues but also break the current isinstance checks. It'd be preferred here to have the two share an explicit interface, which is part of what mypy is so upset about. Once that's true they can be used almost interchangably, relying on checking for PointBallot only when its customizations are needed.

@cdonnay
Copy link
Collaborator Author

cdonnay commented Nov 14, 2023

Alright, let me take another go at this using just a Ballot class, and implementing some kind of point system within that.

@cdonnay cdonnay closed this Jan 10, 2024
@cdonnay cdonnay deleted the cumulative_voting branch March 1, 2024 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants