blob: c893adc8938f2e3b8f8ac4cdd6e78ede6e99a057 [file] [log] [blame]
You Wangb1a16c32016-05-23 15:30:52 -07001#!/usr/bin/env python
2import time
3import random
4
5class Graph:
6 """
7 Graph class provides implementations of graph algorithms.
8 The functions currently supported include:
9 - Comparing two graphs with specified attributes for vertices and edges
10 - Getting DFI (Depth First Index) and back edges during a DFS
11 - Chain decomposition of a graph
12 - Finding (non-)cut-edges and vertices
13 """
14
15 def __init__( self ):
16 # We use a dictionary to store all information about the graph
17 self.graphDict = {}
18 # Depth-first index of each vertex
19 self.DFI = {}
20 self.currentDFI = 0
21 # Parent vertex (and edge to that vertex) of each vertex in depth-first search tree
22 self.parentVertexInDFS = {}
23 self.parentEdgeInDFS = {}
24 # Back edges of the graph generated during DFS
25 self.backEdges = {}
26 # All chains in chain decomposition algorithm
27 self.chains = []
28
29 def update( self, graphDict ):
30 """
31 Update the graph data. The current graph dictionary will be replaced by the
32 new one.
33 graphDict is in a dictionary which maps each vertex to a list of attributes.
34 An example of graphDict:
35 { vertex1: { 'edges': ..., 'name': ..., 'protocol': ... },
36 vertex2: { 'edges': ..., 'name': ..., 'protocol': ... } }
37 Each vertex should at least have an 'edges' attribute which describes the
38 adjacency information. The value of 'edges' attribute is also represented by
39 a dictionary, which maps each edge (identified by the neighbor vertex) to a
40 list of attributes.
41 An example of the edges dictionary:
42 'edges': { vertex2: { 'port': ..., 'type': ... },
43 vertex3: { 'port': ..., 'type': ... } }
44 """
45 self.graphDict = graphDict
46 return main.TRUE
47
48 def compareGraphs( self, graphDictA, graphDictB, vertexAttributes=['edges'], edgeAttributes=['port'] ):
49 """
50 Compare two graphs.
51 By default only the adjacency relationship, i.e. 'port' attribute in
52 'edges' attribute for each vertex, is compared, To get other attributes
53 included, attribute name needs to be specified in the args, e.g.
54 vertexAttributes=[ 'edges', 'protocol' ] or
55 edgeAttributes=[ 'port', 'type' ]
56 Return main.TRUE if two graphs are equal, otherwise main.FALSE
57 """
58 try:
59 result = main.TRUE
60 for vertex in set( graphDictA ).difference( graphDictB ):
61 result = main.FALSE
62 main.log.warn( "Graph: graph B: vertex {} not found".format( vertex ) )
63 for vertex in set( graphDictB ).difference( graphDictA ):
64 result = main.FALSE
65 main.log.warn( "Graph: graph A: vertex {} not found".format( vertex ) )
66 for vertex in set( graphDictA ).intersection( graphDictB ):
67 for vertexAttribute in vertexAttributes:
68 attributeFound = True
69 if vertexAttribute not in graphDictA[ vertex ]:
70 main.log.warn( "Graph: graph A -> vertex {}: attribute {} not found".format( vertex, vertexAttribute ) )
71 attributeFound = False
72 if vertexAttribute not in graphDictB[ vertex ]:
73 attributeFound = False
74 main.log.warn( "Graph: graph B -> vertex {}: attribute {} not found".format( vertex, vertexAttribute ) )
75 if not attributeFound:
76 result = main.FALSE
77 continue
78 else:
79 # Compare two attributes
80 attributeValueA = graphDictA[ vertex ][ vertexAttribute ]
81 attributeValueB = graphDictB[ vertex ][ vertexAttribute ]
82 # FIXME: the comparison may not work for (sub)attribute values that are of list type
83 # For attributes except for 'edges', we just rely on '==' for comparison
84 if not vertexAttribute == 'edges':
85 if not attributeValueA == attributeValueB:
86 result = main.FALSE
87 main.log.warn( "Graph: vertex {}: {} does not match: {} and {}".format( vertex,
88 vertexAttribute,
89 attributeValueA,
90 attributeValueB ) )
91 # The structure of 'edges' is similar to that of graphs, so we use the same method for comparison
92 else:
93 edgeDictA = attributeValueA
94 edgeDictB = attributeValueB
95 for neighbor in set( edgeDictA ).difference( edgeDictB ):
96 result = main.FALSE
97 main.log.warn( "Graph: graph B -> vertex {}: neighbor {} not found".format( vertex, neighbor ) )
98 for neighbor in set( edgeDictB ).difference( edgeDictA ):
99 result = main.FALSE
100 main.log.warn( "Graph: graph A -> vertex {}: neighbor {} not found".format( vertex, neighbor ) )
101 for neighbor in set( edgeDictA ).intersection( edgeDictB ):
102 for edgeAttribute in edgeAttributes:
103 attributeFound = True
104 if edgeAttribute not in edgeDictA[ neighbor ]:
105 attributeFound = False
106 main.log.warn( "Graph: graph A -> vertex {} -> neighbor {}: attribute {} not found".format( vertex,
107 neighbor,
108 edgeAttribute ) )
109 if edgeAttribute not in edgeDictB[ neighbor ]:
110 attributeFound = False
111 main.log.warn( "Graph: graph B -> vertex {} -> neighbor {}: attribute {} not found".format( vertex,
112 neighbor,
113 edgeAttribute ) )
114 if not attributeFound:
115 result = main.FALSE
116 continue
117 else:
118 # Compare two attributes
119 attributeValueA = edgeDictA[ neighbor ][ edgeAttribute ]
120 attributeValueB = edgeDictB[ neighbor ][ edgeAttribute ]
121 if not attributeValueA == attributeValueB:
122 result = main.FALSE
123 main.log.warn( "Graph: vertex {} -> neighbor {}: {} does not match: {} and {}".format( vertex,
124 neighbor,
125 edgeAttribute,
126 attributeValueA,
127 attributeValueB ) )
128 if not result:
129 main.log.debug( "Graph: graphDictA: {}".format( graphDictA ) )
130 main.log.debug( "Graph: graphDictB: {}".format( graphDictB ) )
131 return result
132 except TypeError:
133 main.log.exception( "Graph: TypeError exception found" )
134 return main.ERROR
135 except KeyError:
136 main.log.exception( "Graph: KeyError exception found" )
137 return main.ERROR
138 except Exception:
139 main.log.exception( "Graph: Uncaught exception" )
140 return main.ERROR
141
142 def getNonCutEdges( self ):
143 """
144 Get a list of non-cut-edges (non-bridges).
145 The definition of a cut-edge (bridge) is: the deletion of a cut-edge will
146 increase the number of connected component of a graph.
147 The function is realized by impelementing Schmidt's algorithm based on
148 chain decomposition.
149 Returns a list of edges, e.g.
150 [ [ vertex1, vertex2 ], [ vertex2, vertex3 ] ]
151 """
152 try:
153 if not self.depthFirstSearch():
154 return None
155 if not self.findChains():
156 return None
157 nonCutEdges = []
158 for chain in self.chains:
159 for edge in chain:
160 nonCutEdges.append( edge )
161 main.log.debug( 'Non-cut-edges: {}'.format( nonCutEdges ) )
162 return nonCutEdges
163 except Exception:
164 main.log.exception( "Graph: Uncaught exception" )
165 return None
166
167 def getNonCutVertices( self ):
168 """
169 Get a list of non-cut-vertices.
170 The definition of a cut-vertex is: the deletion of a cut-vertex will
171 increase the number of connected component of a graph.
172 The function is realized by impelementing Schmidt's algorithm based on
173 chain decomposition.
174 Returns a list of vertices, e.g. [ vertex1, vertex2, vertex3 ]
175 """
176 try:
177 nonCutEdges = self.getNonCutEdges()
178 # find all cycle chains
179 cycleChains = []
180 for chain in self.chains:
181 # if the source vertex of the first chain equals to the destination vertex of the last
182 # chain, the chain is a cycle chain
183 if chain[ 0 ][ 0 ] == chain[ -1 ][ 1 ]:
184 cycleChains.append( chain )
185 main.log.debug( 'Cycle chains: {}'.format( cycleChains ) )
186 # Get a set of vertices which are the first vertices of a cycle chain (excluding the first
187 # cycle chain), and these vertices are a subset of all cut-vertices
188 subsetOfCutVertices = []
189 if len( cycleChains ) > 1:
190 for cycleChain in cycleChains[ 1: ]:
191 subsetOfCutVertices.append( cycleChain[ 0 ][ 0 ] )
192 main.log.debug( 'Subset of cut vertices: {}'.format( subsetOfCutVertices ) )
193 nonCutVertices = []
194 assert nonCutEdges != None
195 for vertex in self.graphDict.keys():
196 if vertex in subsetOfCutVertices:
197 continue
198 vertexIsNonCut = True
199 for neighbor in self.graphDict[ vertex ][ 'edges' ].keys():
200 edge = [ vertex, neighbor ]
201 backwardEdge = [ neighbor, vertex ]
202 if not edge in nonCutEdges and not backwardEdge in nonCutEdges:
203 vertexIsNonCut = False
204 break
205 if vertexIsNonCut:
206 nonCutVertices.append( vertex )
207 main.log.debug( 'Non-cut-vertices: {}'.format( nonCutVertices ) )
208 return nonCutVertices
209 except KeyError:
210 main.log.exception( "Graph: KeyError exception found" )
211 return None
212 except AssertionError:
213 main.log.exception( "Graph: AssertionError exception found" )
214 return None
215 except Exception:
216 main.log.exception( "Graph: Uncaught exception" )
217 return None
218
219 def depthFirstSearch( self ):
220 """
221 This function runs a depth-first search and gets DFI of each vertex as well
222 as generates the back edges
223 """
224 try:
225 assert self.graphDict != None and len( self.graphDict ) != 0
226 for vertex in self.graphDict.keys():
227 self.DFI[ vertex ] = -1
228 self.parentVertexInDFS[ vertex ] = ''
229 self.parentEdgeInDFS[ vertex ] = None
230 firstVertex = self.graphDict.keys()[ 0 ]
231 self.currentDFI = 0
232 self.backEdges = {}
233 if not self.depthFirstSearchRecursive( firstVertex ):
234 return main.ERROR
235 return main.TRUE
236 except KeyError:
237 main.log.exception( "Graph: KeyError exception found" )
238 return main.ERROR
239 except AssertionError:
240 main.log.exception( "Graph: AssertionError exception found" )
241 return main.ERROR
242 except Exception:
243 main.log.exception( "Graph: Uncaught exception" )
244 return main.ERROR
245
246 def depthFirstSearchRecursive( self, vertex ):
247 """
248 Recursive function for depth-first search
249 """
250 try:
251 self.DFI[ vertex ] = self.currentDFI
252 self.currentDFI += 1
253 for neighbor in self.graphDict[ vertex ][ 'edges' ].keys():
254 edge = [ vertex, neighbor ]
255 backwardEdge = [ neighbor, vertex ]
256 if neighbor == self.parentVertexInDFS[ vertex ]:
257 continue
258 elif self.DFI[ neighbor ] == -1:
259 self.parentVertexInDFS[ neighbor ] = vertex
260 self.parentEdgeInDFS[ neighbor ] = backwardEdge
261 if not self.depthFirstSearchRecursive( neighbor ):
262 return main.ERROR
263 else:
264 key = self.DFI[ neighbor ]
265 if key in self.backEdges.keys():
266 if not edge in self.backEdges[ key ] and\
267 not backwardEdge in self.backEdges[ key ]:
268 self.backEdges[ key ].append( backwardEdge )
269 else:
270 tempKey = self.DFI[ vertex ]
271 if tempKey in self.backEdges.keys():
272 if not edge in self.backEdges[ tempKey ] and\
273 not backwardEdge in self.backEdges[ tempKey ]:
274 self.backEdges[ key ] = [ backwardEdge ]
275 else:
276 self.backEdges[ key ] = [ backwardEdge ]
277 return main.TRUE
278 except KeyError:
279 main.log.exception( "Graph: KeyError exception found" )
280 return main.ERROR
281 except Exception:
282 main.log.exception( "Graph: Uncaught exception" )
283 return main.ERROR
284
285 def findChains( self ):
286 """
287 This function finds all the chains in chain-decomposition algorithm
288 """
289 keyList = self.backEdges.keys()
290 keyList.sort()
291 vertexIsVisited = {}
292 self.chains = []
293 for vertex in self.graphDict.keys():
294 vertexIsVisited[ vertex ] = False
295 try:
296 for key in keyList:
297 backEdgeList = self.backEdges[ key ]
298 for edge in backEdgeList:
299 chain = []
300 currentEdge = edge
301 sourceVertex = edge[ 0 ]
302 while True:
303 currentVertex = currentEdge[ 0 ]
304 nextVertex = currentEdge[ 1 ]
305 vertexIsVisited[ currentVertex ] = True
306 chain.append( currentEdge )
307 if nextVertex == sourceVertex or vertexIsVisited[ nextVertex ] == True:
308 break
309 currentEdge = self.parentEdgeInDFS[ nextVertex ]
310 self.chains.append( chain )
311 return main.TRUE
312 except KeyError:
313 main.log.exception( "Graph: KeyError exception found" )
314 return main.ERROR
315 except Exception:
316 main.log.exception( "Graph: Uncaught exception" )
317 return main.ERROR