What do family holidays and budget decisions in the Democratic caucus of the Colorado House of Representatives have in common? They are both, it turns out, pretty good use cases of quadratic voting (QV).
If you’re not familiar with QV try going here where Vitalik Buterin goes through it at some length. Basically it is a mechanism of voting designed to take into account aggregate desire. In one-person-one-vote (OPOV) you and I could vote for two opposing groups and our votes would get the same weighting, however, you may be close to indifferent between the two options whilst I may be strongly in favour of one. In OPOV there is no way to take this gap in desire into account. An alternative could be to let everyone bid on options and the option with the highest amount bid on gets chosen, again though we face an issue. If Tigbert has 100x the wealth as Sigbert then even if Tigbert doesn’t have much stronger preferences than Sigbert he would be quite likely to get his way. One way we could solve this is to give everyone 100 tokens and then get them to spend them on options, this moves us closer to what I used but has one potential drawback. The marginal cost of a unit of influence (1 vote) is constant and so gaining an additional unit of influence for your choice is costless. (obviously it still costs the marginal cost, it is just that there is no relative marginal cost difference between options and so you have no opportunity cost through having a greater potential influence if you vote for an option you have voted for less up until then)
Enter QV. Here you are endowed with 100 tokens and every vote for an option costs the square of the number of times you have voted for that option plus one. So your first vote for holiday A would cost you 1 token, your second vote for holiday A 4 tokens, then 9 tokens etc.
This works perfectly for the holiday situation.
- There are a finite number of options (everyone proposed 1 option)
- Everyone likely has some loose ordering of preferences
- Everyone likely has different levels of desire for each option.
It is important to note the difference between the bottom two points. Say you have transitive preferences a>b>c, here we know you prefer a to b and b to c but we know nothing of the relative strengths. You may prefer a hugely over b and then really only prefer b a small bit over c. This is something rank choice preferences alone can’t communicate, but QV can. So with this in mind I wrote the code below to take peoples voting preferences.
A few things to note about the code:
- It is much longer than one would have to make it but I wanted to make it very user friendly and robust to errors with input. Also it is not as concise as it likely could be as I made the programme rather quickly.
- Also why 100 tokens? Well I did a practice with 10 but with this small a number you can only vote for the same option twice before it becomes too expensive and at that point you have only used half your tokens, however, with 100 you can get through 90% of your tokens voting for the same option before it becomes too expensive and so I felt people could express their preferences better without the risk of wasting tokens if they started with 100. Also the fewer tokens the more likely to have ties and I wanted to reduce this chance.
So what were the results?
{‘Costa Rica’: 10, ‘Lake Como’: 15, ‘Tulum’: 18, ‘France/Switzerland/Italy’: 14, ‘Mykonos’: 12} and so Tulum came out the winner. We also see a nice ordering of options for the other destinations.
A few notes on the results:
- In total there were 69 votes cast, the maximum number possible would be 80 (if you voted as evenly between the options as possible) and the minimum 30 (if you only voted for one option until it became too expensive) and so this would suggest that people were relatively even with their distribution of votes but at least a few people had stronger preferences.
- Also it should be noted that the ‘Lake Como’ and the ‘France/Switzerland/Italy’ option may have received votes that someone wouldn’t have minded giving to either one as they were the most similar options. If one was truly indifferent between the two then they should vote for them both as equally as possible, however, it may have been the case that the person just voted for one option a lot. Because of the quadratic pricing if we have two people who were indifferent between the options and they both vote as equally for the two options as possible then they could each vote for one option 4 times and the other 5 times giving a total of 9 votes to each option between the two people. If however they just voted for one option as much as possible as they were indifferent and didn’t see the harm in this they could only vote for that 6 times and so between the two people each option would have 50% fewer votes under this situation. I don’t know if this happened but it is possible and what’s more it is also not possible to just add the votes of the two options in the actual results to see what a combined option would have received.
Anyway, the code is below and I thought this was a fun use of what is still quite a novel mechanism. It also seems to have actually been useful in so far as placing numbers that have some meaningful value behind them to each option.
from IPython.display import clear_output
tokens_per_person = 100
options = ['Costa Rica', 'Lake Como', 'Tulum', 'France/Switzerland/Italy', 'Mykonos']
voters = 5
total_donation_dict = {}
for i in range(len(options)):
total_donation_dict[options[i]] = 0
for i in range(1, voters+1):
redo = True
while redo:
print(f"\nVoter {i}") # this is not working always says voter 3
voter_tokens = tokens_per_person
donation_num = 1
donation_dict = {}
for val in range(len(options)):
donation_dict[options[val]] = 0
while (voter_tokens > 0):
#give feedback on past votes and future costs
print("COST TO DONATE TO:")
for num in range(len(options)):
print(f" {options[num]} = {(donation_dict[options[num]]+1)**2}")
# takes vote or allows candidate to not vote anymore
answering = True
while answering:
answer = int(input(f"""
Who do you want to donate to? \n{options}
e.g type 1 for {options[0]}, 2 for {options[1]} etc, or 0 if you don't want to make any more votes.
"""))
if answer in range(1,len(options)+1):
if voter_tokens >= ((donation_dict[options[answer-1]]+1)**2): # +1 is here as voting again will cost the square of the next vote count
donation_num += 1
# allocate votes
donation_dict[options[answer-1]] += 1
clear_output()
# deduct tokens
voter_tokens -= (donation_dict[options[answer-1]])**2
print(f"remaining voter tokens = {voter_tokens}")
print(f"current votes: {donation_dict}\n")
answering = False
else:
print(f'\n***Not enough tokens to vote for {options[answer-1]}***')
continue
elif answer == 0:
voter_tokens = 0
answering = False
else:
continue
# break loop if they don't have enough tokens to make any votes
a = list(map(lambda x: (x+1)**2, donation_dict.values()))
if voter_tokens < min(a):
voter_tokens = 0
clear_output()
# present their votes, ask if they want to redo, if not then update total tally's
redo_ask = True
while redo_ask:
submit = input(
f"""Here are your votes {donation_dict}\nif you're happy to submit, type 'y' then hit enter,
if you want to redo your votes, type 'n' then hit enter
""")
if submit == 'y':
for option in donation_dict.keys():
total_donation_dict[option]+=donation_dict[option]
redo = False
clear_output()
redo_ask = False
elif submit == 'n':
clear_output()
print('redoing')
redo_ask = False
else:
continue
print(f'overall final votes: {total_donation_dict}')