Package obitools :: Package graph
[hide private]
[frames] | no frames]

Source Code for Package obitools.graph

  1  ''' 
  2  This module offert classes to manipulate graphs 
  3  ''' 
  4   
  5  import sys 
  6   
  7   
  8  from obitools.utils import progressBar 
  9   
 10   
11 -class Indexer(dict):
12 ''' 13 Allow to manage convertion between an arbitrarly hashable python 14 value and an unique integer key 15 ''' 16
17 - def __init__(self):
18 ''' 19 Constructor 20 ''' 21 self.__max=0 22 self.__reverse=[]
23
24 - def getLabel(self,index):
25 ''' 26 Return the python value associated to an integer index. 27 28 @param index: an index value 29 @type index: integer 30 31 @raise IndexError: if the index is not used in this 32 Indexer instance 33 ''' 34 return self.__reverse[index]
35
36 - def getIndex(self,key,strict=False):
37 if dict.__contains__(self,key): 38 return dict.__getitem__(self,key) 39 elif strict: 40 raise KeyError,key 41 else: 42 value = self.__max 43 self[key]= value 44 self.__reverse.append(key) 45 self.__max+=1 46 return value
47
48 - def __getitem__(self,key):
49 ''' 50 Return the integer key associated to a python value. 51 52 @param key: the value to index 53 @type key: an hashable python value 54 @return: an unique integer value associated to the key 55 @rtype: int 56 57 ''' 58 return self.getIndex(key)
59
60 - def __equal__(self,index):
61 ''' 62 Implement equa operator. Two Indexer instances are 63 equals if they are physically the same instance 64 @param index: the seconde Indexer 65 @type index: an Indexer instance 66 67 @return: True is the two instances are the same 68 @rtype: bool 69 ''' 70 return id(self)==id(index)
71 72
73 -class Graph(object):
74 ''' 75 Class used to represent directed or undirected graph. 76 77 Only one edge can connect two nodes 78 '''
79 - def __init__(self,label='G',directed=False,indexer=None,nodes=None,edges=None):
80 ''' 81 Graph constructor. 82 83 @param label: Graph name, set to 'G' by default 84 @type label: str 85 @param directed: true for directed graph, set to False by defalt 86 @type directed: boolean 87 @param indexer: node label indexer 88 @type indexer: Indexer instance 89 @param nodes: set of nodes to add to the graph 90 @type nodes: iterable value 91 @param edges: set of edges to add to the graph 92 @type edges: iterable value 93 ''' 94 self._directed=directed 95 if indexer is None: 96 indexer = Indexer() 97 self._index = indexer 98 self._node = {} 99 self._node_attrs = {} 100 self._edge_attrs = {} 101 self._label=label
102
103 - def newEmpty(self):
104 n = Graph(self._label+"_compact",self._directed,self._index) 105 return n
106
107 - def addNode(self,node=None,index=None,**data):
108 if index is None: 109 index = self._index[node] 110 111 if index not in self._node: 112 self._node[index]=set() 113 114 if data: 115 if index in self._node_attrs: 116 self._node_attrs[index].update(data) 117 else: 118 self._node_attrs[index]=dict(data) 119 return index
120
121 - def getNode(self,node=None,index=None):
122 if index is None: 123 index = self._index.getIndex(node, True) 124 return Node(index,self)
125
126 - def getBestNode(self,estimator):
127 bestScore=0 128 best=None 129 for n in self: 130 score = estimator(n) 131 if best is None or score > bestScore: 132 bestScore = score 133 best=n 134 return best
135 136
137 - def delNode(self,node=None,index=None):
138 if index is None: 139 index = self._index[node] 140 for n in self._node: 141 if n!=index: 142 e = self._node[n] 143 if index in e: 144 if (n,index) in self._edge_attrs: 145 del self._edge_attrs[(n,index)] 146 e.remove(index) 147 e = self._node[index] 148 for n in e: 149 if (index,n) in self._edge_attrs: 150 del self._edge_attrs[(index,n)] 151 del self._node[index] 152 if index in self._node_attrs: 153 del self._node_attrs[index]
154 155
156 - def addEdge(self,node1=None,node2=None,index1=None,index2=None,**data):
157 index1=self.addNode(node1, index1) 158 index2=self.addNode(node2, index2) 159 160 self._node[index1].add(index2) 161 162 if not self._directed: 163 self._node[index2].add(index1) 164 165 if data: 166 if (index1,index2) not in self._edge_attrs: 167 data =dict(data) 168 self._edge_attrs[(index1,index2)]=data 169 if not self._directed: 170 self._edge_attrs[(index2,index1)]=data 171 else: 172 self._edge_attrs.update(data) 173 174 return (index1,index2)
175
176 - def getEdge(self,node1=None,node2=None,index1=None,index2=None):
177 ''' 178 179 @param node1: 180 @type node1: 181 @param node2: 182 @type node2: 183 @param index1: 184 @type index1: 185 @param index2: 186 @type index2: 187 ''' 188 node1=self.getNode(node1, index1) 189 node2=self.getNode(node2, index2) 190 return Edge(node1,node2)
191
192 - def delEdge(self,node1=None,node2=None,index1=None,index2=None):
193 if index1 is None: 194 index1 = self._index[node1] 195 if index2 is None: 196 index2 = self._index[node2] 197 if index1 in self._node and index2 in self._node[index]: 198 self._node[index1].remove(index2) 199 if (index1,index2) in self._node_attrs: 200 del self._node_attrs[(index1,index2)] 201 if not self._directed: 202 self._node[index2].remove(index1) 203 if (index2,index1) in self._node_attrs: 204 del self._node_attrs[(index2,index1)]
205
206 - def edgeIterator(self,predicat=None):
207 for n1 in self._node: 208 for n2 in self._node[n1]: 209 if self._directed or n1 <= n2: 210 e = self.getEdge(index1=n1, index2=n2) 211 if predicat is None or predicat(e): 212 yield e
213 214
215 - def nodeIterator(self,predicat=None):
216 for n in self._node: 217 node = self.getNode(index=n) 218 if predicat is None or predicat(node): 219 yield node
220
221 - def nodeIndexIterator(self,predicat=None):
222 for n in self._node: 223 node = self.getNode(index=n) 224 if predicat is None or predicat(node): 225 yield n
226
227 - def neighbourIndexSet(self,node=None,index=None):
228 if index is None: 229 index=self.getNode(node).index 230 return self._node[index]
231
232 - def edgeCount(self):
233 n = reduce(lambda x,y:x+y, (len(z) for z in self._node.itervalues()),0) 234 if not self._directed: 235 n=n/2 236 return n
237
238 - def subgraph(self,nodes,name='G'):
239 sub = Graph(name,self._directed,self._index) 240 if not isinstance(nodes, set): 241 nodes = set(nodes) 242 for n in nodes: 243 sub._node[n]=nodes & self._node[n] 244 if n in self._node_attrs: 245 sub._node_attrs[n]=dict(self._node_attrs) 246 for n2 in sub._node[n]: 247 if not self._directed: 248 if n < n2: 249 if (n,n2) in self._edge_attrs: 250 data=dict(self._edge_attrs[(n,n2)]) 251 sub._edge_attrs[(n,n2)]=data 252 sub._edge_attrs[(n2,n)]=data 253 else: 254 if (n,n2) in self._edge_attrs: 255 data=dict(self._edge_attrs[(n,n2)]) 256 sub._edge_attrs[(n,n2)]=data 257 return sub
258
259 - def __len__(self):
260 return len(self._node)
261
262 - def __getitem__(self,key):
263 return self.getNode(node=key)
264
265 - def __delitem__(self,key):
266 self.delNode(node=key)
267
268 - def __iter__(self):
269 return self.nodeIterator()
270
271 - def __str__(self):
272 if self._directed: 273 kw ='digraph' 274 else: 275 kw='graph' 276 277 nodes = "\n ".join([str(x) for x in self]) 278 edges = "\n ".join([str(x) for x in self.edgeIterator()]) 279 280 return "%s %s {\n %s\n\n %s\n}" % (kw,self._label,nodes,edges)
281
282 -class Node(object):
283 - def __init__(self,index,graph):
284 ''' 285 Constructor on Node instance. 286 287 Normaly this constructor is only used by Graph methods. 288 289 @param index: Index of the node in the graph 290 @type index: int 291 @param graph: graph instance owning the node 292 @type graph: obitools.graph.Graph 293 ''' 294 self.index = index 295 self.__graph = graph
296
297 - def getGraph(self):
298 ''' 299 return graph owning this node. 300 301 @rtype: L{obitools.graph.Graph} 302 ''' 303 return self.__graph
304 305
306 - def getLabel(self):
307 ''' 308 return label associated to this node. 309 ''' 310 return self.__graph._index.getLabel(self.index)
311 312
313 - def has_key(self,key):
314 ''' 315 test is the node instance has a property named 'key'. 316 317 @param key: the name of a property 318 @type key: str 319 320 @return: True if the nade has a property named <key> 321 @rtype: bool 322 ''' 323 if self.index in self.__graph._node_attrs: 324 return key in self.__graph._node_attrs[self.index] 325 else: 326 return False
327
328 - def neighbourIterator(self,nodePredicat=None,edgePredicat=None):
329 ''' 330 iterate through the nodes directly connected to 331 this node. 332 333 @param nodePredicat: a function accepting one node as parameter 334 and returning True if this node must be 335 returned by the iterator. 336 @type nodePredicat: function 337 338 @param edgePredicat: a function accepting one edge as parameter 339 and returning True if the edge linking self and 340 the current must be considered. 341 @type edgePredicat: function 342 343 344 @rtype: iterator on Node instances 345 ''' 346 for n in self.neighbourIndexIterator(nodePredicat, edgePredicat): 347 node = self.graph.getNode(index=n) 348 yield node
349
350 - def neighbourIndexSet(self):
351 ''' 352 Return a set of node indexes directely connected 353 to this node. 354 355 @attention: do not change this set unless you know 356 exactly what you do. 357 358 @rtype: set of int 359 ''' 360 return self.__graph._node[self.index]
361
362 - def neighbourIndexIterator(self,nodePredicat=None,edgePredicat=None):
363 ''' 364 iterate through the node indexes directly connected to 365 this node. 366 367 @param nodePredicat: a function accepting one node as parameter 368 and returning True if this node must be 369 returned by the iterator. 370 @type nodePredicat: function 371 372 @param edgePredicat: a function accepting one edge as parameter 373 and returning True if the edge linking self and 374 the current must be considered. 375 @type edgePredicat: function 376 377 @rtype: iterator on int 378 ''' 379 for n in self.neighbourIndexSet(): 380 if nodePredicat is None or nodePredicat(self.__graph.getNode(index=n)): 381 if edgePredicat is None or edgePredicat(self.__graph.getEdge(index1=self.index,index2=n)): 382 yield n
383
384 - def degree(self,nodeIndexes=None):
385 ''' 386 return count of edges linking this node to the 387 set of nodes describes by their index in nodeIndexes 388 389 @param nodeIndexes: set of node indexes. 390 if set to None, all nodes of the 391 graph are take into account. 392 Set to None by default. 393 @type nodeIndexes: set of int 394 395 @rtype: int 396 ''' 397 if nodeIndexes is None: 398 return len(self.__graph._node[self.index]) 399 else: 400 return len(self.__graph._node[self.index] & nodeIndexes)
401
402 - def componentIndexSet(self,nodePredicat=None,edgePredicat=None):
403 ''' 404 Return the set of node index in the same connected component. 405 406 @param nodePredicat: a function accepting one node as parameter 407 and returning True if this node must be 408 returned by the iterator. 409 @type nodePredicat: function 410 411 @param edgePredicat: a function accepting one edge as parameter 412 and returning True if the edge linking self and 413 the current must be considered. 414 @type edgePredicat: function 415 416 417 @rtype: set of int 418 ''' 419 cc=set([self.index]) 420 added = set(x for x in self.neighbourIndexIterator(nodePredicat, edgePredicat)) 421 while added: 422 cc |= added 423 added = reduce(lambda x,y : x | y, 424 (set(z for z in self.graph.getNode(index=c).neighbourIndexIterator(nodePredicat, edgePredicat)) 425 for c in added), 426 set()) 427 added -= cc 428 return cc
429
430 - def componentIterator(self,nodePredicat=None,edgePredicat=None):
431 ''' 432 Iterate through the nodes in the same connected 433 component. 434 435 @rtype: iterator on L{Node} instance 436 ''' 437 for c in self.componentIndexSet(nodePredicat, edgePredicat): 438 yield self.graph.getNode(c)
439
440 - def shortestPathIterator(self,nodes=None):
441 ''' 442 Iterate through the shortest path sourcing 443 from this node. if nodes is not None, iterates 444 only path linkink this node to one node listed in 445 nodes 446 447 @param nodes: set of node index 448 @type nodes: iterable on int 449 450 @return: an iterator on list of int describing path 451 @rtype: iterator on list of int 452 ''' 453 if nodes is not None: 454 nodes = set(nodes) 455 456 457 Q=[(self.index,-1)] 458 459 gray = set([self.index]) 460 paths = {} 461 462 while Q and (nodes is None or nodes): 463 u,p = Q.pop() 464 paths[u]=p 465 next = self.graph._node[u] - gray 466 gray|=next 467 Q.extend((x,u) for x in next) 468 if nodes is None or u in nodes: 469 if nodes: 470 nodes.remove(u) 471 path = [u] 472 while p >= 0: 473 path.append(p) 474 p = paths[p] 475 path.reverse() 476 yield path
477
478 - def shortestPathTo(self,node=None,index=None):
479 ''' 480 return one of the shortest path linking this 481 node to specified node. 482 483 @param node: a node label or None 484 @param index: a node index or None. the parameter index 485 has a priority on the parameter node. 486 @type index: int 487 488 @return: list of node index corresponding to the path or None 489 if no path exists. 490 @rtype: list of int or None 491 ''' 492 if index is None: 493 index=self.graph.getNode(node).index 494 for p in self.shortestPathIterator([index]): 495 return p
496 497
498 - def __getitem__(self,key):
499 ''' 500 return the value of the <key> property of this node 501 502 @param key: the name of a property 503 @type key: str 504 ''' 505 return self.__graph._node_attrs.get(self.index,{})[key]
506
507 - def __setitem__(self,key,value):
508 ''' 509 set the value of a node property. In the property doesn't 510 already exist a new property is added to this node. 511 512 @param key: the name of a property 513 @type key: str 514 @param value: the value of the property 515 516 @see: L{Node.__getitem__} 517 ''' 518 if self.index in self.__graph._node_attrs: 519 data = self.__graph._node_attrs[self.index] 520 data[key]=value 521 else: 522 self.graph._node_attrs[self.index]={key:value}
523
524 - def __len__(self):
525 ''' 526 Count neighbour of this node 527 528 @rtype: int 529 530 @see: L{Node.degree} 531 ''' 532 return len(self.__graph._node[self.index])
533
534 - def __iter__(self):
535 ''' 536 iterate through neighbour of this node 537 538 @rtype: iterator in L{Node} instances 539 540 @see: L{Node.neighbourIterator} 541 ''' 542 return self.neighbourIterator()
543
544 - def __contains__(self,key):
545 return self.has_key(key)
546
547 - def __str__(self):
548 549 if self.index in self.__graph._node_attrs: 550 keys = " ".join(['%s="%s"' % (x[0],str(x[1]).replace('"','\\"').replace('\n','\\n')) 551 for x in self.__graph._node_attrs[self.index].iteritems()] 552 ) 553 else: 554 keys='' 555 556 return '%d [label="%s" %s]' % (self.index,str(self.label).replace('"','\\"').replace('\n','\\n'),keys)
557 558 label = property(getLabel, None, None, "Label of the node") 559 560 graph = property(getGraph, None, None, "Graph owning this node")
561 562 563
564 -class Edge(object):
565 - def __init__(self,node1,node2):
566 self.node1 = node1 567 self.node2 = node2
568
569 - def getGraph(self):
570 return self.node1.graph
571
572 - def has_key(self,key):
573 ''' 574 test is the edge instance has a property named 'key'. 575 576 @param key: the name of a property 577 @type key: str 578 579 @return: True if the edge has a property named <key> 580 @rtype: bool 581 ''' 582 if (self.node1.index,self.node2.index) in self.graph._edge_attrs: 583 return key in self.graph._edge_attrs[(self.node1.index,self.node2.index)] 584 else: 585 return False
586 587
588 - def getDirected(self):
589 return self.node1.graph._directed
590
591 - def __getitem__(self,key):
592 return self.graph._edge_attrs.get((self.node1.index,self.node2.index),{})[key]
593
594 - def __setitem__(self,key,value):
595 e = (self.node1.index,self.node2.index) 596 if e in self.graph._edge_attrs: 597 data = self.graph._edge_attrs[e] 598 data[key]=value 599 else: 600 self.graph._edge_attrs[e]={key:value}
601
602 - def __str__(self):
603 e = (self.node1.index,self.node2.index) 604 if e in self.graph._edge_attrs: 605 keys = "[%s]" % " ".join(['%s="%s"' % (x[0],str(x[1]).replace('"','\\"')) 606 for x in self.graph._edge_attrs[e].iteritems()] 607 ) 608 else: 609 keys = "" 610 611 if self.directed: 612 link='->' 613 else: 614 link='--' 615 616 return "%d %s %d %s" % (self.node1.index,link,self.node2.index,keys)
617
618 - def __contains__(self,key):
619 return self.has_key(key)
620 621 622 graph = property(getGraph, None, None, "Graph owning this edge") 623 624 directed = property(getDirected, None, None, "Directed's Docstring")
625 626
627 -class DiGraph(Graph):
628 - def __init__(self,label='G',indexer=None,nodes=None,edges=None):
629 ''' 630 Directed Graph constructor. 631 632 @param label: Graph name, set to 'G' by default 633 @type label: str 634 @param indexer: node label indexer 635 @type indexer: Indexer instance 636 @param nodes: set of nodes to add to the graph 637 @type nodes: iterable value 638 @param edges: set of edges to add to the graph 639 @type edges: iterable value 640 ''' 641 642 Graph.__init__(self, label, True, indexer, nodes, edges)
643
644 -class UndirectedGraph(Graph):
645 - def __init__(self,label='G',indexer=None,nodes=None,edges=None):
646 ''' 647 Directed Graph constructor. 648 649 @param label: Graph name, set to 'G' by default 650 @type label: str 651 @param indexer: node label indexer 652 @type indexer: Indexer instance 653 @param nodes: set of nodes to add to the graph 654 @type nodes: iterable value 655 @param edges: set of edges to add to the graph 656 @type edges: iterable value 657 ''' 658 659 Graph.__init__(self, label, False, indexer, nodes, edges)
660 661 662
663 -def selectEdgeAttributeFactory(attribut,value):
664 def selectEdge(e): 665 return attribut in e and e[attribut]==value
666 return selectEdge 667