def MinimumSpanningTree(G):
    """
    Return the minimum spanning tree of an undirected graph G.
    G should be represented in such a way that iter(G) lists its
    vertices, iter(G[u]) lists the neighbors of u, G[u][v] gives the
    length of edge u,v, and G[u][v] should always equal G[v][u].
    The tree is returned as a list of edges.
    """
    if not isUndirected(G):
        raise ValueError("MinimumSpanningTree: input is not undirected")
    for u in G:
        for v in G[u]:
            if G[u][v] != G[v][u]:
                raise ValueError("MinimumSpanningTree: asymmetric weights")

    # Kruskal's algorithm: sort edges by weight, and add them one at a time.
    # We use Kruskal's algorithm, first because it is very simple to
    # implement once UnionFind exists, and second, because the only slow
    # part (the sort) is sped up by being built in to Python.
    subtrees = UnionFind()
    tree = []
    for W, u, v in sorted((G[u][v], u, v) for u in G for v in G[u]):
        if subtrees[u] != subtrees[v]:
            tree.append((u, v))
            subtrees.union(u, v)
    return tree
Example #2
0
 def __init__(self, G):
     """Search for biconnected components of graph G."""
     if not isUndirected(G):
         raise NotBiconnected
     self._dfsnumber = {}
     self._low = {}
     self._rootedge = None
     DFS.Searcher.__init__(self, G)
 def __init__(self,G):
     """Search for biconnected components of graph G."""
     if not isUndirected(G):
         raise NotBiconnected
     self._dfsnumber = {}
     self._low = {}
     self._rootedge = None
     DFS.Searcher.__init__(self,G)
Example #4
0
def Chordal(G):
    """Test if a given graph is chordal."""
    if not isUndirected(G):
        raise ValueError("Input to Chordal is not an undirected graph")
    try:
        PerfectEliminationOrdering(G)
    except:
        return False
    return True
Example #5
0
def Chordal(G):
    """Test if a given graph is chordal."""
    if not isUndirected(G):
        raise ValueError("Input to Chordal is not an undirected graph")
    try:
        PerfectEliminationOrdering(G)
    except:
        return False
    return True
Example #6
0
def MinimumSpanningTree(G):
    if not isUndirected(G):
        raise ValueError("MinimumSpanningTree: input is not undirected")
    for u in G:
        for v in G[u]:
            if G[u][v] != G[v][u]:
                raise ValueError("MinimumSpanningTree: asymmetric weights")
    subtrees = UnionFind()
    tree = []
    for W, u, v in sorted((G[u][v], u, v) for u in G for v in G[u]):
        if subtrees[u] != subtrees[v]:
            tree.append((u, v))
            subtrees.union(u, v)
    return tree
