From bb535d3bc6c9291163795f2647a61d344f8380e6 Mon Sep 17 00:00:00 2001 From: salamb <4bilalsalam@gmail.com> Date: Sun, 30 Oct 2016 16:02:52 -0400 Subject: [PATCH 1/3] Basic implementation of ranked Pairs --- input | 7 ++ input0 | 11 +++ main.py | 21 ++++ prefpy/profile.py | 2 +- prefpy/rankedPairs.py | 223 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 input create mode 100644 input0 create mode 100644 main.py create mode 100644 prefpy/rankedPairs.py 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/main.py b/main.py new file mode 100644 index 0000000..4a05063 --- /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("input0") + + rankpairMech=RankedPairs() + myList = [(4, 'a', 'b'), (3, 'b', 'c'), (3, 'c', 'd'), (2, 'd', 'b'), (2, 'c', 'a'), (4, 'd', 'a')] + #print(rankpairMech.getWinners(edges = myList)) + print(rankpairMech.getWinners(prof = data)) + #print(rankpairMech.getOneWinner(data)) + #edgeList=rankpairMech.getSortedEdges(data) + #rankpairMech.createNXGraph(edgeList) \ 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..46a4e06 --- /dev/null +++ b/prefpy/rankedPairs.py @@ -0,0 +1,223 @@ +''' +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 getTopRank(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 + + def getNextEdge(self, currentWinners): + #function to progress through each + #list of graphs to branch to + branchedGraphList = [] + branchedEdges, branchedRemEdges = self.findTies() + #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(getTopRank(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 + + def findTies(self): + #function to find which edges are tied + branchedRemEdges = [] #list of lists + edges = self.remainingList + tmpList = [] #holds list of 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 + tmpRemEdges = copy.copy(edges) #deep copy edge list + del tmpRemEdges[i] #remove tied edge from the remaining list + branchedRemEdges.append(tmpRemEdges) + tmpList.append(copy.copy(edges[i])) #add the edge we are using to the list of edges to extend G` + return tmpList, branchedRemEdges + + +class RankedPairs(Mechanism): + def __init__(self): + pass + #returns a list of tuples(weight,label,label) + def createNXGraph(self,edgeList): + DG = nx.DiGraph() + for edge in edgeList: + DG.add_edge(edge[1], edge[2], weight=edge[0]) + #nx.draw(DG) + #stuff for drawing the graph + 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() + + #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 getNewGraph(self,prof): + #function to get the wmg and netx graph from the profile for the getOneWinner function + wmg=prof.getWmg() + DG=nx.DiGraph() + newGraph=nx.DiGraph() + edges=[] + #initialize new graph to all zero edges + edges=self.getSortedEdges(prof) + for edge in edges: + if edge[0]<0: + break + DG.add_edge(edge[1], edge[2], weight=edge[0]) + try: + #if there is no cycle the method throws an exception + nx.find_cycle(DG) + DG.remove_edge(edge[1], edge[2]) + break + # every edge is initialized to 0 + # may have to delete edge instead + except: + pass + return DG + + def getOneWinner(self,prof): + #function to get one winner + newGraph=self.getNewGraph(prof) + numCanidates=newGraph.number_of_nodes() + winners = [] + for i in newGraph.nodes(): + inedges = newGraph.in_edges(i) + if inedges == []: + winners.append(i) + return winners + + def getWinnerWithTieBreakingMech(self,prof,prefList): + pass + + def getTopRank(self, graphList): + #function to get the winners from the donelist + topRanks = [] + for bGraph in graphList: + newGraph = bGraph.DG + for i in newGraph.nodes(): + #check if the node has any in-edges + inedges = newGraph.in_edges(i) + #if it has no in-edges, then we know that it is a winner + if inedges == []: + topRanks.append(i) + return topRanks + + 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 getWinners(self, prof=None, edges=None): + #primary method to calculate multiple winners + if edges is None: + edges = self.getSortedEdges(prof) + else: + edges = sorted(edges, key=lambda weight: weight[0], reverse = True) + print(edges) + #DG = nx.DiGraph() + + #edges = self.getSortEdges(prof) #create the array of edges + 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) #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) #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 += self.getTopRank(doneList) #add the nodes returned by the getTopRank 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 getRanking(self, prof): + pass + + + + + From 98b5bd93cf7fee578617d1e1cc2222d3af860874 Mon Sep 17 00:00:00 2001 From: Levin Huang Date: Sat, 3 Dec 2016 17:04:59 -0500 Subject: [PATCH 2/3] added edge preference functionality, more comments, pruned redundant code, and added two testcases. --- 4candpartialorder.txt | 8 ++ input1.txt | 13 ++++ main.py | 8 +- prefpy/rankedPairs.py | 165 ++++++++++++++++++------------------------ 4 files changed, 97 insertions(+), 97 deletions(-) create mode 100644 4candpartialorder.txt create mode 100644 input1.txt 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/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 index 4a05063..b2cbad1 100644 --- a/main.py +++ b/main.py @@ -10,12 +10,12 @@ data=Profile({},[]) #data.importPreflibFile("4candpartialorder.txt") - data.importPreflibFile("input0") + 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(data)) - #edgeList=rankpairMech.getSortedEdges(data) - #rankpairMech.createNXGraph(edgeList) \ No newline at end of file + #print(rankpairMech.getOneWinner(prof = data)) \ No newline at end of file diff --git a/prefpy/rankedPairs.py b/prefpy/rankedPairs.py index 46a4e06..903d077 100644 --- a/prefpy/rankedPairs.py +++ b/prefpy/rankedPairs.py @@ -1,8 +1,6 @@ ''' Authors: Bilal Salam, Levin Huang, Lucky Cho ''' - - import io import math import itertools @@ -13,7 +11,7 @@ import matplotlib.pyplot as plt import copy -def getTopRank(netxGraph): +def getWinningNodes(netxGraph): #function to find the winning nodes given a networkx graph topRanks = [] for i in netxGraph.nodes(): @@ -32,12 +30,14 @@ def isDone(self): return True else: return False - - def getNextEdge(self, currentWinners): + + #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() + branchedEdges, branchedRemEdges = self.findTies(edgePrefList) #iterate through each branch of the graph for i in range(len(branchedEdges)): tmpGraph = self.DG.copy() @@ -45,7 +45,7 @@ def getNextEdge(self, currentWinners): #add the edge self.addOneWayEdge(tmpGraph, tmpEdge) #pruning check to see if we have already found the possible winners - if not set(getTopRank(tmpGraph)).issubset(set(currentWinners)): + if not set(getWinningNodes(tmpGraph)).issubset(set(currentWinners)): tmpBranchedGraph = branchedGraph(branchedRemEdges[i], tmpGraph) #add a new branched graph to the branchedGraphList branchedGraphList.append( tmpBranchedGraph) @@ -61,40 +61,50 @@ def addOneWayEdge(self, graph, edge): except: pass - def findTies(self): + #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 edges to branch out to + 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 - tmpRemEdges = copy.copy(edges) #deep copy edge list - del tmpRemEdges[i] #remove tied edge from the remaining list - branchedRemEdges.append(tmpRemEdges) 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): - def __init__(self): - pass - #returns a list of tuples(weight,label,label) - def createNXGraph(self,edgeList): - DG = nx.DiGraph() - for edge in edgeList: - DG.add_edge(edge[1], edge[2], weight=edge[0]) - #nx.draw(DG) - #stuff for drawing the graph - 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() - +#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 @@ -118,57 +128,33 @@ def drawGraph(self, DG): labels = nx.get_edge_attributes(DG, 'weight') nx.draw_networkx_edge_labels(DG, pos=nodeLayout, edge_labels=labels) #plt.show() - - - def getNewGraph(self,prof): - #function to get the wmg and netx graph from the profile for the getOneWinner function - wmg=prof.getWmg() - DG=nx.DiGraph() - newGraph=nx.DiGraph() - edges=[] - #initialize new graph to all zero edges - edges=self.getSortedEdges(prof) - for edge in edges: - if edge[0]<0: - break - DG.add_edge(edge[1], edge[2], weight=edge[0]) - try: - #if there is no cycle the method throws an exception - nx.find_cycle(DG) - DG.remove_edge(edge[1], edge[2]) - break - # every edge is initialized to 0 - # may have to delete edge instead - except: - pass - return DG - def getOneWinner(self,prof): + def getOneWinner(self, prof = None, edgePrefList = None): #function to get one winner - newGraph=self.getNewGraph(prof) - numCanidates=newGraph.number_of_nodes() - winners = [] - for i in newGraph.nodes(): - inedges = newGraph.in_edges(i) - if inedges == []: - winners.append(i) - return winners - - def getWinnerWithTieBreakingMech(self,prof,prefList): - pass + 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 - def getTopRank(self, graphList): - #function to get the winners from the donelist - topRanks = [] - for bGraph in graphList: - newGraph = bGraph.DG - for i in newGraph.nodes(): - #check if the node has any in-edges - inedges = newGraph.in_edges(i) - #if it has no in-edges, then we know that it is a winner - if inedges == []: - topRanks.append(i) - return topRanks + 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 @@ -179,43 +165,36 @@ def initDG(self, edges): DG.add_nodes_from(nodeSet) return DG - def getWinners(self, prof=None, edges=None): + def getWinners(self, prof=None, edgePrefList=None): #primary method to calculate multiple winners - if edges is None: - edges = self.getSortedEdges(prof) - else: - edges = sorted(edges, key=lambda weight: weight[0], reverse = True) - print(edges) - #DG = nx.DiGraph() + edges = self.getSortedEdges(prof) - #edges = self.getSortEdges(prof) #create the array of edges 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) #try to get the next edge from the new 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) #look at the next edge of the 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 += self.getTopRank(doneList) #add the nodes returned by the getTopRank call + 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() + plt.show()''' - return set(winners) - - - def getRanking(self, prof): - pass + 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 From 7b33120daa9eb7e4d729430c1af6f945d41f333e Mon Sep 17 00:00:00 2001 From: salamb <4bilalsalam@gmail.com> Date: Fri, 9 Dec 2016 18:23:09 -0500 Subject: [PATCH 3/3] Implemented getting one ranked pair winner or all possible ranked pair winners --- prefpy/rankedPairs.py | 17 +++++++++++++++-- requirements.txt | 5 +++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 requirements.txt diff --git a/prefpy/rankedPairs.py b/prefpy/rankedPairs.py index 903d077..4949a21 100644 --- a/prefpy/rankedPairs.py +++ b/prefpy/rankedPairs.py @@ -165,8 +165,21 @@ def initDG(self, edges): DG.add_nodes_from(nodeSet) return DG + + def getRanking(self, profile): + + raise NotImplementedError + def getWinners(self, prof=None, edgePrefList=None): - #primary method to calculate multiple winners + """ + 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 @@ -185,7 +198,7 @@ def getWinners(self, prof=None, edgePrefList=None): 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 + #print(len(doneList)) #debug statement to see how many graphs needed to be computed ''' #loop to output each completed graph for winner in doneList: 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