Ticket #7288: trac_7288.patch

File trac_7288.patch, 34.6 KB (added by ncohen, 11 years ago)
• sage/graphs/generic_graph.py

# HG changeset patch
# User Nathann Cohen <nathann.cohen@gmail.com>
# Date 1268382978 -3600
# Node ID f28f7f29eaf0b2432c15571286384acb35d91208
# Parent  deb9406f163f6048dd0b93813d158541393cf71b
[mq]: hu.patch

diff -r deb9406f163f -r f28f7f29eaf0 sage/graphs/generic_graph.py
 a v = pv return B, C def edge_cut(self,s,t,value_only=True,use_edge_labels=False): def edge_cut(self, s, t, value_only=True, use_edge_labels=False, vertices=False): r""" Returns a minimum edge cut between vertices s and t represented by a list of edges. A minimum edge cut between two vertices s and t of self is a set U of edges of minimum weight such that the graph obtained by removing U from self is disconnected. ( cf. http://en.wikipedia.org/wiki/Cut_%28graph_theory%29 ) represented by a list of edges. INPUT: - s -- Source vertex - t -- Sink vertex - value_only (boolean) - When set to True, only the value of a maximal flow is returned. - When set to False, a list of edges in the minimum cut is also returned. - use_edge_labels (boolean) - When set to True, computes a weighted minimum cut where each edge has a weight defined by its label. ( if an edge has no label, 1 is assumed ) - when set to False (default), each edge has weight 1. EXAMPLE: INPUT: - s -- source vertex - t -- sink vertex - value_only -- boolean (default: True). When set to True, only the weight of a minimum cut is returned. Otherwise, a list of edges of a minimum cut is also returned. - use_edge_labels -- boolean (default: False). When set to True, computes a weighted minimum cut where each edge has a weight defined by its label (if an edge has no label, 1 is assumed). Otherwise, each edge has weight 1. - vertices -- boolean (default: False). When set to True, also returns the two sets of vertices that are disconnected by the cut. Implies value_only=False. OUTPUT: real number or tuple, depending on the arguments given (examples are given below) EXAMPLES: A basic application in a PappusGraph:: sage: g=graphs.PappusGraph() sage: g.edge_cut(1,2,value_only=True) # optional - requires Glpk or COIN-OR/CBC A basic application in the Pappus graph:: sage: g = graphs.PappusGraph() sage: g.edge_cut(1, 2, value_only=True) # optional - requires GLPK or COIN-OR/CBC 3.0 If the graph is a path with weighted edges, the edge cut between the two ends is the edge of minimum weight :: If the graph is a path with randomly weighted edges:: sage: g = graphs.PathGraph(15) sage: for (u,v) in g.edge_iterator(labels=None): ...      g.set_edge_label(u,v,random()) The edge cut between the two ends is the edge of minimum weight:: sage: minimum = min([l for u,v,l in g.edge_iterator()]) sage: minimum == g.edge_cut(0,14,use_edge_labels=True)                                # optional - requires Glpk or COIN-OR/CBC True sage: [value,[[u,v]]] = g.edge_cut(0,14,use_edge_labels=True, value_only=False)       # optional - requires Glpk or COIN-OR/CBC sage: g.edge_label(u,v) == minimum                                                    # optional - requires Glpk or COIN-OR/CBC sage: minimum == g.edge_cut(0, 14, use_edge_labels=True) # optional - requires GLPK or COIN-OR/CBC True sage: [value,[[u,v]]] = g.edge_cut(0, 14, use_edge_labels=True, value_only=False) # optional - requires GLPK or COIN-OR/CBC sage: g.edge_label(u, v) == minimum # optional - requires GLPK or COIN-OR/CBC True The two sides of the edge cut are obviously shorter paths:: sage: value,edges,[set1,set2] = g.edge_cut(0, 14, use_edge_labels=True, vertices=True)  # optional - requires Glpk or COIN-OR/CBC sage: g.subgraph(set1).is_isomorphic(graphs.PathGraph(len(set1)))                     # optional - requires Glpk or COIN-OR/CBC True sage: g.subgraph(set2).is_isomorphic(graphs.PathGraph(len(set2)))                     # optional - requires Glpk or COIN-OR/CBC True sage: len(set1) + len(set2) == g.order()                                                # optional - requires Glpk or COIN-OR/CBC True """ from sage.numerical.mip import MixedIntegerLinearProgram g=self p=MixedIntegerLinearProgram(maximization=False) b=p.new_variable(dim=2) v=p.new_variable() g = self p = MixedIntegerLinearProgram(maximization=False) b = p.new_variable(dim=2) v = p.new_variable() if vertices: value_only = False if use_edge_labels: weight=lambda x: 1 if x==None else x else: weight=lambda x: 1 weight = lambda x: 1 if x == None else x else: weight = lambda x: 1 # Some vertices belong to part 1, others to part 0 p.add_constraint(v[s],min=0,max=0) p.add_constraint(v[t],min=1,max=1) p.add_constraint(v[s], min=0, max=0) p.add_constraint(v[t], min=1, max=1) if g.is_directed(): # we minimize the number of edges p.set_objective(sum([weight(w)*b[x][y] for (x,y,w) in g.edges()])) # Adjacent vertices can belong to different parts only if the # edge that connects them is part of the cut [p.add_constraint(v[x]+b[x][y]-v[y],min=0,max=0) for (x,y) in g.edges(labels=None)] else: # we minimize the number of edges p.set_objective(sum([weight(w)*b[min(x,y)][max(x,y)] for (x,y,w) in g.edges()])) p.set_objective(sum([weight(w) * b[x][y] for (x,y,w) in g.edges()])) # Adjacent vertices can belong to different parts only if the # edge that connects them is part of the cut for (x,y) in g.edges(labels=None): p.add_constraint(v[x]+b[min(x,y)][max(x,y)]-v[y],min=0) p.add_constraint(v[y]+b[min(x,y)][max(x,y)]-v[x],min=0) p.add_constraint(v[x] + b[x][y] - v[y], min=0, max=0) else: # we minimize the number of edges p.set_objective(sum([weight(w) * b[min(x,y)][max(x,y)] for (x,y,w) in g.edges()])) # Adjacent vertices can belong to different parts only if the # edge that connects them is part of the cut for (x,y) in g.edges(labels=None): p.add_constraint(v[x] + b[min(x,y)][max(x,y)] - v[y], min=0) p.add_constraint(v[y] + b[min(x,y)][max(x,y)] - v[x], min=0) p.set_binary(v) p.set_binary(b) if value_only: return p.solve(objective_only=True) else: obj=p.solve() b=p.get_values(b) obj = p.solve() b = p.get_values(b) answer = [obj] if g.is_directed(): return [obj,[(x,y) for (x,y) in g.edges(labels=None) if b[x][y]==1]] else: return [obj,[(x,y) for (x,y) in g.edges(labels=None) if b[min(x,y)][max(x,y)]==1]] def vertex_cut(self,s,t,value_only=True): r""" Returns a minimum vertex cut between non-adjacent vertices s and t answer.append([(x,y) for (x,y) in g.edges(labels=None) if b[x][y] == 1]) else: answer.append([(x,y) for (x,y) in g.edges(labels=None) if b[min(x,y)][max(x,y)] == 1]) if vertices: v = p.get_values(v) l0 = [] l1 = [] for x in g.vertex_iterator(): if v.has_key(x) and v[x] == 1: l1.append(x) else: l0.append(x) answer.append([l0, l1]) return tuple(answer) def vertex_cut(self, s, t, value_only=True, vertices=False): r""" Returns a minimum vertex cut between non adjacent vertices s and t represented by a list of vertices. A vertex cut between two non adjacent vertices is a set U of vertices of self such that the graph obtained by removing U from self is disconnected. ( cf. http://en.wikipedia.org/wiki/Cut_%28graph_theory%29 ) represented by a list of vertices. INPUT: - value_only : When set to True, only the cardinal of the minimum cut is returned EXAMPLE: A basic application in a PappusGraph:: sage: g=graphs.PappusGraph() sage: g.vertex_cut(1,16,value_only=True) # optional - requires Glpk or COIN-OR/CBC INPUT: - value_only -- boolean (default: True). When set to True, only the size of the minimum cut is returned - vertices -- boolean (default: False). When set to True, also returns the two sets of vertices that are disconnected by the cut. Implies value_only set to False. OUTPUT: real number or tuple, depending on the arguments given (examples are given below) EXAMPLE: A basic application in a Pappus graph:: sage: g = graphs.PappusGraph() sage: g.vertex_cut(1, 16, value_only=True) # optional - requires Glpk or COIN-OR/CBC 3.0 Or in a bipartite complete graph K_{2,8} between the first 2 vertices, in which case the minimum cut is the other set of 8 vertices :: sage: g = graphs.CompleteBipartiteGraph(2,8) sage: [value, vertices] = g.vertex_cut(0,1, value_only=False) # optional - requires Glpk or COIN-OR/CBC sage: print value                                             # optional - requires Glpk or COIN-OR/CBC In the bipartite complete graph K_{2,8}, a cut between the two vertices in the size 2 part consists of the other 8 vertices:: sage: g = graphs.CompleteBipartiteGraph(2, 8) sage: [value, vertices] = g.vertex_cut(0, 1, value_only=False) # optional - requires Glpk or COIN-OR/CBC sage: print value # optional - requires Glpk or COIN-OR/CBC 8.0 sage: vertices == range(2,10)                                 # optional - requires Glpk or COIN-OR/CBC sage: vertices == range(2,10) # optional - requires Glpk or COIN-OR/CBC True Clearly, in this case the two sides of the cut are singletons :: sage: [value, vertices, [set1, set2]] = g.vertex_cut(0,1, vertices=True) # optional - requires Glpk or COIN-OR/CBC sage: len(set1) == 1 # optional - requires Glpk or COIN-OR/CBC True sage: len(set2) == 1 # optional - requires Glpk or COIN-OR/CBC True """ from sage.numerical.mip import MixedIntegerLinearProgram g=self g = self if g.has_edge(s,t): raise ValueError, "There can be no vertex cut between adjacent vertices !" p=MixedIntegerLinearProgram(maximization=False) b=p.new_variable() v=p.new_variable() if vertices: value_only = False p = MixedIntegerLinearProgram(maximization=False) b = p.new_variable() v = p.new_variable() # Some vertices belong to part 1, some others to part 0 p.add_constraint(v[s],min=0,max=0) p.add_constraint(v[t],min=1,max=1) p.add_constraint(v[s], min=0, max=0) p.add_constraint(v[t], min=1, max=1) # b indicates whether the vertices belong to the cut p.add_constraint(b[s],min=0,max=0) p.add_constraint(b[t],min=0,max=0) p.add_constraint(b[s], min=0, max=0) p.add_constraint(b[t], min=0, max=0) if g.is_directed(): p.set_objective(sum([b[x] for x in g.vertices()])) # adjacent vertices belong to the same part except if one of them # belongs to the cut [p.add_constraint(v[x]+b[y]-v[y],min=0) for (x,y) in g.edges(labels=None)] for (x,y) in g.edges(labels=None): p.add_constraint(v[x] + b[y] - v[y], min=0) else: p.set_objective(sum([b[x] for x in g.vertices()])) # adjacent vertices belong to the same part except if one of them # belongs to the cut for (x,y) in g.edges(labels=None): p.add_constraint(v[x]+b[y]-v[y],min=0) p.add_constraint(v[y]+b[x]-v[x],min=0) p.add_constraint(v[x] + b[y] - v[y],min=0) p.add_constraint(v[y] + b[x] - v[x],min=0) p.set_binary(b) p.set_binary(v) if value_only: return p.solve(objective_only=True) else: obj=p.solve() b=p.get_values(b) return [obj,[v for v in g if b[v]==1]] obj = p.solve() b = p.get_values(b) answer = [obj,[x for x in g if b[x] == 1]] if vertices: v = p.get_values(v) l0 = [] l1 = [] for x in g.vertex_iterator(): # if the vertex is not in the cut if not (b.has_key(x) and b[x] == 1): if (v.has_key(x) and v[x] == 1): l1.append(x) else: l0.append(x) answer.append([l0, l1]) return tuple(answer) def vertex_cover(self,algorithm="Cliquer",value_only=False,log=0): r""" Returns a minimum vertex cover of the graph ( cf. http://en.wikipedia.org/wiki/Vertex_cover ) represented by the list of its vertices. Returns a minimum vertex cover of self represented by a list of vertices. A minimum vertex cover of a graph is a set S of its vertices such that each edge is incident to at least one element of S, and such that S is of minimum cardinality. A vertex cover is also defined as the complement of an independent set. As an optimization problem, it can be expressed as : ( cf. http://en.wikipedia.org/wiki/Vertex_cover ) Equivalently, a vertex cover is defined as the complement of an independent set. As an optimization problem, it can be expressed as follows: .. MATH:: \mbox{Minimize : }&\sum_{v\in G} b_v\\ INPUT: - algorithm -- Select an algorithm - "Cliquer" (default) will return a minimum vertex cover - algorithm -- string (default: "Cliquer") indicating which algorithm is performed. It can be one of those two values. - "Cliquer" will compute a minimum vertex cover using the algorithm Cliquer. - "MILP" will return a minimum vertex cover through a Mixed Integer Linear Program ( requires packages GLPK or CBC ) - value_only -- - If value_only = True, only the cardinality of a minimum vertex cover is returned. - If value_only = False ( default ), a minimum vertex cover is returned as the list of its vertices - log ( integer ) -- As vertex cover is an NP-complete problem, its solving may take some time depending on the graph. Use log to define the level of verbosity you want from the linear program solver. By default log=0, meaning that there will be no message printed by the solver. Only useful if algorithm="MILP". - "MILP" will compute a minimum vertex cover through a mixed integer linear program (requires packages GLPK or CBC). - value_only -- boolean (default: False). If set to True, only the size of a minimum vertex cover is returned. Otherwise, a minimum vertex cover is returned as a list of vertices. - log -- non negative integer (default: 0) precising the level of verbosity you want from the linear program solver. Since the problem of computing a vertex cover is NP-complete, its solving may take some time depending on the graph. A value of 0 means that there will be no message printed by the solver. Only useful if algorithm="MILP". EXAMPLES: On a PappusGraph :: sage: g=graphs.PappusGraph() On the Pappus graph :: sage: g = graphs.PappusGraph() sage: g.vertex_cover(value_only=True) 9 Obviously, the two methods return the same results :: sage: g=graphs.RandomGNP(10,.5) sage: vc1 = g.vertex_cover(algorithm="MILP")     # optional requires GLPK or CBC sage: vc2 = g.vertex_cover(algorithm="Cliquer")  # optional requires GLPK or CBC The two algorithms should return the same result:: sage: g = graphs.RandomGNP(10,.5) sage: vc1 = g.vertex_cover(algorithm="MILP") # optional requires GLPK or CBC sage: vc2 = g.vertex_cover(algorithm="Cliquer") # optional requires GLPK or CBC sage: len(vc1) == len(vc2) # optional requires GLPK or CBC True """ if algorithm=="Cliquer": if algorithm == "Cliquer": independent = self.independent_set() if value_only: return self.order()-len(independent) return self.order() - len(independent) else: return set(self.vertices()).difference(set(independent)) elif algorithm=="MILP": elif algorithm == "MILP": from sage.numerical.mip import MixedIntegerLinearProgram g=self p=MixedIntegerLinearProgram(maximization=False) b=p.new_variable() g = self p = MixedIntegerLinearProgram(maximization=False) b = p.new_variable() # minimizes the number of vertices in the set p.set_objective(sum([b[v] for v in g.vertices()])) # an edge contains at least one vertex of the minimum vertex cover [p.add_constraint(b[u]+b[v],min=1) for (u,v) in g.edges(labels=None)] for (u,v) in g.edges(labels=None): p.add_constraint(b[u] + b[v], min=1) p.set_binary(b) if value_only: return p.solve(objective_only=True,log=log) return p.solve(objective_only=True, log=log) else: p.solve() b=p.get_values(b) return set([v for v in g.vertices() if b[v]==1]) b = p.get_values(b) return set([v for v in g.vertices() if b[v] == 1]) else: raise ValueError("Only two algorithms are available : Cliquer and MILP.") N=range(n) # First and second constraints [p.add_constraint(sum([x[v][i] for i in N]),min=1,max=1) for v in self] [p.add_constraint(sum([x[v][i] for v in self]),min=1,max=1) for i in N] for v in self: p.add_constraint(sum([x[v][i] for i in N]),min=1,max=1) for i in N: p.add_constraint(sum([x[v][i] for v in self]),min=1,max=1) # Definition of d_v [p.add_constraint(sum([i*x[v][i] for i in N])-d[v],max=0,min=0) for v in self] for v in self: p.add_constraint(sum([i*x[v][i] for i in N])-d[v],max=0,min=0) # The removed vertices cover all the back arcs ( third condition ) [p.add_constraint(d[u]-d[v]+n*(b[(u,v)]),min=0) for (u,v) in self.edges(labels=None)] for (u,v) in self.edges(labels=None): p.add_constraint(d[u]-d[v]+n*(b[(u,v)]),min=0) p.set_binary(b) p.set_binary(x) N=range(n) # First and second constraints [p.add_constraint(sum([x[v][i] for i in N]),min=1,max=1) for v in self] [p.add_constraint(sum([x[v][i] for v in self]),min=1,max=1) for i in N] for v in self: p.add_constraint(sum([x[v][i] for i in N]),min=1,max=1) for i in N: p.add_constraint(sum([x[v][i] for v in self]),min=1,max=1) # Definition of d_v [p.add_constraint(sum([i*x[v][i] for i in N])-d[v],max=0,min=0) for v in self] for v in self: p.add_constraint(sum([i*x[v][i] for i in N])-d[v],max=0,min=0) # The removed vertices cover all the back arcs ( third condition ) [p.add_constraint(d[u]-d[v]+n*(b[u]+b[v]),min=0) for (u,v) in self.edges(labels=None)] for (u,v) in self.edges(labels=None): p.add_constraint(d[u]-d[v]+n*(b[u]+b[v]),min=0) p.set_binary(b) p.set_binary(x) a sink t to all the vertices of the second set, then computing a maximum s-t flow :: sage: g = graphs.CompleteBipartiteGraph(4,4) sage: g.add_edges([('s',i) for i in range(4)]) sage: g = DiGraph() sage: g.add_edges([('s',i) for i in range(4)]) sage: g.add_edges([(i,4+j) for i in range(4) for j in range(4)]) sage: g.add_edges([(4+i,'t') for i in range(4)]) sage: [cardinal, flow_graph] = g.flow('s','t',integer=True,value_only=False) # optional - requries GLPK or CBC sage: flow_graph.delete_vertices(['s','t'])                                  # optional - requries GLPK or CBC sage: flow_graph.edges(labels=None)                                          # optional - requries GLPK or CBC [(0, 5), (1, 4), (2, 7), (3, 6)] sage: len(flow_graph.edges(labels=None))                                     # optional - requries GLPK or CBC 4 """ from sage.numerical.mip import MixedIntegerLinearProgram p.set_objective(flow_sum(x)) # Elsewhere, the flow is equal to 0- [p.add_constraint(flow_sum(v),min=0,max=0) for v in g if v!=x and v!=y] for v in g: if v!=x and v!=y: p.add_constraint(flow_sum(v),min=0,max=0) # Capacity constraints [p.add_constraint(flow[u][v],max=capacity(w)) for (u,v,w) in g.edges()] for (u,v,w) in g.edges(): p.add_constraint(flow[u][v],max=capacity(w)) if vertex_bound: [p.add_constraint(sum([flow[uu][vv] for (uu,vv) in g.outgoing_edges([v],labels=None)]),max=1) for v in g.vertices() if v!=x and v!=y] for v in g.vertices(): if v!=x and v!=y: p.add_constraint(sum([flow[uu][vv] for (uu,vv) in g.outgoing_edges([v],labels=None)]),max=1) else: # This function return the balance of flow at X p.set_objective(flow_sum(x)) # Elsewhere, the flow is equal to 0 [p.add_constraint(flow_sum(v),min=0,max=0) for v in g if v!=x and v!=y] for v in g: if v!=x and v!=y: p.add_constraint(flow_sum(v),min=0,max=0) # Capacity constraints [p.add_constraint(flow[u][v]+flow[v][u],max=capacity(w)) for (u,v,w) in g.edges()] for (u,v,w) in g.edges(): p.add_constraint(flow[u][v]+flow[v][u],max=capacity(w)) if vertex_bound: [p.add_constraint([flow[X][v] for X in g[v]],max=1) for v in g if v!=x and v!=y] for v in g: if v!=x and v!=y: p.add_constraint([flow[X][v] for X in g[v]],max=1) if integer: p=MixedIntegerLinearProgram(maximization=True) b=p.new_variable(dim=2) [p.set_objective(sum([weight(w)*b[min(u,v)][max(u,v)] for (u,v,w) in g.edges()]))] p.set_objective(sum([weight(w)*b[min(u,v)][max(u,v)] for (u,v,w) in g.edges()])) # for any vertex v, there is at most one edge incident to v in the maximum matching [p.add_constraint(sum([b[min(u,v)][max(u,v)] for u in g.neighbors(v)]),max=1) for v in g.vertices()] for v in g.vertices(): p.add_constraint(sum([b[min(u,v)][max(u,v)] for u in g.neighbors(v)]),max=1) p.set_binary(b) # For any vertex v, one of its neighbors or v itself is in # the minimum dominating set [p.add_constraint(b[v]+sum([b[u] for u in g.neighbors(v)]),min=1) for v in g.vertices()] for v in g.vertices(): p.add_constraint(b[v]+sum([b[u] for u in g.neighbors(v)]),min=1) if independent: # no two adjacent vertices are in the set [p.add_constraint(b[u]+b[v],max=1) for (u,v) in g.edges(labels=None)] for (u,v) in g.edges(labels=None): p.add_constraint(b[u]+b[v],max=1) # Minimizes the number of vertices used p.set_objective(sum([b[v] for v in g.vertices()]))
