def spanning_forest(M): r""" Return a list of edges of a spanning forest of the bipartite graph defined by `M` INPUT: - ``M`` -- a matrix defining a bipartite graph G. The vertices are the rows and columns, if `M[i,j]` is non-zero, then there is an edge between row `i` and column `j`. OUTPUT: A list of tuples `(r_i,c_i)` representing edges between row `r_i` and column `c_i`. EXAMPLES:: sage: len(sage.matroids.utilities.spanning_forest(matrix([[1,1,1],[1,1,1],[1,1,1]]))) 5 sage: len(sage.matroids.utilities.spanning_forest(matrix([[0,0,1],[0,1,0],[0,1,0]]))) 3 """ # Given a matrix, produce a spanning tree G = Graph() m = M.ncols() for (x, y) in M.dict(): G.add_edge(x + m, y) T = [] # find spanning tree in each component for component in G.connected_components(): spanning_tree = kruskal(G.subgraph(component)) for (x, y, z) in spanning_tree: if x < m: t = x x = y y = t T.append((x - m, y)) return T
def spanning_forest(M): r""" Return a list of edges of a spanning forest of the bipartite graph defined by `M` INPUT: - ``M`` -- a matrix defining a bipartite graph G. The vertices are the rows and columns, if `M[i,j]` is non-zero, then there is an edge between row `i` and column `j`. OUTPUT: A list of tuples `(r_i,c_i)` representing edges between row `r_i` and column `c_i`. EXAMPLES:: sage: len(sage.matroids.utilities.spanning_forest(matrix([[1,1,1],[1,1,1],[1,1,1]]))) 5 sage: len(sage.matroids.utilities.spanning_forest(matrix([[0,0,1],[0,1,0],[0,1,0]]))) 3 """ # Given a matrix, produce a spanning tree G = Graph() m = M.ncols() for (x,y) in M.dict(): G.add_edge(x+m,y) T = [] # find spanning tree in each component for component in G.connected_components(): spanning_tree = kruskal(G.subgraph(component)) for (x,y,z) in spanning_tree: if x < m: t = x x = y y = t T.append((x-m,y)) return T
def strong_orientations_iterator(G): r""" Returns an iterator over all strong orientations of a graph `G`. A strong orientation of a graph is an orientation of its edges such that the obtained digraph is strongly connected (i.e. there exist a directed path between each pair of vertices). ALGORITHM: It is an adaptation of the algorithm published in [CGMRV16]_. It runs in `O(mn)` amortized time, where `m` is the number of edges and `n` is the number of vertices. The amortized time can be improved to `O(m)` with a more involved method. In this function, first the graph is preprocessed and a spanning tree is generated. Then every orientation of the non-tree edges of the graph can be extended to at least one new strong orientation by orienting properly the edges of the spanning tree (this property is proved in [CGMRV16]_). Therefore, this function generates all partial orientations of the non-tree edges and then launches a helper function corresponding to the generation algorithm described in [CGMRV16]_. In order to avoid trivial symetries, the orientation of an arbitrary edge is fixed before the start of the enumeration process. INPUT: - ``G`` -- an undirected graph. OUTPUT: - an iterator which will produce all strong orientations of this graph. .. NOTE:: Works only for simple graphs (no multiple edges). In order to avoid symetries an orientation of an arbitrary edge is fixed. EXAMPLES: A cycle has one possible (non-symmetric) strong orientation:: sage: g = graphs.CycleGraph(4) sage: it = g.strong_orientations_iterator() sage: len(list(it)) 1 A tree cannot be strongly oriented:: sage: g = graphs.RandomTree(100) sage: len(list(g.strong_orientations_iterator())) 0 Neither can be a disconnected graph:: sage: g = graphs.CompleteGraph(6) sage: g.add_vertex(7) sage: len(list(g.strong_orientations_iterator())) 0 TESTS: sage: g = graphs.CompleteGraph(2) sage: len(list(g.strong_orientations_iterator())) 0 sage: g = graphs.CubeGraph(3) sage: b = True sage: for orientedGraph in g.strong_orientations_iterator(): ....: if not orientedGraph.is_strongly_connected(): ....: b = False sage: b True The total number of strong orientations of a graph can be counted using the Tutte polynomial evaluated at points (0,2):: sage: g = graphs.PetersenGraph() sage: nr1 = len(list(g.strong_orientations_iterator())) sage: nr2 = g.tutte_polynomial()(0,2) sage: nr1 == nr2/2 # The Tutte polynomial counts also the symmetrical orientations True """ # if the graph has a bridge or is disconnected, # then it cannot be strongly oriented if G.order() < 3 or not G.is_biconnected(): return V = G.vertices() Dg = DiGraph([G.vertices(), G.edges()], pos=G.get_pos()) # compute an arbitrary spanning tree of the undirected graph te = kruskal(G) treeEdges = [(u,v) for u,v,_ in te] A = [edge for edge in G.edges(labels=False) if edge not in treeEdges] # initialization of the first binary word 00...0 # corresponding to the current orientation of the non-tree edges existingAedges = [0]*len(A) # Make the edges of the spanning tree doubly oriented for e in treeEdges: if Dg.has_edge(e): Dg.add_edge(e[1], e[0]) else: Dg.add_edge(e) # Generate all orientations for non-tree edges (using Gray code) # Each of these orientations can be extended to a strong orientation # of G by orienting properly the tree-edges previousWord = 0 i = 0 # the orientation of one edge is fixed so we consider one edge less nr = 2**(len(A)-1) while i < nr: word = (i >> 1) ^ i bitChanged = word ^ previousWord bit = 0 while bitChanged > 1: bitChanged >>= 1 bit += 1 previousWord = word if existingAedges[bit] == 0: Dg.reverse_edge(A[bit]) existingAedges[bit] = 1 else: Dg.reverse_edge(A[bit][1], A[bit][0]) existingAedges[bit] = 0 # launch the algorithm for enumeration of the solutions for sol in _strong_orientations_of_a_mixed_graph(Dg, V, treeEdges): yield sol i = i + 1
def strong_orientations_iterator(G): r""" Returns an iterator over all strong orientations of a graph `G`. A strong orientation of a graph is an orientation of its edges such that the obtained digraph is strongly connected (i.e. there exist a directed path between each pair of vertices). ALGORITHM: It is an adaptation of the algorithm published in [CGMRV16]_. It runs in `O(mn)` amortized time, where `m` is the number of edges and `n` is the number of vertices. The amortized time can be improved to `O(m)` with a more involved method. In this function, first the graph is preprocessed and a spanning tree is generated. Then every orientation of the non-tree edges of the graph can be extended to at least one new strong orientation by orienting properly the edges of the spanning tree (this property is proved in [CGMRV16]_). Therefore, this function generates all partial orientations of the non-tree edges and then launches a helper function corresponding to the generation algorithm described in [CGMRV16]_. In order to avoid trivial symetries, the orientation of an arbitrary edge is fixed before the start of the enumeration process. INPUT: - ``G`` -- an undirected graph. OUTPUT: - an iterator which will produce all strong orientations of this graph. .. NOTE:: Works only for simple graphs (no multiple edges). In order to avoid symetries an orientation of an arbitrary edge is fixed. EXAMPLES: A cycle has one possible (non-symmetric) strong orientation:: sage: g = graphs.CycleGraph(4) sage: it = g.strong_orientations_iterator() sage: len(list(it)) 1 A tree cannot be strongly oriented:: sage: g = graphs.RandomTree(100) sage: len(list(g.strong_orientations_iterator())) 0 Neither can be a disconnected graph:: sage: g = graphs.CompleteGraph(6) sage: g.add_vertex(7) sage: len(list(g.strong_orientations_iterator())) 0 TESTS: sage: g = graphs.CompleteGraph(2) sage: len(list(g.strong_orientations_iterator())) 0 sage: g = graphs.CubeGraph(3) sage: b = True sage: for orientedGraph in g.strong_orientations_iterator(): ....: if not orientedGraph.is_strongly_connected(): ....: b = False sage: b True The total number of strong orientations of a graph can be counted using the Tutte polynomial evaluated at points (0,2):: sage: g = graphs.PetersenGraph() sage: nr1 = len(list(g.strong_orientations_iterator())) sage: nr2 = g.tutte_polynomial()(0,2) sage: nr1 == nr2/2 # The Tutte polynomial counts also the symmetrical orientations True """ # if the graph has a bridge or is disconnected, # then it cannot be strongly oriented if G.order() < 3 or not G.is_biconnected(): return V = G.vertices() Dg = DiGraph([G.vertices(), G.edges()], pos=G.get_pos()) # compute an arbitrary spanning tree of the undirected graph te = kruskal(G) treeEdges = [(u,v) for u,v,_ in te] A = [edge for edge in G.edges(labels=False) if edge not in treeEdges] # initialization of the first binary word 00...0 # corresponding to the current orientation of the non-tree edges existingAedges = [0]*len(A) # Make the edges of the spanning tree doubly oriented for e in treeEdges: if Dg.has_edge(e): Dg.add_edge(e[1], e[0]) else: Dg.add_edge(e) # Generate all orientations for non-tree edges (using Gray code) # Each of these orientations can be extended to a strong orientation # of G by orienting properly the tree-edges previousWord = 0 i = 0 # the orientation of one edge is fixed so we consider one edge less nr = 2**(len(A)-1) while i < nr: word = (i >> 1) ^ i bitChanged = word ^ previousWord bit = 0 while bitChanged > 1: bitChanged >>= 1 bit += 1 previousWord = word if existingAedges[bit] == 0: Dg.reverse_edge(A[bit]) existingAedges[bit] = 1 else: Dg.reverse_edge(A[bit][1], A[bit][0]) existingAedges[bit] = 0 # launch the algorithm for enumeration of the solutions for sol in _strong_orientations_of_a_mixed_graph(Dg, V, treeEdges): yield sol i = i + 1