def __init__(self,V=None,E=None,directed=True): if V is None: V=[] if E is None: E=[] self.directed = directed self.sV = Poset(V) self.sE = Poset([]) self.degenerated_edges=[] if len(self.sV)==1: v = self.sV.o[0] v.c = self for e in v.e: e.detach() return for e in E: x = e.v[0] y = e.v[1] if (x in V) and (y in V): if e.deg==0: e.detach() self.degenerated_edges.append(e) continue e.attach() self.sE.add(e) if x.c is None: x.c=Poset([x]) if y.c is None: y.c=Poset([y]) if id(x.c)!=id(y.c): x.c.update(y.c) y.c=x.c s=x.c else: raise ValueError,'unknown Vertex (%s or %s)'%(x.data,y.data) #check if graph is connected: for v in self.V(): if v.c is None or (v.c!=s): raise ValueError,'unconnected Vertex %s'%v.data else: v.c = self
class graph_core(object): def __init__(self,V=None,E=None,directed=True): if V is None: V=[] if E is None: E=[] self.directed = directed self.sV = Poset(V) self.sE = Poset([]) self.degenerated_edges=[] if len(self.sV)==1: v = self.sV.o[0] v.c = self for e in v.e: e.detach() return for e in E: x = e.v[0] y = e.v[1] if (x in V) and (y in V): if e.deg==0: e.detach() self.degenerated_edges.append(e) continue e.attach() self.sE.add(e) if x.c is None: x.c=Poset([x]) if y.c is None: y.c=Poset([y]) if id(x.c)!=id(y.c): x.c.update(y.c) y.c=x.c s=x.c else: raise ValueError,'unknown Vertex (%s or %s)'%(x.data,y.data) #check if graph is connected: for v in self.V(): if v.c is None or (v.c!=s): raise ValueError,'unconnected Vertex %s'%v.data else: v.c = self # allow a graph_core to hold a single vertex: def add_single_vertex(self,v): if len(self.sE)==0 and len(self.sV)==0: self.sV.add(v) v.c = self # add edge e. At least one of its vertex must belong to the graph, # the other being added automatically. def add_edge(self,e): if e not in self.sE: x = e.v[0] y = e.v[1] if not ((x in self.sV) or (y in self.sV)): raise ValueError,'unconnected edge' self.sV.add(x) self.sV.add(y) e.attach() self.sE.add(e) x.c = self y.c = self # remove Edge : # this procedure checks that the resulting graph is connex. def remove_edge(self,e): if e.deg==0: return e.detach() # check if still connected (path is not oriented here): if not self.path(e.v[0],e.v[1]): # return to inital state by reconnecting everything: e.attach() # exit with exception! raise ValueError,e else: self.sE.remove(e) # remove Vertex: # this procedure checks that the resulting graph is connex. def remove_vertex(self,x): V = x.N() #get all neighbor vertices to check paths E = x.detach() #remove the edges from x and neighbors list # now we need to check if all neighbors are still connected, # and it is sufficient to check if one of them is connected to # all others: v0 = V.pop(0) for v in V: if not self.path(v0,v): # repair everything and raise exception if not connected: for e in E: e.attach() raise ValueError,x # remove edges and vertex from internal sets: for e in E: self.sE.remove(e) self.sV.remove(x) x.c = None # generates an iterator over vertices, with optional filter def V(self,cond=None): V = self.sV if cond is None: cond=(lambda x:True) for v in V: if cond(v): yield v # generates an iterator over edges, with optional filter def E(self,cond=None): E = self.sE if cond is None: cond=(lambda x:True) for e in E: if cond(e): yield e # vertex/edge properties : #------------------------- # returns number of vertices def order(self): return len(self.sV) # returns number of edges def norm(self): return len(self.sE) # returns the minimum degree def deg_min(self): return min(map(vertex_core.deg,self.sV)) # returns the maximum degree def deg_max(self): return max(map(vertex_core.deg,self.sV)) # returns the average degree d(G) def deg_avg(self): return sum(map(vertex_core.deg,self.sV))/float(self.order()) # returns the epsilon value (number of edges of G per vertex) def eps(self): return float(self.norm())/self.order() # shortest path between vertices x and y by breadth-first descent def path(self,x,y,f_io=0,hook=None): assert x in self.sV assert y in self.sV if x==y: return [] if f_io!=0: assert self.directed==True # path: p = None if hook is None: hook = lambda x:False # apply hook: hook(x) # visisted: v = {x:None} # queue: q = [x] while (not p) and len(q)>0: c = q.pop(0) for n in c.N(f_io): if not v.has_key(n): hook(n) v[n] = c if n==y: p = [n] q.append(n) if p: break #now we fill the path p backward from y to x: while p and p[0]!=x: p.insert(0,v[p[0]]) return p # shortest weighted-edges paths between x and all other vertices # by dijkstra's algorithm with heap used as priority queue. def dijkstra(self,x,f_io=0,hook=None): from collections import defaultdict from heapq import heappop, heappush if x not in self.sV: return None if f_io!=0: assert self.directed==True # take a shallow copy of the set of vertices containing those for which # the shortest path to x needs to be computed: S = self.sV.copy() # initiate with path to itself... v = x # D is the returned vector of distances: D = defaultdict(lambda :None) D[v] = 0.0 L = [(D[v],v)] while len(S)>0: #pdb.set_trace() l,u = heappop(L) S.remove(u) for e in u.e: v = e.v[0] if (u is e.v[1]) else e.v[1] Dv = l+e.w if D[v]!=None: # check if heap/D needs updating: # ignore if a shorter path was found already... if Dv<D[v]: for i,t in enumerate(L): if t[1] is v: L.pop(i) break D[v]=Dv heappush(L,(Dv,v)) else: D[v]=Dv heappush(L,(Dv,v)) return D # returns the set of strongly connected components # ("scs") by using Tarjan algorithm. # These are maximal sets of vertices such that there is a path from each # vertex to every other vertex. # The algorithm performs a DFS from the provided list of root vertices. # A cycle is of course a strongly connected component, # but a strongly connected component can include several cycles. # The Feedback Acyclic Set of edge to be removed/reversed is provided by # marking the edges with a "feedback" flag. # Complexity is O(V+E). def get_scs_with_feedback(self,roots): from sys import getrecursionlimit,setrecursionlimit limit=getrecursionlimit() N=self.norm()+10 if N>limit: setrecursionlimit(N) def _visit(v,L): v.ind = v.ncur v.lowlink = v.ncur Vertex.ncur += 1 self.tstack.append(v) v.mark = True for e in v.e_out(): w = e.v[1] if w.ind==0: _visit(w,L) v.lowlink = min(v.lowlink,w.lowlink) elif w.mark: e.feedback = True if w in self.tstack: v.lowlink = min(v.lowlink,w.ind) if v.lowlink==v.ind: l=[self.tstack.pop()] while l[0]!=v: l.insert(0,self.tstack.pop()) #print "unstacked %s"%('-'.join([x.data[1:13] for x in l])) L.append(l) v.mark=False self.tstack=[] scs = [] Vertex.ncur=1 for v in self.sV: v.ind=0 # start exploring tree from roots: for v in roots: if v.ind==0: _visit(v,scs) # now possibly unvisited vertices: for v in self.sV: if v.ind==0: _visit(v,scs) # clean up Tarjan-specific data: for v in self.sV: del v.ind del v.lowlink del v.mark del Vertex.ncur del self.tstack setrecursionlimit(limit) return scs # returns neighbours of a vertex v: # f_io=-1 : parent nodes # f_io=+1 : child nodes # f_io= 0 : all (default) def N(self,v,f_io=0): return v.N(f_io) # general graph properties: # ------------------------- # returns True iff # - o is a subgraph of self, or # - o is a vertex in self, or # - o is an edge in self def __contains__(self,o): try: return o.sV.issubset(self.sV) and o.sE.issubset(self.sE) except AttributeError: return ((o in self.sV) or (o in self.sE)) # merge graph_core G into self def union_update(self,G): for v in G.sV: v.c = self self.sV.update(G.sV) self.sE.update(G.sE) # derivated graphs: # ----------------- # returns subgraph spanned by vertices V def spans(self,V): raise NotImplementedError # returns join of G (if disjoint) def __mul__(self,G): raise NotImplementedError # returns complement of a graph G def complement(self,G): raise NotImplementedError # contraction G\e def contract(self,e): raise NotImplementedError