| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879 | # -*- coding: utf-8 -*-""".. _graphs:graphs.py=========This module implements essential graph classes for representingvertices (nodes), edges (links), and graphs."""# This code is part of Grandalf# Copyright (C) 2008-2011 Axel Tillequin (bdcht3@gmail.com)# published under GPLv2 license or EPLv1 licensefrom mantis.grandalf.utils import Poset# ------------------------------------------------------------------------------class vertex_core(object):    """ The Vertex essentials attributes and methods.        Attributes:            e (list[Edge]): list of edges associated with this vertex.        Methods:            deg() : degree of the vertex (number of edges).            e_in() : list of edges directed toward this vertex.            e_out(): list of edges directed outward this vertex.            e_dir(int): either e_in, e_out or all edges depending on               provided direction parameter (>0 means outward).            N(f_io=0): list of neighbor vertices in all directions (default)               or in filtered f_io direction (>0 means outward).            e_to(v): returns the Edge from this vertex directed toward vertex v.            e_from(v): returns the Edge from vertex v directed toward this vertex.            e_with(v): return the Edge with both this vertex and vertex v            detach(): removes this vertex from all its edges and returns this list               of edges.    """    def __init__(self):        # will hold list of edges for this vertex (adjacency list)        self.e = []    def deg(self):        return len(self.e)    def e_in(self):        return list(filter((lambda e: e.v[1] == self), self.e))    def e_out(self):        return list(filter((lambda e: e.v[0] == self), self.e))    def e_dir(self, dir):        if dir > 0:            return self.e_out()        if dir < 0:            return self.e_in()        return self.e    def N(self, f_io=0):        N = []        if f_io <= 0:            N += [e.v[0] for e in self.e_in()]        if f_io >= 0:            N += [e.v[1] for e in self.e_out()]        return N    def e_to(self, y):        for e in self.e_out():            if e.v[1] == y:                return e        return None    def e_from(self, x):        for e in self.e_in():            if e.v[0] == x:                return e        return None    def e_with(self, v):        for e in self.e:            if v in e.v:                return e        return None    def detach(self):        E = self.e[:]        for e in E:            e.detach()        assert self.deg() == 0        return E# ------------------------------------------------------------------------------class edge_core(object):    """The Edge essentials attributes.       Attributes:          v (list[Vertex]): list of vertices associated with this edge.          deg (int): degree of the edge (number of unique vertices).    """    def __init__(self, x, y):        self.deg = 0 if x == y else 1        self.v = (x, y)# ------------------------------------------------------------------------------class Vertex(vertex_core):    """Vertex class enhancing a vertex_core with graph-related features.              Attributes:          c (graph_core): the component of connected vertices that contains this vertex.             By default a vertex belongs no component but when it is added in a             graph, c points to the connected component in this graph.          data (object) : an object associated with the vertex.    """    def __init__(self, data=None):        super().__init__()        # by default, a new vertex belongs to its own component        # but when the vertex is added to a graph, c points to the        # connected component where it belongs.        self.c = None        self.data = data        self.__index = None    @property    def index(self):        if self.__index:            return self.__index        elif isinstance(self.c, graph_core):            self.__index = self.c.sV.index(self)            return self.__index        else:            return None    def __lt__(self, v):        return 0    def __gt__(self, v):        return 0    def __le__(self, v):        return 0    def __ge__(self, v):        return 0    def __getstate__(self):        return (self.index, self.data)    def __setstate__(self, state):        self.__index, self.data = state        self.c = None        self.e = []# ------------------------------------------------------------------------------class Edge(edge_core):    """Edge class enhancing edge_core with attributes and methods related to the graph.       Attributes:         w (int): a weight associated with the edge (default 1) used by Dijkstra to           find min-flow paths.         data (object): an object associated with the edge.         feedback (bool): indicates if the edge has been marked as a *feeback* edge           by the Tarjan algorithm which means that it is part of a cycle and that           inverting this edge would remove this cycle.       Methods:         attach(): add this edge in its vertices edge lists.         detach(): remove this edge from its vertices edge lists.    """    def __init__(self, x, y, w=1, data=None, connect=False):        super().__init__(x, y)        # w is an optional weight associated with the edge.        self.w = w        self.data = data        self.feedback = False        if connect and (x.c is None or y.c is None):            c = x.c or y.c            c.add_edge(self)    def attach(self):        if not self in self.v[0].e:            self.v[0].e.append(self)        if not self in self.v[1].e:            self.v[1].e.append(self)    def detach(self):        if self.deg == 1:            assert self in self.v[0].e            assert self in self.v[1].e            self.v[0].e.remove(self)            self.v[1].e.remove(self)        else:            if self in self.v[0].e:                self.v[0].e.remove(self)            assert self not in self.v[0].e        return [self]    def __lt__(self, v):        return 0    def __gt__(self, v):        return 0    def __le__(self, v):        return 0    def __ge__(self, v):        return 0    def __getstate__(self):        xi, yi = (self.v[0].index, self.v[1].index)        return (xi, yi, self.w, self.data, self.feedback)    def __setstate__(self, state):        xi, yi, self.w, self.data, self.feedback = state        self._v = [xi, yi]        self.deg = 0 if xi == yi else 1# ------------------------------------------------------------------------------class graph_core(object):    """A connected graph of Vertex/Edge objects. A graph_core is a *component*       of a Graph that contains a connected set of Vertex and Edges.       Attributes:         sV (poset[Vertex]): the partially ordered set of vertices of the graph.         sE (poset[Edge]): the partially ordered set of edges of the graph.         degenerated_edges (set[Edge]): the set of *degenerated* edges (of degree 0).         directed (bool): indicates if the graph is considered *oriented* or not.       Methods:         V(cond=None): generates an iterator over vertices, with optional filter         E(cond=None): generates an iterator over edges, with optional filter         M(cond=None): returns the associativity matrix of the graph component         order(): the order of the graph (number of vertices)         norm(): the norm of the graph (number of edges)         deg_min(): the minimum degree of vertices         deg_max(): the maximum degree of vertices         deg_avg(): the average degree of vertices         eps(): the graph epsilon value (norm/order), average number of edges per vertex.          path(x,y,f_io=0,hook=None): shortest path between vertices x and y by breadth-first descent,           contrained by f_io direction if provided. The path is returned as a list of Vertex objects.           If a *hook* function is provided, it is called at every vertex added to the path, passing           the vertex object as argument.         roots(): returns the list of *roots* (vertices with no inward edges).         leaves(): returns the list of *leaves* (vertices with no outward edges).         add_single_vertex(v): allow a graph_core to hold a single vertex.         add_edge(e): add edge e. At least one of its vertex must belong to the graph,           the other being added automatically.         remove_edge(e): remove Edge e, asserting that the resulting graph is still connex.         remove_vertex(x): remove Vertex x and all associated edges.         dijkstra(x,f_io=0,hook=None): shortest weighted-edges paths between x and all other vertices           by dijkstra's algorithm with heap used as priority queue.         get_scs_with_feedback(): 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).         partition(): returns a *partition* of the connected graph as a list of lists.         N(v): returns neighbours of a vertex v.    """    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 = set()        if len(self.sV) == 1:            v = self.sV[0]            v.c = self            for e in v.e:                e.detach()            return        for e in E:            x = self.sV.get(e.v[0])            y = self.sV.get(e.v[1])            if x is None or y is None:                raise ValueError("unknown Vertex (%s or %s)" % e.v)            e.v = (x, y)            if e.deg == 0:                self.degenerated_edges.add(e)            e = self.sE.add(e)            e.attach()            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, y = (x, y) if len(x.c) > len(y.c) else (y, x)                x.c.update(y.c)                for v in y.c:                    v.c = x.c            s = x.c        # 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    def roots(self):        return list(filter(lambda v: len(v.e_in()) == 0, self.sV))    def leaves(self):        return list(filter(lambda v: len(v.e_out()) == 0, self.sV))    def add_single_vertex(self, v):        if len(self.sE) == 0 and len(self.sV) == 0:            v = self.sV.add(v)            v.c = self            return v        return None    def add_edge(self, e):        if e in self.sE:            return self.sE.get(e)        x = e.v[0]        y = e.v[1]        if not ((x in self.sV) or (y in self.sV)):            raise ValueError("unconnected edge")        x = self.sV.add(x)        y = self.sV.add(y)        e.v = (x, y)        e.attach()        e = self.sE.add(e)        x.c = self        y.c = self        if e.deg == 0:            self.degenerated_edges.add(e)        return e    def remove_edge(self, e):        if not e in self.sE:            return        e.detach()        # check if still connected (path is not oriented here):        if e.deg == 1 and 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:            e = self.sE.remove(e)            if e in self.degenerated_edges:                self.degenerated_edges.remove(e)            return e    def remove_vertex(self, x):        if x not in self.sV:            return        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)        x = self.sV.remove(x)        x.c = None        return x    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    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    def M(self, cond=None):        from array import array        mat = []        for v in self.V(cond):            vec = array("b", [0] * self.order())            mat.append(vec)            for e in v.e_in():                v0 = e.v[0]                if v0.index == v.index:                    continue                vec[v0.index] = -e.w            for e in v.e_out():                v1 = e.v[1]                vec[v1.index] = e.w        return mat    def order(self):        return len(self.sV)    def norm(self):        return len(self.sE)    def deg_min(self):        return min([v.deg() for v in self.sV])    def deg_max(self):        return max([v.deg() for v in self.sV])    def deg_avg(self):        return sum([v.deg() for v in self.sV]) / float(self.order())    def eps(self):        return float(self.norm()) / self.order()    def path(self, x, y, f_io=0, hook=None):        assert x in self.sV        assert y in self.sV        x = self.sV.get(x)        y = self.sV.get(y)        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 n in v:                    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    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        # initiate with path to itself...        v = self.sV.get(x)        # D is the returned vector of distances:        D = defaultdict(lambda: None)        D[v] = 0.0        L = [(D[v], v)]        while len(L) > 0:            l, u = heappop(L)            for e in u.e_dir(f_io):                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    def get_scs_with_feedback(self, roots=None):        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        if roots is None:            roots = self.roots()        self.tstack = []        scs = []        Vertex.ncur = 1        for v in self.sV:            v.ind = 0        # start exploring tree from roots:        for v in roots:            v = self.sV.get(v)            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    def partition(self):        V = self.sV.copy()        R = self.roots()        for r in R:            V.remove(r)        parts = []        while len(R) > 0:            v = R.pop(0)            p = Poset([v])            l = v.N(+1)            while len(l) > 0:                x = l.pop(0)                if x in p:                    continue                if all([(y in p) for y in x.N(-1)]):                    p.add(x)                    if x in R:                        R.remove(x)                    else:                        V.remove(x)                    l.extend(x.N(+1))                else:                    if x in V:                        V.remove(x)                        R.append(x)            parts.append(list(p))        return parts    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    def __getstate__(self):        V = [v for v in self.sV]        E = [e for e in self.sE]        return (V, E, self.directed)    def __setstate__(self, state):        V, E, directed = state        for e in E:            e.v = [V[x] for x in e._v]            del e._v        graph_core.__init__(self, V, E, directed)# ------------------------------------------------------------------------------class Graph(object):    """Disjoint-set Graph.       The graph is stored in disjoint-sets holding each connex component       in self.C as a list of graph_core objects.       Attributes:          C (list[graph_core]): list of graph_core components.       Methods:          add_vertex(v): add vertex v into the Graph as a new component          add_edge(e): add edge e and its vertices into the Graph possibly merging the            associated graph_core components          get_vertices_count(): see order()          V(): see graph_core          E(): see graph_core          remove_edge(e): remove edge e possibly spawning two new cores            if the graph_core that contained e gets disconnected.          remove_vertex(v): remove vertex v and all its edges.          order(): the order of the graph (number of vertices)          norm(): the norm of the graph (number of edges)          deg_min(): the minimum degree of vertices          deg_max(): the maximum degree of vertices          deg_avg(): the average degree of vertices          eps(): the graph epsilon value (norm/order), average number of edges per vertex.           connected(): returns True if the graph is connected (i.e. it has only one component).          components(): returns self.C    """    component_class = graph_core    def __init__(self, V=None, E=None, directed=True):        if V is None:            V = []        if E is None:            E = []        self.directed = directed        # tag connex set of vertices:        # at first, every vertex is its own component        for v in V:            v.c = Poset([v])        CV = [v.c for v in V]        # then pass through edges and union associated vertices such that        # CV finally holds only connected sets:        for e in E:            x = e.v[0]            y = e.v[1]            assert x in V            assert y in V            assert x.c in CV            assert y.c in CV            e.attach()            if x.c != y.c:                # merge y.c into x.c :                x.c.update(y.c)                # update set list (MUST BE DONE BEFORE UPDATING REFS!)                CV.remove(y.c)                # update reference:                for z in y.c:                    z.c = x.c        # now create edge sets from connected vertex sets and        # make the graph_core connected graphs for this component :        self.C = []        for c in CV:            s = set()            for v in c:                s.update(v.e)            self.C.append(self.component_class(c, s, directed))    def add_vertex(self, v):        for c in self.C:            if v in c.sV:                return c.sV.get(v)        g = self.component_class(directed=self.directed)        v = g.add_single_vertex(v)        self.C.append(g)        return v    def add_edge(self, e):        # take vertices:        x = e.v[0]        y = e.v[1]        x = self.add_vertex(x)        y = self.add_vertex(y)        # take respective graph_cores:        cx = x.c        cy = y.c        # add edge:        e = cy.add_edge(e)        # connect (union) the graphs:        if cx != cy:            cx.union_update(cy)            self.C.remove(cy)        return e    def get_vertices_count(self):        return sum([c.order() for c in self.C])    def V(self):        for c in self.C:            V = c.sV            for v in V:                yield v    def E(self):        for c in self.C:            E = c.sE            for e in E:                yield e    def remove_edge(self, e):        # get the graph_core:        c = e.v[0].c        assert c == e.v[1].c        if not c in self.C:            return None        # remove edge in graph_core and replace it with two new cores        # if removing edge disconnects the graph_core:        try:            e = c.remove_edge(e)        except ValueError:            e = c.sE.remove(e)            e.detach()            self.C.remove(c)            tmpg = type(self)(c.sV, c.sE, self.directed)            assert len(tmpg.C) == 2            self.C.extend(tmpg.C)        return e    def remove_vertex(self, x):        # get the graph_core:        c = x.c        if not c in self.C:            return None        try:            x = c.remove_vertex(x)            if c.order() == 0:                self.C.remove(c)        except ValueError:            for e in x.detach():                c.sE.remove(e)            x = c.sV.remove(x)            self.C.remove(c)            tmpg = type(self)(c.sV, c.sE, self.directed)            assert len(tmpg.C) == 2            self.C.extend(tmpg.C)        return x    def order(self):        return sum([c.order() for c in self.C])    def norm(self):        return sum([c.norm() for c in self.C])    def deg_min(self):        return min([c.deg_min() for c in self.C])    def deg_max(self):        return max([c.deg_max() for c in self.C])    def deg_avg(self):        t = 0.0        for c in self.C:            t += sum([v.deg() for v in c.sV])        return t / float(self.order())    def eps(self):        return float(self.norm()) / self.order()    def path(self, x, y, f_io=0, hook=None):        if x == y:            return []        if x.c != y.c:            return None        # path:        return x.c.path(x, y, f_io, hook)    def N(self, v, f_io=0):        return v.N(f_io)    def __contains__(self, G):        r = False        for c in self.C:            r |= G in c        return r    def connected(self):        return len(self.C) == 1    # returns connectivity (kappa)    def connectivity(self):        raise NotImplementedError    # returns edge-connectivity (lambda)    def e_connectivity(self):        raise NotImplementedError    # returns the list of graphs components    def components(self):        return self.C    # 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
 |