Example #7
0
def CubicMatchPartitions(G):
    """Partition a biconnected cubic graph G into three matchings.
    Each matching is represented as a graph, in which G[v] is a list
    of the three edges of G in the order of the three matchings.
    This function generates a sequence of such representations.
    """
    
    if not isUndirected(G):
        raise ValueError("CubicMatchPartitions: graph is not undirected")
    for v in G:
        if len(G[v]) != 3:
            raise ValueError("CubicMatchPartitions: graph is not cubic")
    ST = stOrientation(G)
    L = TopologicalOrder(ST)
    for B in xrange(1<<(len(L)//2 - 1)):
        # Here with a bitstring representing the sequence of choices
        out = {}
        pos = 0
        for v in L:
            source = [w for w in G[v] if w in out]
            sourcepos = {}
            adjlist = [None,None,None]
            for w in source:
                sourcepos[w] = [i for i in (0,1,2) if out[w][i]==v][0]
                adjlist[sourcepos[w]] = w
            usedpos = [sourcepos[w] for w in source]
            if len(set(usedpos)) != len(usedpos):
                # two edges in with same index, doesn't form matching
                break 
            elif len(source) == 0:
                # start vertex, choose one orientation
                adjlist = list(ST[v])
            elif len(source) == 1:
                # two outgoing vertices, one incoming
                avail = [i for i in (0,1,2) if i != usedpos[0]]
                if B & (1<<pos):
                    avail.reverse()
                pos += 1
                for i,w in zip(avail,list(ST[v])):
                    adjlist[i] = w
            elif len(source) == 2:
                avail = 3 - sum(usedpos)
                adjlist[avail] = list(ST[v])[0]
            out[v] = adjlist
            if len(source) == 3:
                # final vertex of topological ordering, still all consistent
                yield out
Example #8
0
def CubicMatchPartitions(G):
    """Partition a biconnected cubic graph G into three matchings.
    Each matching is represented as a graph, in which G[v] is a list
    of the three edges of G in the order of the three matchings.
    This function generates a sequence of such representations.
    """

    if not isUndirected(G):
        raise ValueError("CubicMatchPartitions: graph is not undirected")
    for v in G:
        if len(G[v]) != 3:
            raise ValueError("CubicMatchPartitions: graph is not cubic")
    ST = stOrientation(G)
    L = TopologicalOrder(ST)
    for B in xrange(1 << (len(L) // 2 - 1)):
        # Here with a bitstring representing the sequence of choices
        out = {}
        pos = 0
        for v in L:
            source = [w for w in G[v] if w in out]
            sourcepos = {}
            adjlist = [None, None, None]
            for w in source:
                sourcepos[w] = [i for i in (0, 1, 2) if out[w][i] == v][0]
                adjlist[sourcepos[w]] = w
            usedpos = [sourcepos[w] for w in source]
            if len(set(usedpos)) != len(usedpos):
                # two edges in with same index, doesn't form matching
                break
            elif len(source) == 0:
                # start vertex, choose one orientation
                adjlist = list(ST[v])
            elif len(source) == 1:
                # two outgoing vertices, one incoming
                avail = [i for i in (0, 1, 2) if i != usedpos[0]]
                if B & (1 << pos):
                    avail.reverse()
                pos += 1
                for i, w in zip(avail, list(ST[v])):
                    adjlist[i] = w
            elif len(source) == 2:
                avail = 3 - sum(usedpos)
                adjlist[avail] = list(ST[v])[0]
            out[v] = adjlist
            if len(source) == 3:
                # final vertex of topological ordering, still all consistent
                yield out
    def __init__(self,G):
        """Search for biconnected components of graph G."""
        if not isUndirected(G):
            raise ValueError("BiconnectedComponents: input not undirected graph")

        # set up data structures for DFS
        self._components = []
        self._dfsnumber = {}
        self._activelen = {}
        self._active = []
        self._low = {}
        self._ancestors = {} # directed subgraph from nodes to DFS ancestors

        # perform the Depth First Search
        DFS.Searcher.__init__(self,G)

        # clean up now-useless data structures
        del self._dfsnumber, self._activelen, self._active
        del self._low, self._ancestors
Example #10
0
 def __init__(self,G):
     if not isUndirected(G):
         raise MediumError("not an undirected graph")
     self._action = {v:{} for v in G}
     self._reverse = {}
     for v in G:
         for w in G[v]:
             t = G[v][w]
             if t in self._action[v]:
                 raise MediumError("multiple edges for state %s and token %s" % (v,t))
             self._action[v][t] = w
             if t not in self._reverse:
                 rt = G[w][v]
                 if rt in self._reverse:
                     raise MediumError("mismatched token reversals")
                 self._reverse[t] = rt
                 self._reverse[rt] = t
             elif G[w][v] != self._reverse[t]:
                 raise MediumError("mismatched token reversals")
Example #11
0
    def __init__(self, G):
        """Search for biconnected components of graph G."""
        if not isUndirected(G):
            raise ValueError(
                "BiconnectedComponents: input not undirected graph")

        # set up data structures for DFS
        self._components = []
        self._dfsnumber = {}
        self._activelen = {}
        self._active = []
        self._low = {}
        self._ancestors = {}  # directed subgraph from nodes to DFS ancestors

        # perform the Depth First Search
        DFS.Searcher.__init__(self, G)

        # clean up now-useless data structures
        del self._dfsnumber, self._activelen, self._active
        del self._low, self._ancestors
    def __init__(self,G):
        """Relate edges for st-orientation."""
        if not isUndirected(G):
            raise ValueError("stOrienter: input not undirected graph")

        # set up data structures for DFS
        self._dfsnumber = {}
        self._low = {}
        self._down = {} # down[v] = child we're currently exploring from v
        self._lowv = {} # lowv[n] = vertex with low number n

        # The main data structure!
        # a dictionary mapping edges to lists of edges
        # each of which should be oriented the same as the key.
        self.orient = {}
        self.roots = [] # edges with no predecessor

        # perform the Depth First Search
        DFS.Searcher.__init__(self,G)

        # clean up now-useless data structures
        del self._dfsnumber, self._low, self._down, self._lowv
Example #13
0
    def __init__(self, G):
        """Relate edges for st-orientation."""
        if not isUndirected(G):
            raise ValueError("stOrienter: input not undirected graph")

        # set up data structures for DFS
        self._dfsnumber = {}
        self._low = {}
        self._down = {}  # down[v] = child we're currently exploring from v
        self._lowv = {}  # lowv[n] = vertex with low number n

        # The main data structure!
        # a dictionary mapping edges to lists of edges
        # each of which should be oriented the same as the key.
        self.orient = {}
        self.roots = []  # edges with no predecessor

        # perform the Depth First Search
        DFS.Searcher.__init__(self, G)

        # clean up now-useless data structures
        del self._dfsnumber, self._low, self._down, self._lowv
Example #14
0
File: Halin.py Project: max6cn/PADS
def D3reducible(G,triangleHooks=[],pathHooks=[],finalize=isK4):
    """Test if the graph G is D3-reducible.

    Whenever a reduction is found, the hook functions are
    called in turn on it, and should return True if the reduction
    should be allowed to continue or False otherwise.

    The arguments to a triangle hook are the three triangle
    vertices, their three neighbors, and the id that will be
    given to the new vertex formed by the collapsed triangle.
    The arguments to a path hook are the three path vertices
    and the apex.
    
    The finalize hook takes as input the irreducible graph
    after all reductions are complete, and produces as output
    the return value for the overall computation.
    By default the return value is a Boolean that is true
    whenever the irreducible graph is K4."""
    if not isUndirected(G):
        raise TypeError("Argument to D3reducible must be an undirected graph")
    G = copyGraph(G)        # We are going to change G, so make a copy of it
    C = {v for v in G if len(G[v]) == 3}  # Active vertices, init. all w/deg=3

    def otherNeighbor(u,v,w):
        """Other neighbor of v given known neighbors u,w"""
        return [x for x in G[v] if x != u and x != w][0]

    def triangle(u,v,w):
        """Try a D3a reduction."""
        # u, v, and w are known to form a triangle but we still need to
        # check whether they have distinct neighbors."""
        Nu = otherNeighbor(v,u,w)
        Nv = otherNeighbor(u,v,w)
        Nw = otherNeighbor(u,w,v)
        if Nu == Nv or Nv == Nw or Nu == Nw:
            return      # Need to have three distinct neighbors
        x = object()    # Cons up an id for the triangle

        # Run the hooks
        for hook in triangleHooks:
            if not hook(u,v,w,Nu,Nv,Nw,x):
                return False

        # Make the change!
        del G[u],G[v],G[w]
        G[Nu].remove(u)
        G[Nv].remove(v)
        G[Nw].remove(w)
        G[x] = {Nu,Nv,Nw}
        G[Nu].add(x)
        G[Nv].add(x)
        G[Nw].add(x)

        # Update the active vertices
        for z in (x,Nu,Nv,Nw):
            if len(G[z]) == 3:
                C.add(z)
    
    def path(u,v,w):
        """Try a D3b reduction."""
        # u, v, and w are known to induce a path but we still need to
        # check whether they have a common neighbor as apex."""
        apexes = G[u] & G[v] & G[w]
        if len(apexes) != 1:
            return
        apex = apexes.pop()

        # Run the hooks
        for hook in pathHooks:
            if not hook(u,v,w,apex):
                return False
        
        # Make the change!
        del G[v]
        G[u].remove(v)
        G[w].remove(v)
        G[apex].remove(v)
        G[u].add(w)
        G[w].add(u)
        
        # Update the active vertices
        if len(G[apex]) == 3:
            C.add(apex)
        pass

    def reduce(u,v,w):
        """Try a reduction in which v is the middle vertex"""
        for x in (u,v,w):
            if x not in G or len(G[x]) != 3:
                return      # No longer in G or not all degree 3, no redux
        if w in G[u]:
            triangle(u,v,w)
        else:
            path(u,v,w)
    
    while C:
        v = C.pop()
        if v in G and len(G[v]) == 3:
            p,q,r = tuple(G[v])     # Decode set of neighbors
            reduce(p,v,q)
            reduce(p,v,r)
            reduce(q,v,r)

    # Now check whether we found K4
    return finalize(G)
Example #15
0
def PartialCubeEdgeLabeling(G):
    """
    Label edges of G by their equivalence classes in a partial cube structure.

    We follow the algorithm of arxiv:0705.1025, in which a number of
    equivalence classes equal to the maximum degree of G can be found
    simultaneously by a single breadth first search, using bitvectors.
    However, in order to avoid deep recursions (problematic in Python)
    we use a union-find data structure to keep track of edge identifications
    discovered so far. That is, we repeatedly contract our initial graph,
    maintaining as we do the property that G[v][w] points to a union-find
    set representing edges in the original graph that have been contracted
    to the single edge v-w.
    """

    # Some simple sanity checks
    if not isUndirected(G):
        raise Medium.MediumError("graph is not undirected")
    L = list(StronglyConnectedComponents(G))
    if len(L) != 1:
        raise Medium.MediumError("graph is not connected")

    # Set up data structures for algorithm:
    # - UF: union find data structure representing known edge equivalences
    # - CG: contracted graph at current stage of algorithm
    # - LL: limit on number of remaining available labels
    UF = UnionFind()
    CG = dict([(v,dict([(w,(v,w)) for w in G[v]])) for v in G])
    NL = len(CG)-1

    # Initial sanity check: are there few enough edges?
    # Needed so that we don't try to use union-find on a dense
    # graph and incur superquadratic runtimes.
    n = len(CG)
    m = sum([len(CG[v]) for v in CG])
    if 1<<(m//n) > n:
        raise Medium.MediumError("graph has too many edges")

    # Main contraction loop in place of the original algorithm's recursion
    while len(CG) > 1:
        if not isBipartite(CG):
            raise Medium.MediumError("graph is not bipartite")

        # Find max degree vertex in G, and update label limit
        deg,root = max([(len(CG[v]),v) for v in CG])
        if deg > NL:
            raise Medium.MediumError("graph has too many equivalence classes")
        NL -= deg

        # Set up bitvectors on vertices
        bitvec = dict([(v,0) for v in CG])
        neighbors = {}
        i = 0
        for neighbor in CG[root]:
            bitvec[neighbor] = 1<<i
            neighbors[1<<i] = neighbor
            i += 1

        # Breadth first search to propagate bitvectors to the rest of the graph
        for LG in BFS.BreadthFirstLevels(CG,root):
            for v in LG:
                for w in LG[v]:
                    bitvec[w] |= bitvec[v]

        # Make graph of labeled edges and union them together
        labeled = dict([(v,set()) for v in CG])
        for v in CG:
            for w in CG[v]:
                diff = bitvec[v]^bitvec[w]
                if not diff or bitvec[w] &~ bitvec[v] == 0:
                    continue    # zero edge or wrong direction
                if diff not in neighbors:
                    raise Medium.MediumError("multiply-labeled edge")
                neighbor = neighbors[diff]
                UF.union(CG[v][w],CG[root][neighbor])
                UF.union(CG[w][v],CG[neighbor][root])
                labeled[v].add(w)
                labeled[w].add(v)

        # Map vertices to components of labeled-edge graph
        component = {}
        compnum = 0
        for SCC in StronglyConnectedComponents(labeled):
            for v in SCC:
                component[v] = compnum
            compnum += 1

        # generate new compressed subgraph
        NG = dict([(i,{}) for i in range(compnum)])
        for v in CG:
            for w in CG[v]:
                if bitvec[v] == bitvec[w]:
                    vi = component[v]
                    wi = component[w]
                    if vi == wi:
                        raise Medium.MediumError("self-loop in contracted graph")
                    if wi in NG[vi]:
                        UF.union(NG[vi][wi],CG[v][w])
                    else:
                        NG[vi][wi] = CG[v][w]

        CG = NG

    # Here with all edge equivalence classes represented by UF.
    # Turn them into a labeled graph and return it.
    return dict([(v,dict([(w,UF[v,w]) for w in G[v]])) for v in G])
Example #16
0
def PartialCubeEdgeLabeling(G):
    """
    Label edges of G by their equivalence classes in a partial cube structure.

    We follow the algorithm of arxiv:0705.1025, in which a number of
    equivalence classes equal to the maximum degree of G can be found
    simultaneously by a single breadth first search, using bitvectors.
    However, in order to avoid deep recursions (problematic in Python)
    we use a union-find data structure to keep track of edge identifications
    discovered so far. That is, we repeatedly contract our initial graph,
    maintaining as we do the property that G[v][w] points to a union-find
    set representing edges in the original graph that have been contracted
    to the single edge v-w.
    """

    # Some simple sanity checks
    if not isUndirected(G):
        raise Medium.MediumError("graph is not undirected")
    L = list(StronglyConnectedComponents(G))
    if len(L) != 1:
        raise Medium.MediumError("graph is not connected")

    # Set up data structures for algorithm:
    # - UF: union find data structure representing known edge equivalences
    # - CG: contracted graph at current stage of algorithm
    # - LL: limit on number of remaining available labels
    UF = UnionFind()
    CG = dict([(v, dict([(w, (v, w)) for w in G[v]])) for v in G])
    NL = len(CG) - 1

    # Initial sanity check: are there few enough edges?
    # Needed so that we don't try to use union-find on a dense
    # graph and incur superquadratic runtimes.
    n = len(CG)
    m = sum([len(CG[v]) for v in CG])
    if 1 << (m // n) > n:
        raise Medium.MediumError("graph has too many edges")

    # Main contraction loop in place of the original algorithm's recursion
    while len(CG) > 1:
        if not isBipartite(CG):
            raise Medium.MediumError("graph is not bipartite")

        # Find max degree vertex in G, and update label limit
        deg, root = max([(len(CG[v]), v) for v in CG])
        if deg > NL:
            raise Medium.MediumError("graph has too many equivalence classes")
        NL -= deg

        # Set up bitvectors on vertices
        bitvec = dict([(v, 0) for v in CG])
        neighbors = {}
        i = 0
        for neighbor in CG[root]:
            bitvec[neighbor] = 1 << i
            neighbors[1 << i] = neighbor
            i += 1

        # Breadth first search to propagate bitvectors to the rest of the graph
        for LG in BFS.BreadthFirstLevels(CG, root):
            for v in LG:
                for w in LG[v]:
                    bitvec[w] |= bitvec[v]

        # Make graph of labeled edges and union them together
        labeled = dict([(v, set()) for v in CG])
        for v in CG:
            for w in CG[v]:
                diff = bitvec[v] ^ bitvec[w]
                if not diff or bitvec[w] & ~bitvec[v] == 0:
                    continue  # zero edge or wrong direction
                if diff not in neighbors:
                    raise Medium.MediumError("multiply-labeled edge")
                neighbor = neighbors[diff]
                UF.union(CG[v][w], CG[root][neighbor])
                UF.union(CG[w][v], CG[neighbor][root])
                labeled[v].add(w)
                labeled[w].add(v)

        # Map vertices to components of labeled-edge graph
        component = {}
        compnum = 0
        for SCC in StronglyConnectedComponents(labeled):
            for v in SCC:
                component[v] = compnum
            compnum += 1

        # generate new compressed subgraph
        NG = dict([(i, {}) for i in range(compnum)])
        for v in CG:
            for w in CG[v]:
                if bitvec[v] == bitvec[w]:
                    vi = component[v]
                    wi = component[w]
                    if vi == wi:
                        raise Medium.MediumError(
                            "self-loop in contracted graph")
                    if wi in NG[vi]:
                        UF.union(NG[vi][wi], CG[v][w])
                    else:
                        NG[vi][wi] = CG[v][w]

        CG = NG

    # Here with all edge equivalence classes represented by UF.
    # Turn them into a labeled graph and return it.
    return dict([(v, dict([(w, UF[v, w]) for w in G[v]])) for v in G])
Example #17
0
def D3reducible(G, triangleHooks=[], pathHooks=[], finalize=isK4):
    """Test if the graph G is D3-reducible.

    Whenever a reduction is found, the hook functions are
    called in turn on it, and should return True if the reduction
    should be allowed to continue or False otherwise.

    The arguments to a triangle hook are the three triangle
    vertices, their three neighbors, and the id that will be
    given to the new vertex formed by the collapsed triangle.
    The arguments to a path hook are the three path vertices
    and the apex.
    
    The finalize hook takes as input the irreducible graph
    after all reductions are complete, and produces as output
    the return value for the overall computation.
    By default the return value is a Boolean that is true
    whenever the irreducible graph is K4."""
    if not isUndirected(G):
        raise TypeError("Argument to D3reducible must be an undirected graph")
    G = copyGraph(G)  # We are going to change G, so make a copy of it
    C = {v for v in G if len(G[v]) == 3}  # Active vertices, init. all w/deg=3

    def otherNeighbor(u, v, w):
        """Other neighbor of v given known neighbors u,w"""
        return [x for x in G[v] if x != u and x != w][0]

    def triangle(u, v, w):
        """Try a D3a reduction."""
        # u, v, and w are known to form a triangle but we still need to
        # check whether they have distinct neighbors."""
        Nu = otherNeighbor(v, u, w)
        Nv = otherNeighbor(u, v, w)
        Nw = otherNeighbor(u, w, v)
        if Nu == Nv or Nv == Nw or Nu == Nw:
            return  # Need to have three distinct neighbors
        x = object()  # Cons up an id for the triangle

        # Run the hooks
        for hook in triangleHooks:
            if not hook(u, v, w, Nu, Nv, Nw, x):
                return False

        # Make the change!
        del G[u], G[v], G[w]
        G[Nu].remove(u)
        G[Nv].remove(v)
        G[Nw].remove(w)
        G[x] = {Nu, Nv, Nw}
        G[Nu].add(x)
        G[Nv].add(x)
        G[Nw].add(x)

        # Update the active vertices
        for z in (x, Nu, Nv, Nw):
            if len(G[z]) == 3:
                C.add(z)

    def path(u, v, w):
        """Try a D3b reduction."""
        # u, v, and w are known to induce a path but we still need to
        # check whether they have a common neighbor as apex."""
        apexes = G[u] & G[v] & G[w]
        if len(apexes) != 1:
            return
        apex = apexes.pop()

        # Run the hooks
        for hook in pathHooks:
            if not hook(u, v, w, apex):
                return False

        # Make the change!
        del G[v]
        G[u].remove(v)
        G[w].remove(v)
        G[apex].remove(v)
        G[u].add(w)
        G[w].add(u)

        # Update the active vertices
        if len(G[apex]) == 3:
            C.add(apex)
        pass

    def reduce(u, v, w):
        """Try a reduction in which v is the middle vertex"""
        for x in (u, v, w):
            if x not in G or len(G[x]) != 3:
                return  # No longer in G or not all degree 3, no redux
        if w in G[u]:
            triangle(u, v, w)
        else:
            path(u, v, w)

    while C:
        v = C.pop()
        if v in G and len(G[v]) == 3:
            p, q, r = tuple(G[v])  # Decode set of neighbors
            reduce(p, v, q)
            reduce(p, v, r)
            reduce(q, v, r)

    # Now check whether we found K4
    return finalize(G)