diff --git a/4candpartialorder.txt b/4candpartialorder.txt new file mode 100644 index 0000000..326b234 --- /dev/null +++ b/4candpartialorder.txt @@ -0,0 +1,8 @@ +4 +1,a +2,b +3,c +4,d +2,2,2 +1,1,2 +1,2,3 \ No newline at end of file diff --git a/input b/input new file mode 100644 index 0000000..4bebc23 --- /dev/null +++ b/input @@ -0,0 +1,7 @@ +3 +1,a +2,b +3,c +4,4,2 +1,1,2,3 +3,3,1,2 diff --git a/input0 b/input0 new file mode 100644 index 0000000..4aa9fbe --- /dev/null +++ b/input0 @@ -0,0 +1,11 @@ +3 +1,a +2,b +3,c +6,6,6 +1,1,2,3 +1,1,3,2 +1,2,3,1 +1,2,1,3 +1,3,1,2 +1,3,2,1 \ No newline at end of file diff --git a/input1.txt b/input1.txt new file mode 100644 index 0000000..0057edf --- /dev/null +++ b/input1.txt @@ -0,0 +1,13 @@ +6 +1,a +2,b +3,c +4,d +5,e +6,f +5,5,5 +1,4,2 +1,4,1 +1,3,4 +1,6,4 +1,1,6 \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..b2cbad1 --- /dev/null +++ b/main.py @@ -0,0 +1,21 @@ +import prefpy +from prefpy import preference +from prefpy import profile + +from prefpy.rankedPairs import RankedPairs +from prefpy.profile import Profile + +if __name__ == '__main__' : + + data=Profile({},[]) + + #data.importPreflibFile("4candpartialorder.txt") + data.importPreflibFile("input1.txt") + + rankpairMech=RankedPairs() + myList = [(4, 'a', 'b'), (3, 'b', 'c'), (3, 'c', 'd'), (2, 'd', 'b'), (2, 'c', 'a'), (4, 'd', 'a')] + myEdgePrefList = [('a', 'b'), ('a', 'c'), ('b', 'c'), ('c', 'b'), ('b', 'a'), ('c', 'a'), ] + #print(rankpairMech.getWinners(edges = myList)) + #print(rankpairMech.getWinners(prof = data, edgePrefList = myEdgePrefList)) + print(rankpairMech.getWinners(prof = data)) + #print(rankpairMech.getOneWinner(prof = data)) \ No newline at end of file diff --git a/prefpy/profile.py b/prefpy/profile.py index 34951f5..b4b1d8b 100644 --- a/prefpy/profile.py +++ b/prefpy/profile.py @@ -2,7 +2,7 @@ Author: Kevin J. Hwang """ import copy -import io +from . import io import itertools import math import json diff --git a/prefpy/rankedPairs.py b/prefpy/rankedPairs.py new file mode 100644 index 0000000..4949a21 --- /dev/null +++ b/prefpy/rankedPairs.py @@ -0,0 +1,215 @@ +''' +Authors: Bilal Salam, Levin Huang, Lucky Cho +''' +import io +import math +import itertools +from prefpy.profile import Profile +from prefpy.preference import Preference +from prefpy.mechanism import Mechanism +import networkx as nx +import matplotlib.pyplot as plt +import copy + +def getWinningNodes(netxGraph): + #function to find the winning nodes given a networkx graph + topRanks = [] + for i in netxGraph.nodes(): + if netxGraph.in_edges(i) == []: + topRanks.append(i) + return topRanks + +class branchedGraph(): + def __init__(self, rL, DG): + self.remainingList = rL + self.DG = DG + + def isDone(self): + #function to check if the graph is done + if len(self.remainingList) ==0: + return True + else: + return False + + #currentWinners - list of string names of known winners + #edgePrefList - [(node1, node2), (node2, node3)] list of edges sorted in decreasing order of preference, each tuple is an edge + def getNextEdge(self, currentWinners, edgePrefList = None): + #function to progress through each + #list of graphs to branch to + branchedGraphList = [] + branchedEdges, branchedRemEdges = self.findTies(edgePrefList) + #iterate through each branch of the graph + for i in range(len(branchedEdges)): + tmpGraph = self.DG.copy() + tmpEdge = branchedEdges[i] + #add the edge + self.addOneWayEdge(tmpGraph, tmpEdge) + #pruning check to see if we have already found the possible winners + if not set(getWinningNodes(tmpGraph)).issubset(set(currentWinners)): + tmpBranchedGraph = branchedGraph(branchedRemEdges[i], tmpGraph) + #add a new branched graph to the branchedGraphList + branchedGraphList.append( tmpBranchedGraph) + return branchedGraphList + + def addOneWayEdge(self, graph, edge): + #function to check whether or not we want to add the edge in the case of the graph already having an opposite edge + if not graph.has_edge(edge[2], edge[1]): + graph.add_edge(edge[1], edge[2], weight=edge[0]) + try: + nx.find_cycle(graph) + graph.remove_edge(edge[1], edge[2]) + except: + pass + + #edgePrefList - [(node1, node2), (node2, node3)] list of edges sorted in decreasing order of preference, each tuple is an edge + def findTies(self, edgePrefList = None): + #function to find which edges are tied + branchedRemEdges = [] #list of lists + edges = self.remainingList + tmpList = [] #holds list of tied edges to branch out to + tmpRemEdges = [] + + if len(edges) > 0: #we know that we don't care about negative edge weights + tmpWeight = edges[0][0] + #loop through the list of edges + for i in range(len(edges)): + if edges[i][0] == tmpWeight: #we find tied edge that we want to use + tmpList.append(copy.copy(edges[i])) #add the edge we are using to the list of edges to extend G` + if edgePrefList is not None: + if len(tmpList) > 0: + tmpList = self.getBestEdge(tmpList, edgePrefList) + for i in range(len(edges)): + tmpRemEdges = copy.copy(edges) #deep copy edge list + del tmpRemEdges[i] #remove tied edge from the remaining list + branchedRemEdges.append(tmpRemEdges) + return tmpList, branchedRemEdges + + def getBestEdge(self, edgeList, edgePrefList): + bestIndex = None + edgeListIndex = 0 + for i in range(0, len(edgeList)): + edge = edgeList[i] + try: + tmpBestIndex = edgePrefList.index((edge[1], edge[2])) + if bestIndex is None or bestIndex > tmpBestIndex: + bestIndex = tmpBestIndex + edgeListIndex = i + except ValueError: + print('edge does not exist in preference list, user did not give preference over all edges') + return [edgeList[edgeListIndex]] + + + + + +class RankedPairs(Mechanism): +#Calculates winners using the RankedPairs voting mechanism + + #returns edges from the wmg sorted by weights + def getSortedEdges(self,prof): + #initialize the wmg from the given profile + wmg = prof.getWmg() + # empty array to hold the edges + edges = [] + #add edges to the array + for cand1 in prof.candMap.keys(): + for cand2 in prof.candMap.keys(): + if cand1 is not cand2: + edges.append((wmg[cand1][cand2], prof.candMap[cand1], prof.candMap[cand2])) + #sort the edges + edges = sorted(edges, key=lambda weight: weight[0], reverse = True) + return edges + + def drawGraph(self, DG): + #function to draw the given networkx graph. + plt.figure() + nodeLayout=nx.spring_layout(DG) + nx.draw_networkx(DG,pos=nodeLayout,arrows=True) + labels = nx.get_edge_attributes(DG, 'weight') + nx.draw_networkx_edge_labels(DG, pos=nodeLayout, edge_labels=labels) + #plt.show() + + def getOneWinner(self, prof = None, edgePrefList = None): + #function to get one winner + edges = self.getSortedEdges(prof) + DG=self.initDG(edges)#get the networkx Directed Graph from the set of edges + winners = [] #list to hold the winners + doneList = [] #list of the graphs that have been completed + newBranchedGraph = branchedGraph(edges, DG) #create the first branched graph + graphs = newBranchedGraph.getNextEdge(winners, edgePrefList) #try to get the next edge from the new branched graph + + while(len(graphs) != 0): + #Iterating through graphs, appending to tmp graphs list such that we arent modifying the list we are iterating over + tmpGraphs = [] + graphs = graphs[0:1] + graph=graphs.pop(0) #we've finished looking at this graph + tmpNextEdgeList = graph.getNextEdge(winners, edgePrefList) #look at the next edge of the graph + if tmpNextEdgeList == [] and graph.isDone(): #we know that the graph is complete if it satisfies these conditions + doneList.append(graph) + winners += getWinningNodes(graph.DG) #add the nodes returned by the getWinningNodes call + else: + graphs=tmpNextEdgeList+graphs + print(len(doneList)) #debug statement to see how many graphs needed to be computed + #loop to output each completed graph + for winner in doneList: + self.drawGraph(winner.DG) + plt.show() + return set(winners) + + def initDG(self, edges): + #function to intialize the directed graph + nodeSet=set() + DG = nx.DiGraph() + for i in range(len(edges)): + nodeSet.add(edges[i][1]) + DG.add_nodes_from(nodeSet) + return DG + + + def getRanking(self, profile): + + raise NotImplementedError + + def getWinners(self, prof=None, edgePrefList=None): + """ + prof Profile + edgePrefList- list of tuples representing a preference over wmg edges + primary method to calculate multiple winners + returns the set of winning nodes and then the linear orders justifying each + winner in the form of networkx objects + + """ + + edges = self.getSortedEdges(prof) + + DG=self.initDG(edges)#get the networkx Directed Graph from the set of edges + winners = [] #list to hold the winners + doneList = [] #list of the graphs that have been completed + newBranchedGraph = branchedGraph(edges, DG) #create the first branched graph + graphs = newBranchedGraph.getNextEdge(winners, edgePrefList) #try to get the next edge from the new branched graph + + while(len(graphs) != 0): + #Iterating through graphs, appending to tmp graphs list such that we arent modifying the list we are iterating over + tmpGraphs = [] + graph=graphs.pop(0) #we've finished looking at this graph + tmpNextEdgeList = graph.getNextEdge(winners, edgePrefList) #look at the next edge of the graph + if tmpNextEdgeList == [] and graph.isDone(): #we know that the graph is complete if it satisfies these conditions + doneList.append(graph) + winners += getWinningNodes(graph.DG) #add the nodes returned by the getWinningNodes call + else: + graphs=tmpNextEdgeList+graphs + #print(len(doneList)) #debug statement to see how many graphs needed to be computed + ''' + #loop to output each completed graph + for winner in doneList: + self.drawGraph(winner.DG) + plt.show()''' + + return set(winners), [graph.DG for graph in doneList] + #returns the set of winning nodes and then the linear orders justifying each winner in the form of networkx objects + #uncomment the above loop for a visual representation + + + + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cc0ed78 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +matplotlib==1.5.3 +networkx==1.11 +numpy==1.11.2 +scipy==0.18.1 +six==1.10.0