def summarize(name, results, field): print print "== Results from %s" % name print print "Votes from %s." % (", ".join(sorted(r[field] for r in results))) print rankings = collections.Counter(r[RANK] for r in results).items() rankings.sort(key=lambda x: x[1], reverse=True) ballots = [] for order, count in rankings: print "%3d %s" % (count, order) ballots.append({ "count": count, "ballot": [[x] for x in order.split(",")] }) from pyvotecore.schulze_method import SchulzeMethod from pyvotecore.condorcet import CondorcetHelper schulze = SchulzeMethod( ballots, ballot_notation=CondorcetHelper.BALLOT_NOTATION_GROUPING) print if hasattr(schulze, "tied_winners"): print "*Schulze winners: tie between %s*" % (", ".join( schulze.tied_winners)) else: print "*Schulze winner: %s*" % schulze.winner print print "Algorithm details:" print "```" pprint.pprint(schulze.as_dict()) print "```"
def condorcet_results(self): result = Condorcet( list(self.hashes_with_counts(self.ballots_as_rankings())), ballot_notation=Schulze.BALLOT_NOTATION_RANKING, ).as_dict() if not result.get('tied_winners'): return [result['winner']] else: return []
def condorcet_results(self): result = Condorcet( list(self.hashes_with_counts(self.ballots_as_rankings())), ballot_notation=Schulze.BALLOT_NOTATION_RANKING, ).as_dict() if not result.get('tied_winners'): return [result['winner']] else: return []
def test_ranking_format(self): # Generate data input = [{ "count": 12, "ballot": { "Andrea": 1, "Brad": 2, "Carter": 3 } }, { "count": 26, "ballot": { "Andrea": 1, "Carter": 2, "Brad": 3 } }, { "count": 12, "ballot": { "Andrea": 1, "Carter": 2, "Brad": 3 } }, { "count": 13, "ballot": { "Carter": 1, "Andrea": 2, "Brad": 3 } }, { "count": 27, "ballot": { "Brad": 1 } }] output = SchulzeMethod( input, ballot_notation=SchulzeMethod.BALLOT_NOTATION_RANKING).as_dict() # Run tests self.assertEqual( output, { "candidates": set(['Carter', 'Brad', 'Andrea']), "pairs": { ('Andrea', 'Brad'): 63, ('Brad', 'Carter'): 39, ('Carter', 'Andrea'): 13, ('Andrea', 'Carter'): 50, ('Brad', 'Andrea'): 27, ('Carter', 'Brad'): 51 }, "strong_pairs": { ('Andrea', 'Brad'): 63, ('Carter', 'Brad'): 51, ('Andrea', 'Carter'): 50 }, "winner": 'Andrea' })
def test_tiebreaker_bug(self): # Generate data input = [ { "count": 1, "ballot": [["A"], ["B", "C"]] }, { "count": 1, "ballot": [["B"], ["A"], ["C"]] }, ] output = SchulzeMethod(input, ballot_notation="grouping").as_dict() # Run tests self.assertEqual(output['candidates'], set(['A', 'B', 'C'])) self.assertEqual( output['pairs'], { ('A', 'B'): 1, ('A', 'C'): 2, ('B', 'A'): 1, ('B', 'C'): 1, ('C', 'A'): 0, ('C', 'B'): 0, }) self.assertEqual(output['strong_pairs'], { ('A', 'C'): 2, ('B', 'C'): 1, }) self.assertEqual(output['tied_winners'], set(['A', 'B']))
def handle_close(self): # IMPORTANT! Use deepcopy, we don't want the SchulzePollPlugin to modify our ballots, # just calculate a result ballots = deepcopy(self.context.ballots) if ballots: schulze_ballots = self.schulze_format_ballots(ballots) self.context.poll_result = SchulzeMethod( schulze_ballots, ballot_notation="ranking" ).as_dict() else: raise HTTPForbidden(_("No votes, cancel the poll instead."))
def calculateVote(db, event, break_ties=False): '''Calculate the winner for this event based on current votes''' #return a dict of all votes for all users for a single event votes = {} event_votes = db.query(User.name, Vote.rank, Restaurant.name).join(Vote).join(Restaurant).filter(Vote.event==event.id) if event_votes.count() > 0: for user, rank, rest in event_votes.all(): entry = votes.get(user, {}) entry[rest] = rank votes[user] = entry #Format the list into the input for Schulze input = [] for v in votes: input.append({"count":1, "ballot":copy(votes[v])}) output = SchulzeMethod(input, ballot_notation="rating").as_dict() tb_user = None if "tied_winners" in output and break_ties: #Randomly select a tie-breaker user among those voters tied for the lowest tb_count users = db.query(User).join(Vote).filter(Vote.event==event.id).order_by(User.tb_count).all() users = [U for U in users if U.tb_count == users[0].tb_count] tb_user = random.choice(users) tb_votes = event_votes.filter(Vote.user == tb_user.id).order_by(Vote.rank.desc()).all() tb_list = [x[2] for x in tb_votes] #Update the user's tb_count tb_user.tb_count += 1 db.commit() output = SchulzeMethod(input, tie_breaker=tb_list, ballot_notation="rating").as_dict() return output, tb_user else: #No votes?! return None, ""
def add_ballots(self, ballots, tie_breaker=None, ballot_notation=None, reverse=False): """ Add ballots of ordered issues. Ballots are translated to their complementary graph using PyVoteCoreAssistance and the graph's edges values are added/deducted from the IssuesGraph instance. Example: input = [{'ballot': [[1], [2], [3], [4], [5]], 'count': 3}, {'ballot': [[5], [2], [3], [4], [1]], 'count': 9}, {'ballot': [[5], [1], [3], [4], [1]], 'count': 8}, {'ballot': [[3], [2], [4], [1], [5]], 'count': 5}, {'ballot': [[1], [2], [3], [4], [5]], 'count': 5}] g.add_ballots(input) use reverse=True to deduct the ballot from the IssuesGraph, reversing it's effect. """ output = SchulzeMethod(ballots, ballot_notation="grouping").as_dict( ) #TODO: remove this line when iteritems bug is solved assistance = PyVoteCoreAssistance() assistance.add_ballot(ballots, tie_breaker, ballot_notation) for edge_key in assistance.pairs.keys(): try: from_node = IssueNode.objects.get(issue_id=edge_key[0]) except IssueNode.DoesNotExist: try: new_issue = Issue.objects.get(id=edge_key[0]) from_node = self.add_node(new_issue) except Issue.DoesNotExist: raise # TODO: decide on proper exception try: to_node = IssueNode.objects.get(issue_id=edge_key[1]) except IssueNode.DoesNotExist: try: new_issue = Issue.objects.get(id=edge_key[1]) to_node = self.add_node(new_issue) except Issue.DoesNotExist: raise # TODO: decide on proper exception edge = IssueEdge.objects.get(graph=self, from_node=from_node, to_node=to_node) if reverse: edge.weight -= assistance.pairs[edge_key] else: edge.weight += assistance.pairs[edge_key] edge.save()
def test_grouping_format(self): # Generate data input = [ { "count": 12, "ballot": [["Andrea"], ["Brad"], ["Carter"]] }, { "count": 26, "ballot": [["Andrea"], ["Carter"], ["Brad"]] }, { "count": 12, "ballot": [["Andrea"], ["Carter"], ["Brad"]] }, { "count": 13, "ballot": [["Carter"], ["Andrea"], ["Brad"]] }, { "count": 27, "ballot": [["Brad"]] }, ] output = SchulzeMethod(input, ballot_notation="grouping").as_dict() # Run tests self.assertEqual( output, { "candidates": set(['Carter', 'Brad', 'Andrea']), "pairs": { ('Andrea', 'Brad'): 63, ('Brad', 'Carter'): 39, ('Carter', 'Andrea'): 13, ('Andrea', 'Carter'): 50, ('Brad', 'Andrea'): 27, ('Carter', 'Brad'): 51 }, "strong_pairs": { ('Andrea', 'Brad'): 63, ('Carter', 'Brad'): 51, ('Andrea', 'Carter'): 50 }, "winner": 'Andrea' })
def do_POST(self): try: # Parse the incoming data request_raw = self.rfile.read(int(self.headers["content-length"])) request_raw = re.sub("\n", "", request_raw) request = json.loads(request_raw) # Send the data to the requested voting system if request["voting_system"] == "plurality": system = Plurality(request["ballots"]) elif request["voting_system"] == "plurality_at_large": system = PluralityAtLarge(request["ballots"], required_winners = request["winners"]) elif request["voting_system"] == "irv": system = IRV(request["ballots"]) elif request["voting_system"] == "stv": system = STV(request["ballots"], required_winners = request["winners"]) elif request["voting_system"] == "schulze_method": system = SchulzeMethod(request["ballots"], ballot_notation = request["notation"]) elif request["voting_system"] == "schulze_stv": system = SchulzeSTV(request["ballots"], required_winners = request["winners"], ballot_notation = request["notation"]) elif request["voting_system"] == "schulze_pr": system = SchulzePR(request["ballots"], winner_threshold = request["winners"], ballot_notation = request["notation"]) else: raise Exception("No voting system specified") response = system.as_dict() # Ensure a response came back from the voting system if response == None: raise except: fp = StringIO.StringIO() traceback.print_exc(10,fp) response = fp.getvalue() self.send_response(500) else: self.send_response(200) finally: response = json.dumps(self.__simplify_object__(response)) self.send_header("Content-type", "application/json") self.send_header("Content-length", str(len(response))) self.end_headers() self.wfile.write(response)
def handle_close(self): """ Calculate results per round instead. Each round has exactly 1 winner. (It could be a randomized tie though) """ # IMPORTANT! Use deepcopy, we don't want the SortedSchulzePollPlugin to modify our ballots, # just calculate a result ballots = deepcopy(self.context.ballots) wcount = self.context.poll_settings.get("winners", 0) if ballots: candidates_left = set(self.context.proposals) if wcount and len(self.context.proposals) > wcount: rounds = wcount else: rounds = len(self.context.proposals) schulze_ballots = self.schulze_format_ballots(ballots) round_data = [] for i in range(rounds): if len(candidates_left) > 1: # SchulzeMethod changed the ballots, so we need another copy here! res = SchulzeMethod( deepcopy(schulze_ballots), ballot_notation="ranking" ).as_dict() round_data.append(res) schulze_ballots = self.eliminate_candidate( res["winner"], schulze_ballots ) candidates_left.remove(res["winner"]) else: # Only 1 candidate left round_data.append({"winner": list(candidates_left)[0]}) self.context.poll_result = { "rounds": round_data, "candidates": set(self.context.proposals), "winners": [x["winner"] for x in round_data], } else: raise HTTPForbidden(_("No votes, cancel the poll instead."))
def _calculate_results(unresolved): """ Given a QuerySet of unresolved topics (with expired deadlines), resolve them into results, possibly indicating a tie of some sort. TODO: Handle the case of abstaning being a "winning" option. """ extra = '' for topic in unresolved: # if topic.agenda.quorum: # raise Exception, '%d / %d' % (topic.agenda.quorum, topic.num_voters()) if topic.agenda.quorum and topic.num_voters() < topic.agenda.quorum: # Mark invalid with no results. Somewhat counter-intuitively, # we consider the results to have been "calculated" in this case # (specifically, we calculated that there are no valid results). topic.invalid = True topic.result_calculated = True topic.save() continue # Currently, proceed with results even if there are fewer results # than expected. It is conceivable that this is a valid outcome, # for instance, if only three candidates receive any votes in a Board # election, then there should be only three winners even though there # are four or five positions. options = topic.counted_options().filter(num_votes__gt=0) num_options = options.count() if topic.vote_type.name == TYPE_CHARTER: # Charter amendments require a 2/3 majority. for_option = options.get(name='For') against_option = options.get(name='Against') total_votes = for_option.num_votes + against_option.num_votes if for_option.num_votes >= (total_votes * (2.0 / 3.0)): for_option.result = True for_option.save() else: against_option.result = True against_option.save() # It's not possible for this sort of measure to tie- it either reaches # the two-thirds threshold or it does not. topic.invalid = False topic.result_calculated = True topic.save() extra = ('\n\nPlease note that Charter Amendments require a 2/3 ' 'majority to pass.') elif not topic.vote_type.max_votes: # evaluate Schulze method. First collect votes, than use library. voters = User.objects.filter(votes__option__topic=topic).distinct() options = topic.options.all() ballots = [] for voter in voters: votes = Vote.objects.filter(option__in=options, voter=voter)\ .order_by('rank') ordered_votes = [] last_rank = votes[0].rank - 1 current_rank = 0 for vote in votes: if vote.rank == last_rank: ordered_votes[-1].append(vote.option_id) else: ordered_votes.append([vote.option_id]) last_rank = vote.rank current_rank += 1 vote.rank = current_rank vote.save() ballot = {'ballot': ordered_votes} ballots.append(ballot) result = SchulzeMethod(ballots, ballot_notation="grouping") if hasattr(result, 'tied_winners'): # Schulze method can be tied as well, treat as other ties options = Option.objects.filter(id__in=result.tied_winners) for option in options: option.result = True option.save() topic.invalid = True topic.result_calculated = True topic.save() else: option = Option.objects.get(id=result.winner) option.result = True option.save() options = options.exclude(id=result.winner) for option in options: option.result = False option.save() topic.result_calculated = True topic.save() elif topic.vote_type.max_votes <= topic.vote_type.max_winners: # Flag ties that affect the validity of the results, # and set any tied options after the valid winning range # to a "winning" result as well, indicating that they are all # equally plausible as winners despite producing more winners # than are allowed. i = topic.vote_type.max_winners while (i > 0 and i < num_options and options[i - 1].num_votes == options[i].num_votes): topic.invalid = True # Note that setting options[i].result = True and then # saving options[i] does not work, probably as a side effect # of evaluationg the options queryset (it's not actualy an array) option = options[i] option.result = True option.save() i += 1 # Set all non-tied winning results. for option in options[0:topic.vote_type.max_winners]: option.result = True option.save() topic.result_calculated = True topic.save() else: return _send_result_email(topic, extra)
def test_wiki_example2(self): # Generate data input = [{ "count": 5, "ballot": [["A"], ["C"], ["B"], ["E"], ["D"]] }, { "count": 5, "ballot": [["A"], ["D"], ["E"], ["C"], ["B"]] }, { "count": 8, "ballot": [["B"], ["E"], ["D"], ["A"], ["C"]] }, { "count": 3, "ballot": [["C"], ["A"], ["B"], ["E"], ["D"]] }, { "count": 7, "ballot": [["C"], ["A"], ["E"], ["B"], ["D"]] }, { "count": 2, "ballot": [["C"], ["B"], ["A"], ["D"], ["E"]] }, { "count": 7, "ballot": [["D"], ["C"], ["E"], ["B"], ["A"]] }, { "count": 8, "ballot": [["E"], ["B"], ["A"], ["D"], ["C"]] }] output = SchulzeMethod(input, ballot_notation="grouping").as_dict() # Run tests self.assertEqual( output, { 'candidates': set(['A', 'C', 'B', 'E', 'D']), 'pairs': { ('A', 'B'): 20, ('A', 'C'): 26, ('A', 'D'): 30, ('A', 'E'): 22, ('B', 'A'): 25, ('B', 'C'): 16, ('B', 'D'): 33, ('B', 'E'): 18, ('C', 'A'): 19, ('C', 'B'): 29, ('C', 'D'): 17, ('C', 'E'): 24, ('D', 'A'): 15, ('D', 'B'): 12, ('D', 'C'): 28, ('D', 'E'): 14, ('E', 'A'): 23, ('E', 'B'): 27, ('E', 'C'): 21, ('E', 'D'): 31 }, 'strong_pairs': { ('B', 'D'): 33, ('E', 'D'): 31, ('A', 'D'): 30, ('C', 'B'): 29, ('D', 'C'): 28, ('E', 'B'): 27, ('A', 'C'): 26, ('B', 'A'): 25, ('C', 'E'): 24, ('E', 'A'): 23 }, 'actions': [{ 'edges': set([('E', 'A')]) }, { 'edges': set([('C', 'E')]) }, { 'nodes': set(['A', 'C', 'B', 'D']) }], 'winner': 'E' })
#!/usr/bin/env python from pyvotecore.schulze_method import SchulzeMethod from pyvotecore.condorcet import CondorcetHelper from allvotes import ballots from pprint import pprint pprint(SchulzeMethod(ballots, ballot_notation = CondorcetHelper.BALLOT_NOTATION_GROUPING).as_dict())