• sage/graphs/graph.py

diff -r deb9406f163f -r f28f7f29eaf0 sage/graphs/graph.py
 a else: raise NotImplementedError, "Minimum Spanning Tree algorithm '%s' is not implemented."%algorithm def _gomory_hu_tree(self, vertices=None): r""" Returns the Gomory-Hu tree associated to self (private function) This function is the privatre counterpart of gomory_hu_tree, with the difference that it accepts an optional argument needed for recursive computations, which the user is not interested in defining by himself. See the documentation of gomory_hu_tree for more information. INPUT: - vertices -- a set of ''real'' vertices, as opposed to the fakes one introduced during the computations. This variable is useful for the algorithm, and I see no reason for the user to change it. EXAMPLE: This function is actually tested in gomory_hu_tree, this example is only present to have a doctest coverage of 100% sage: g = graphs.PetersenGraph() sage: t = g._gomory_hu_tree()      # optional - requires GLPK or CBC """ from sage.sets.set import Set # The default capacity of an arc is 1 capacity = lambda label: label if label is not None else 1 # Keeping the graph's embedding pos = False # Small case, not really a problem ;-) if self.order() == 1: return copy(g) # This is a sign that this is the first call # to this recursive function if vertices is None: # Now is the time to care about positions pos = self.get_pos() # if the graph is not connected, returns the union # of the Gomory-Hu tree of each component if not self.is_connected(): g = Graph() for cc in self.connected_components_subgraphs(): g = g.union(cc._gomory_hu_tree()) g.set_pos(self.get_pos()) return g # All the vertices is this graph are the "real ones" vertices = Set(self.vertices()) # There may be many vertices, though only one which is "real" if len(vertices) == 1: g = Graph() g.add_vertex(vertices[0]) return g # Take any two vertices u,v = vertices[0:2] # flow = connectivity between u and v # edges = min cut # sets1, sets2 = the two sides of the edge cut flow,edges,[set1,set2] = self.edge_cut(u, v, use_edge_labels=True, vertices=True) # One graph for each part of the previous one g1,g2 = self.subgraph(set1), self.subgraph(set2) # Adding the fake vertex to each part g1_v = Set(set2) g2_v = Set(set1) g1.add_vertex(g1_v) g1.add_vertex(g2_v) # Each part of the graph had many edges going to the other part # Now that we have a new fake vertex in each part # we just say that the edges which were in the cut and going # to the other side are now going to this fake vertex # We must preserve the labels. They sum. for x,y in edges: # Assumes x is in g1 if x in g2: x,y = y,x # If the edge x-g1_v exists, adds to its label the capacity of arc xy if g1.has_edge(x, g1_v): g1.set_edge_label(x, g1_v, g1.edge_label(x, g1_v) + capacity(self.edge_label(x, y))) else: # Otherwise, creates it with the good label g1.add_edge(x, g1_v, capacity(self.edge_label(x, y))) # Same thing for g2 if g2.has_edge(y, g2_v): g2.set_edge_label(y, g2_v, g2.edge_label(y, g2_v) + capacity(self.edge_label(x, y))) else: g2.add_edge(y, g2_v, capacity(self.edge_label(x, y))) # Recursion for the two new graphs... The new "real" vertices are the intersection with # with the previous set of "real" vertices g1_tree = g1._gomory_hu_tree(vertices=(vertices & Set(g1.vertices()))) g2_tree = g2._gomory_hu_tree(vertices=(vertices & Set(g2.vertices()))) # Union of the two partial trees ( it is disjoint, but # disjoint_union does not preserve the name of the vertices ) g = g1_tree.union(g2_tree) # An edge to connect them, with the appropriate label g.add_edge(g1_tree.vertex_iterator().next(), g2_tree.vertex_iterator().next(), flow) if pos: g.set_pos(pos) return g def gomory_hu_tree(self): r""" Returns the Gomory-Hu tree associated to self. Given a tree T with labelled edges representing capacities, it is very easy to determine the maximal flow between any pair of vertices : it is the minimal label on the edges of the unique path between them. Given a graph G, the Gomory-Hu tree T of G, is a tree with the same set of vertices, and such that the maximal flow between any two vertices is the same in G and in T. (see http://en.wikipedia.org/wiki/Gomory%E2%80%93Hu_tree) OUTPUT: graph with labeled edges EXAMPLE: Taking the Petersen graph:: sage: g = graphs.PetersenGraph() sage: t = g.gomory_hu_tree() # optional - requires GLPK or CBC Obviously, this graph is a tree:: sage: t.is_tree()  # optional - requires GLPK or CBC True Note that if the original graph is not connected, then the Gomory-Hu tree is in fact a forest:: sage: (2*g).gomory_hu_tree().is_forest() # optional - requires GLPK or CBC True sage: (2*g).gomory_hu_tree().is_connected() # optional - requires GLPK or CBC False On the other hand, such a tree has lost nothing of the initial graph connectedness:: sage: all([ t.flow(u,v) == g.flow(u,v) for u,v in Subsets( g.vertices(), 2 ) ]) # optional - requires GLPK or CBC True Just to make sure, let's check the same is true for two vertices in a random graph:: sage: g = graphs.RandomGNP(20,.3) sage: t = g.gomory_hu_tree() # optional - requires GLPK or CBC sage: g.flow(0,1) == t.flow(0,1) # optional - requires GLPK or CBC True And also the min cut:: sage: g.edge_connectivity() == min(t.edge_labels()) # optional - requires GLPK or CBC True """ return self._gomory_hu_tree() def two_factor_petersen(self): r""" Returns a decomposition of the graph into 2-factors.
• sage/graphs/graph_generators.py

diff -r deb9406f163f -r f28f7f29eaf0 sage/graphs/graph_generators.py
 a sage: g = graphs.DegreeSequenceBipartite([2,2,2,2,1],[5,5]) # optional - requires GLPK or CBC Traceback (most recent call last): ... ValueError: The two partitions must sum to the same value. """ from sage.combinat.partition import Partition ValueError: There exists no bipartite graph corresponding to the given degree sequences """ from sage.combinat.integer_vector import gale_ryser_theorem from sage.graphs.bipartite_graph import BipartiteGraph s1.sort(reverse=True) s2.sort(reverse=True) p1 = Partition(s1) p2 = Partition(s2) return BipartiteGraph(p1.gale_ryser_theorem(p2)) s1 = sorted(s1, reverse = True) s2 = sorted(s2, reverse = True) m = gale_ryser_theorem(s1,s2) if m is False: raise ValueError("There exists no bipartite graph corresponding to the given degree sequences") else: return BipartiteGraph(m) def DegreeSequenceConfigurationModel(self, deg_sequence, seed=None): """