Ticket #13784: trac_13784.patch

File trac_13784.patch, 44.8 KB (added by ncohen, 8 years ago)
• sage/graphs/generic_graph.py

# HG changeset patch
# User Nathann Cohen <nathann.cohen@gmail.com>
# Date 1354362247 -3600
# Node ID 76c0c1b4aee6a503ca4569e9b3de9b7cc468cb3f
# Parent  654bc12b640b8bc0fa5da75f62f4ecfda1a351ac
Move methods from GenericGraph to Graph

diff --git a/sage/graphs/generic_graph.py b/sage/graphs/generic_graph.py
 a :meth:~GenericGraph.genus | Returns the minimal genus of the graph. :meth:~GenericGraph.trace_faces | A helper function for finding the genus of a graph. **Graph stuff that should not be in this file:** .. csv-table:: :class: contentstable :widths: 30, 70 :delim: | :meth:~GenericGraph.minimum_outdegree_orientation | Returns an orientation of self with the smallest possible maximum outdegree :meth:~GenericGraph.matching | Returns a maximum weighted matching of the graph :meth:~GenericGraph.maximum_average_degree | Returns the Maximum Average Degree (MAD) of the current graph. :meth:~GenericGraph.cores | Returns the core number for each vertex in an ordered list. Methods ------- """ import networkx return networkx.cycle_basis(self.networkx_graph(copy=False)) def minimum_outdegree_orientation(self, use_edge_labels=False, solver=None, verbose=0): r""" Returns an orientation of self with the smallest possible maximum outdegree. Given a Graph G, is is polynomial to compute an orientation D of the edges of G such that the maximum out-degree in D is minimized. This problem, though, is NP-complete in the weighted case [AMOZ06]_. INPUT: - use_edge_labels -- boolean (default: False) - When set to True, uses edge labels as weights to compute the orientation and assumes a weight of 1 when there is no value available for a given edge. - When set to False (default), gives a weight of 1 to all the edges. - solver -- (default: None) Specify a Linear Program (LP) solver to be used. If set to None, the default one is used. For more information on LP solvers and which default solver is used, see the method :meth:solve  of the class :class:MixedIntegerLinearProgram . - verbose -- integer (default: 0). Sets the level of verbosity. Set to 0 by default, which means quiet. EXAMPLE: Given a complete bipartite graph K_{n,m}, the maximum out-degree of an optimal orientation is \left\lceil \frac {nm} {n+m}\right\rceil:: sage: g = graphs.CompleteBipartiteGraph(3,4) sage: o = g.minimum_outdegree_orientation() sage: max(o.out_degree()) == ceil((4*3)/(3+4)) True REFERENCES: .. [AMOZ06] Asahiro, Y. and Miyano, E. and Ono, H. and Zenmyo, K. Graph orientation algorithms to minimize the maximum outdegree Proceedings of the 12th Computing: The Australasian Theory Symposium Volume 51, page 20 Australian Computer Society, Inc. 2006 """ if self.is_directed(): raise ValueError("Cannot compute an orientation of a DiGraph. "+\ "Please convert it to a Graph if you really mean it.") if use_edge_labels: from sage.rings.real_mpfr import RR weight = lambda u,v : self.edge_label(u,v) if self.edge_label(u,v) in RR else 1 else: weight = lambda u,v : 1 from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram(maximization=False, solver=solver) # The orientation of an edge is boolean # and indicates whether the edge uv # with uv else variable for u in self: p.add_constraint(p.sum([weight(u,v)*outgoing(u,v,orientation[min(u,v)][max(u,v)]) for v in self.neighbors(u)])-degree['max'],max=0) p.set_objective(degree['max']) p.set_binary(orientation) p.solve(log=verbose) orientation = p.get_values(orientation) # All the edges from self are doubled in O # ( one in each direction ) from sage.graphs.digraph import DiGraph O = DiGraph(self) # Builds the list of edges that should be removed edges=[] for u,v in self.edge_iterator(labels=None): # assumes uv: u,v=v,u if orientation[min(u,v)][max(u,v)] == 1: edges.append((max(u,v),min(u,v))) else: edges.append((min(u,v),max(u,v))) O.delete_edges(edges) return O ### Planarity def is_planar(self, on_embedding=None, kuratowski=False, set_embedding=False, set_pos=False): return paths def matching(self, value_only=False, algorithm="Edmonds", use_edge_labels=True, solver=None, verbose=0): r""" Returns a maximum weighted matching of the graph represented by the list of its edges. For more information, see the Wikipedia article on matchings _. Given a graph G such that each edge e has a weight w_e, a maximum matching is a subset S of the edges of G of maximum weight such that no two edges of S are incident with each other. As an optimization problem, it can be expressed as: .. math:: \mbox{Maximize : }&\sum_{e\in G.edges()} w_e b_e\\ \mbox{Such that : }&\forall v \in G, \sum_{(u,v)\in G.edges()} b_{(u,v)}\leq 1\\ &\forall x\in G, b_x\mbox{ is a binary variable} INPUT: - value_only -- boolean (default: False). When set to True, only the cardinal (or the weight) of the matching is returned. - algorithm -- string (default: "Edmonds") - "Edmonds" selects Edmonds' algorithm as implemented in NetworkX - "LP" uses a Linear Program formulation of the matching problem - use_edge_labels -- boolean (default: False) - When set to True, computes a weighted matching where each edge is weighted by its label. (If an edge has no label, 1 is assumed.) - When set to False, each edge has weight 1. - solver -- (default: None) Specify a Linear Program (LP) solver to be used. If set to None, the default one is used. For more information on LP solvers and which default solver is used, see the method :meth:solve  of the class :class:MixedIntegerLinearProgram . - verbose -- integer (default: 0). Sets the level of verbosity. Set to 0 by default, which means quiet. Only useful when algorithm == "LP". ALGORITHM: The problem is solved using Edmond's algorithm implemented in NetworkX, or using Linear Programming depending on the value of algorithm. EXAMPLES: Maximum matching in a Pappus Graph:: sage: g = graphs.PappusGraph() sage: g.matching(value_only=True) 9.0 Same test with the Linear Program formulation:: sage: g = graphs.PappusGraph() sage: g.matching(algorithm="LP", value_only=True) 9.0 TESTS: If algorithm is set to anything different from "Edmonds" or "LP", an exception is raised:: sage: g = graphs.PappusGraph() sage: g.matching(algorithm="somethingdifferent") Traceback (most recent call last): ... ValueError: algorithm must be set to either "Edmonds" or "LP" """ from sage.rings.real_mpfr import RR weight = lambda x: x if x in RR else 1 if algorithm == "Edmonds": import networkx if use_edge_labels: g = networkx.Graph() for u, v, l in self.edges(): g.add_edge(u, v, attr_dict={"weight": weight(l)}) else: g = self.networkx_graph(copy=False) d = networkx.max_weight_matching(g) if value_only: if use_edge_labels: return sum([weight(self.edge_label(u, v)) for u, v in d.iteritems()]) * 0.5 else: return Integer(len(d)/2) else: return [(u, v, self.edge_label(u, v)) for u, v in d.iteritems() if u < v] elif algorithm == "LP": from sage.numerical.mip import MixedIntegerLinearProgram g = self # returns the weight of an edge considering it may not be # weighted ... p = MixedIntegerLinearProgram(maximization=True, solver=solver) b = p.new_variable(dim=2) p.set_objective( p.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 for v in g.vertex_iterator(): p.add_constraint( p.sum([b[min(u, v)][max(u, v)] for u in g.neighbors(v)]), max=1) p.set_binary(b) if value_only: if use_edge_labels: return p.solve(objective_only=True, log=verbose) else: return Integer(round(p.solve(objective_only=True, log=verbose))) else: p.solve(log=verbose) b = p.get_values(b) return [(u, v, w) for u, v, w in g.edges() if b[min(u, v)][max(u, v)] == 1] else: raise ValueError('algorithm must be set to either "Edmonds" or "LP"') def dominating_set(self, independent=False, value_only=False, solver=None, verbose=0): r""" Returns a minimum dominating set of the graph return 2*Integer(self.size())/Integer(self.order()) def maximum_average_degree(self, value_only=True, solver = None, verbose = 0): r""" Returns the Maximum Average Degree (MAD) of the current graph. The Maximum Average Degree (MAD) of a graph is defined as the average degree of its densest subgraph. More formally, Mad(G) = \max_{H\subseteq G} Ad(H), where Ad(G) denotes the average degree of G. This can be computed in polynomial time. INPUT: - value_only (boolean) -- True by default - If value_only=True, only the numerical value of the MAD is returned. - Else, the subgraph of G realizing the MAD is returned. - solver -- (default: None) Specify a Linear Program (LP) solver to be used. If set to None, the default one is used. For more information on LP solvers and which default solver is used, see the method :meth:solve  of the class :class:MixedIntegerLinearProgram . - verbose -- integer (default: 0). Sets the level of verbosity. Set to 0 by default, which means quiet. EXAMPLES: In any graph, the Mad is always larger than the average degree:: sage: g = graphs.RandomGNP(20,.3) sage: mad_g = g.maximum_average_degree() sage: g.average_degree() <= mad_g True Unlike the average degree, the Mad of the disjoint union of two graphs is the maximum of the Mad of each graphs:: sage: h = graphs.RandomGNP(20,.3) sage: mad_h = h.maximum_average_degree() sage: (g+h).maximum_average_degree() == max(mad_g, mad_h) True The subgraph of a regular graph realizing the maximum average degree is always the whole graph :: sage: g = graphs.CompleteGraph(5) sage: mad_g = g.maximum_average_degree(value_only=False) sage: g.is_isomorphic(mad_g) True This also works for complete bipartite graphs :: sage: g = graphs.CompleteBipartiteGraph(3,4) sage: mad_g = g.maximum_average_degree(value_only=False) sage: g.is_isomorphic(mad_g) True """ g = self from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram(maximization=True, solver = solver) d = p.new_variable() one = p.new_variable() # Reorders u and v so that uv and vu are not considered # to be different edges reorder = lambda u,v : (min(u,v),max(u,v)) for u,v in g.edge_iterator(labels=False): p.add_constraint( one[ reorder(u,v) ] - 2*d[u] , max = 0 ) p.add_constraint( one[ reorder(u,v) ] - 2*d[v] , max = 0 ) p.add_constraint( p.sum([d[v] for v in g]), max = 1) p.set_objective( p.sum([ one[reorder(u,v)] for u,v in g.edge_iterator(labels=False)]) ) obj = p.solve(log = verbose) # Paying attention to numerical error : # The zero values could be something like 0.000000000001 # so I can not write l > 0 # And the non-zero, though they should be equal to # 1/(order of the optimal subgraph) may be a bit lower # setting the minimum to 1/(10 * size of the whole graph ) # should be safe :-) m = 1/(10 *Integer(g.order())) g_mad = g.subgraph([v for v,l in p.get_values(d).iteritems() if l>m ]) if value_only: return g_mad.average_degree() else: return g_mad def degree_histogram(self): """ Returns a list, whose ith entry is the frequency of degree i. """ import networkx return networkx.transitivity(self.networkx_graph(copy=False)) ### Cores def cores(self, k = None, with_labels=False): """ Returns the core number for each vertex in an ordered list. **DEFINITIONS** * *K-cores* in graph theory were introduced by Seidman in 1983 and by Bollobas in 1984 as a method of (destructively) simplifying graph topology to aid in analysis and visualization. They have been more recently defined as the following by Batagelj et al: *Given a graph G with vertices set V and edges set E, the k-core of G is the graph obtained from G by recursively removing the vertices with degree less than k, for as long as there are any.* This operation can be useful to filter or to study some properties of the graphs. For instance, when you compute the 2-core of graph G, you are cutting all the vertices which are in a tree part of graph.  (A tree is a graph with no loops). [WPkcore]_ [PSW1996]_ defines a k-core of G as the largest subgraph (it is unique) of G with minimum degree at least k. * Core number of a vertex The core number of a vertex v is the largest integer k such that v belongs to the k-core of G. * Degeneracy The *degeneracy* of a graph G, usually denoted \delta^*(G), is the smallest integer k such that the graph G can be reduced to the empty graph by iteratively removing vertices of degree \leq k. Equivalently, \delta^*(G)=k if k is the smallest integer such that the k-core of G is empty. **IMPLEMENTATION** This implementation is based on the NetworkX implementation of the algorithm described in [BZ]_. **INPUT** - k (integer) * If k = None (default), returns the core number for each vertex. * If k is an integer, returns a pair (ordering, core), where core is the list of vertices in the k-core of self, and ordering is an elimination order for the other vertices such that each vertex is of degree strictly less than k when it is to be eliminated from the graph. - with_labels (boolean) * When set to False, and k = None, the method returns a list whose i th element is the core number of the i th vertex. When set to True, the method returns a dictionary whose keys are vertices, and whose values are the corresponding core numbers. By default, with_labels = False. REFERENCE: .. [WPkcore] K-core. Wikipedia. (2007). [Online] Available: http://en.wikipedia.org/wiki/K-core .. [PSW1996] Boris Pittel, Joel Spencer and Nicholas Wormald. Sudden Emergence of a Giant k-Core in a Random Graph. (1996). J. Combinatorial Theory. Ser B 67. pages 111-151. [Online] Available: http://cs.nyu.edu/cs/faculty/spencer/papers/k-core.pdf .. [BZ] Vladimir Batagelj and Matjaz Zaversnik. An O(m) Algorithm for Cores Decomposition of Networks. arXiv:cs/0310049v1. [Online] Available: http://arxiv.org/abs/cs/0310049 EXAMPLES:: sage: (graphs.FruchtGraph()).cores() [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3] sage: (graphs.FruchtGraph()).cores(with_labels=True) {0: 3, 1: 3, 2: 3, 3: 3, 4: 3, 5: 3, 6: 3, 7: 3, 8: 3, 9: 3, 10: 3, 11: 3} sage: a=random_matrix(ZZ,20,x=2,sparse=True, density=.1) sage: b=DiGraph(20) sage: b.add_edges(a.nonzero_positions()) sage: cores=b.cores(with_labels=True); cores {0: 3, 1: 3, 2: 3, 3: 3, 4: 2, 5: 2, 6: 3, 7: 1, 8: 3, 9: 3, 10: 3, 11: 3, 12: 3, 13: 3, 14: 2, 15: 3, 16: 3, 17: 3, 18: 3, 19: 3} sage: [v for v,c in cores.items() if c>=2] # the vertices in the 2-core [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] Checking the 2-core of a random lobster is indeed the empty set:: sage: g = graphs.RandomLobster(20,.5,.5) sage: ordering, core = g.cores(2) sage: len(core) == 0 True """ # compute the degrees of each vertex degrees=self.degree(labels=True) # sort vertices by degree.  Store in a list and keep track of # where a specific degree starts (effectively, the list is # sorted by bins). verts= sorted( degrees.keys(), key=lambda x: degrees[x]) bin_boundaries=[0] curr_degree=0 for i,v in enumerate(verts): if degrees[v]>curr_degree: bin_boundaries.extend([i]*(degrees[v]-curr_degree)) curr_degree=degrees[v] vert_pos = dict((v,pos) for pos,v in enumerate(verts)) # Set up initial guesses for core and lists of neighbors. core= degrees nbrs=dict((v,set(self.neighbors(v))) for v in self) # form vertex core building up from smallest for v in verts: # If all the vertices have a degree larger than k, we can # return our answer if k != None if k is not None and core[v] >= k: return verts[:vert_pos[v]], verts[vert_pos[v]:] for u in nbrs[v]: if core[u] > core[v]: nbrs[u].remove(v) # cleverly move u to the end of the next smallest # bin (i.e., subtract one from the degree of u). # We do this by swapping u with the first vertex # in the bin that contains u, then incrementing # the bin boundary for the bin that contains u. pos=vert_pos[u] bin_start=bin_boundaries[core[u]] vert_pos[u]=bin_start vert_pos[verts[bin_start]]=pos verts[bin_start],verts[pos]=verts[pos],verts[bin_start] bin_boundaries[core[u]]+=1 core[u] -= 1 if k is not None: return verts, [] if with_labels: return core else: return core.values() ### Distance
• sage/graphs/graph.py

diff --git a/sage/graphs/graph.py b/sage/graphs/graph.py
 a :delim: | :meth:~Graph.gomory_hu_tree | Returns a Gomory-Hu tree of self. :meth:~Graph.minimum_outdegree_orientation | Returns an orientation of self with the smallest possible maximum outdegree :meth:~Graph.bounded_outdegree_orientation | Computes an orientation of self such that every vertex v has out-degree less than b(v) :meth:~Graph.strong_orientation | Returns a strongly connected orientation of the current graph. :meth:~Graph.degree_constrained_subgraph | Returns a degree-constrained subgraph. **Clique-related methods:** .. csv-table:: :widths: 30, 70 :delim: | :meth:~Graph.cores | Returns the core number for each vertex in an ordered list. :meth:~Graph.matching | Returns a maximum weighted matching of the graph :meth:~Graph.fractional_chromatic_index | Computes the fractional chromatic index of self :meth:~Graph.modular_decomposition | Returns the modular decomposition of the current graph. :meth:~Graph.maximum_average_degree | Returns the Maximum Average Degree (MAD) of the current graph. :meth:~Graph.two_factor_petersen | Returns a decomposition of the graph into 2-factors. else: d.add_edge(e) tmp = (e[0],e[1]) return d def minimum_outdegree_orientation(self, use_edge_labels=False, solver=None, verbose=0): r""" Returns an orientation of self with the smallest possible maximum outdegree. Given a Graph G, is is polynomial to compute an orientation D of the edges of G such that the maximum out-degree in D is minimized. This problem, though, is NP-complete in the weighted case [AMOZ06]_. INPUT: - use_edge_labels -- boolean (default: False) - When set to True, uses edge labels as weights to compute the orientation and assumes a weight of 1 when there is no value available for a given edge. - When set to False (default), gives a weight of 1 to all the edges. - solver -- (default: None) Specify a Linear Program (LP) solver to be used. If set to None, the default one is used. For more information on LP solvers and which default solver is used, see the method :meth:solve  of the class :class:MixedIntegerLinearProgram . - verbose -- integer (default: 0). Sets the level of verbosity. Set to 0 by default, which means quiet. EXAMPLE: Given a complete bipartite graph K_{n,m}, the maximum out-degree of an optimal orientation is \left\lceil \frac {nm} {n+m}\right\rceil:: sage: g = graphs.CompleteBipartiteGraph(3,4) sage: o = g.minimum_outdegree_orientation() sage: max(o.out_degree()) == ceil((4*3)/(3+4)) True REFERENCES: .. [AMOZ06] Asahiro, Y. and Miyano, E. and Ono, H. and Zenmyo, K. Graph orientation algorithms to minimize the maximum outdegree Proceedings of the 12th Computing: The Australasian Theory Symposium Volume 51, page 20 Australian Computer Society, Inc. 2006 """ if self.is_directed(): raise ValueError("Cannot compute an orientation of a DiGraph. "+\ "Please convert it to a Graph if you really mean it.") if use_edge_labels: from sage.rings.real_mpfr import RR weight = lambda u,v : self.edge_label(u,v) if self.edge_label(u,v) in RR else 1 else: weight = lambda u,v : 1 from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram(maximization=False, solver=solver) # The orientation of an edge is boolean # and indicates whether the edge uv # with uv else variable for u in self: p.add_constraint(p.sum([weight(u,v)*outgoing(u,v,orientation[min(u,v)][max(u,v)]) for v in self.neighbors(u)])-degree['max'],max=0) p.set_objective(degree['max']) p.set_binary(orientation) p.solve(log=verbose) orientation = p.get_values(orientation) # All the edges from self are doubled in O # ( one in each direction ) from sage.graphs.digraph import DiGraph O = DiGraph(self) # Builds the list of edges that should be removed edges=[] for u,v in self.edge_iterator(labels=None): # assumes uv: u,v=v,u if orientation[min(u,v)][max(u,v)] == 1: edges.append((max(u,v),min(u,v))) else: edges.append((min(u,v),max(u,v))) O.delete_edges(edges) return O def bounded_outdegree_orientation(self, bound): r""" Computes an orientation of self such that every vertex v else: raise ValueError("The 'algorithm' keyword must be set to either 'DLX' or 'MILP'.") def matching(self, value_only=False, algorithm="Edmonds", use_edge_labels=True, solver=None, verbose=0): r""" Returns a maximum weighted matching of the graph represented by the list of its edges. For more information, see the Wikipedia article on matchings _. Given a graph G such that each edge e has a weight w_e, a maximum matching is a subset S of the edges of G of maximum weight such that no two edges of S are incident with each other. As an optimization problem, it can be expressed as: .. math:: \mbox{Maximize : }&\sum_{e\in G.edges()} w_e b_e\\ \mbox{Such that : }&\forall v \in G, \sum_{(u,v)\in G.edges()} b_{(u,v)}\leq 1\\ &\forall x\in G, b_x\mbox{ is a binary variable} INPUT: - value_only -- boolean (default: False). When set to True, only the cardinal (or the weight) of the matching is returned. - algorithm -- string (default: "Edmonds") - "Edmonds" selects Edmonds' algorithm as implemented in NetworkX - "LP" uses a Linear Program formulation of the matching problem - use_edge_labels -- boolean (default: False) - When set to True, computes a weighted matching where each edge is weighted by its label. (If an edge has no label, 1 is assumed.) - When set to False, each edge has weight 1. - solver -- (default: None) Specify a Linear Program (LP) solver to be used. If set to None, the default one is used. For more information on LP solvers and which default solver is used, see the method :meth:solve  of the class :class:MixedIntegerLinearProgram . - verbose -- integer (default: 0). Sets the level of verbosity. Set to 0 by default, which means quiet. Only useful when algorithm == "LP". ALGORITHM: The problem is solved using Edmond's algorithm implemented in NetworkX, or using Linear Programming depending on the value of algorithm. EXAMPLES: Maximum matching in a Pappus Graph:: sage: g = graphs.PappusGraph() sage: g.matching(value_only=True) 9.0 Same test with the Linear Program formulation:: sage: g = graphs.PappusGraph() sage: g.matching(algorithm="LP", value_only=True) 9.0 TESTS: If algorithm is set to anything different from "Edmonds" or "LP", an exception is raised:: sage: g = graphs.PappusGraph() sage: g.matching(algorithm="somethingdifferent") Traceback (most recent call last): ... ValueError: algorithm must be set to either "Edmonds" or "LP" """ from sage.rings.real_mpfr import RR weight = lambda x: x if x in RR else 1 if algorithm == "Edmonds": import networkx if use_edge_labels: g = networkx.Graph() for u, v, l in self.edges(): g.add_edge(u, v, attr_dict={"weight": weight(l)}) else: g = self.networkx_graph(copy=False) d = networkx.max_weight_matching(g) if value_only: if use_edge_labels: return sum([weight(self.edge_label(u, v)) for u, v in d.iteritems()]) * 0.5 else: return Integer(len(d)/2) else: return [(u, v, self.edge_label(u, v)) for u, v in d.iteritems() if u < v] elif algorithm == "LP": from sage.numerical.mip import MixedIntegerLinearProgram g = self # returns the weight of an edge considering it may not be # weighted ... p = MixedIntegerLinearProgram(maximization=True, solver=solver) b = p.new_variable(dim=2) p.set_objective( p.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 for v in g.vertex_iterator(): p.add_constraint( p.sum([b[min(u, v)][max(u, v)] for u in g.neighbors(v)]), max=1) p.set_binary(b) if value_only: if use_edge_labels: return p.solve(objective_only=True, log=verbose) else: return Integer(round(p.solve(objective_only=True, log=verbose))) else: p.solve(log=verbose) b = p.get_values(b) return [(u, v, w) for u, v, w in g.edges() if b[min(u, v)][max(u, v)] == 1] else: raise ValueError('algorithm must be set to either "Edmonds" or "LP"') def fractional_chromatic_index(self, verbose_constraints = 0, verbose = 0): r""" Computes the fractional chromatic index of self The fractional chromatic index is computed through Linear Programming through its dual. The LP solved by sage is actually: .. MATH:: .. MATH:: \mbox{Maximize : }&\sum_{e\in E(G)} r_{e}\\ \mbox{Such that : }&\\ &\forall M\text{ matching }\subseteq G, \sum_{e\in M}r_{v}\leq 1\\ \mbox{Such that : }&\\ &\forall M\text{ matching }\subseteq G, \sum_{e\in M}r_{v}\leq 1\\ INPUT: - verbose_constraints -- whether to display which constraints are being generated. - verbose -- level of verbosity required from the LP solver .. NOTE:: This implementation can be improved by computing matchings through a EXAMPLE: The fractional chromatic index of a C_5 is 5/2:: sage: g = graphs.CycleGraph(5) sage: g.fractional_chromatic_index() 2.5 """ from sage.numerical.mip import MixedIntegerLinearProgram g = self.copy() p = MixedIntegerLinearProgram(constraint_generation = True) # One variable per edge r = p.new_variable(dim = 2) R = lambda x,y : r[x][y] if x of the class :class:MixedIntegerLinearProgram . - verbose -- integer (default: 0). Sets the level of verbosity. Set to 0 by default, which means quiet. EXAMPLES: In any graph, the Mad is always larger than the average degree:: sage: g = graphs.RandomGNP(20,.3) sage: mad_g = g.maximum_average_degree() sage: g.average_degree() <= mad_g True Unlike the average degree, the Mad of the disjoint union of two graphs is the maximum of the Mad of each graphs:: sage: h = graphs.RandomGNP(20,.3) sage: mad_h = h.maximum_average_degree() sage: (g+h).maximum_average_degree() == max(mad_g, mad_h) True The subgraph of a regular graph realizing the maximum average degree is always the whole graph :: sage: g = graphs.CompleteGraph(5) sage: mad_g = g.maximum_average_degree(value_only=False) sage: g.is_isomorphic(mad_g) True This also works for complete bipartite graphs :: sage: g = graphs.CompleteBipartiteGraph(3,4) sage: mad_g = g.maximum_average_degree(value_only=False) sage: g.is_isomorphic(mad_g) True """ g = self from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram(maximization=True, solver = solver) d = p.new_variable() one = p.new_variable() # Reorders u and v so that uv and vu are not considered # to be different edges reorder = lambda u,v : (min(u,v),max(u,v)) for u,v in g.edge_iterator(labels=False): p.add_constraint( one[ reorder(u,v) ] - 2*d[u] , max = 0 ) p.add_constraint( one[ reorder(u,v) ] - 2*d[v] , max = 0 ) p.add_constraint( p.sum([d[v] for v in g]), max = 1) p.set_objective( p.sum([ one[reorder(u,v)] for u,v in g.edge_iterator(labels=False)]) ) obj = p.solve(log = verbose) # Paying attention to numerical error : # The zero values could be something like 0.000000000001 # so I can not write l > 0 # And the non-zero, though they should be equal to # 1/(order of the optimal subgraph) may be a bit lower # setting the minimum to 1/(10 * size of the whole graph ) # should be safe :-) m = 1/(10 *Integer(g.order())) g_mad = g.subgraph([v for v,l in p.get_values(d).iteritems() if l>m ]) if value_only: return g_mad.average_degree() else: return g_mad def independent_set_of_representatives(self, family, solver=None, verbose=0): r""" Returns an independent set of representatives. ### Miscellaneous def cores(self, k = None, with_labels=False): """ Returns the core number for each vertex in an ordered list. **DEFINITIONS** * *K-cores* in graph theory were introduced by Seidman in 1983 and by Bollobas in 1984 as a method of (destructively) simplifying graph topology to aid in analysis and visualization. They have been more recently defined as the following by Batagelj et al: *Given a graph G with vertices set V and edges set E, the k-core of G is the graph obtained from G by recursively removing the vertices with degree less than k, for as long as there are any.* This operation can be useful to filter or to study some properties of the graphs. For instance, when you compute the 2-core of graph G, you are cutting all the vertices which are in a tree part of graph.  (A tree is a graph with no loops). [WPkcore]_ [PSW1996]_ defines a k-core of G as the largest subgraph (it is unique) of G with minimum degree at least k. * Core number of a vertex The core number of a vertex v is the largest integer k such that v belongs to the k-core of G. * Degeneracy The *degeneracy* of a graph G, usually denoted \delta^*(G), is the smallest integer k such that the graph G can be reduced to the empty graph by iteratively removing vertices of degree \leq k. Equivalently, \delta^*(G)=k if k is the smallest integer such that the k-core of G is empty. **IMPLEMENTATION** This implementation is based on the NetworkX implementation of the algorithm described in [BZ]_. **INPUT** - k (integer) * If k = None (default), returns the core number for each vertex. * If k is an integer, returns a pair (ordering, core), where core is the list of vertices in the k-core of self, and ordering is an elimination order for the other vertices such that each vertex is of degree strictly less than k when it is to be eliminated from the graph. - with_labels (boolean) * When set to False, and k = None, the method returns a list whose i th element is the core number of the i th vertex. When set to True, the method returns a dictionary whose keys are vertices, and whose values are the corresponding core numbers. By default, with_labels = False. REFERENCE: .. [WPkcore] K-core. Wikipedia. (2007). [Online] Available: http://en.wikipedia.org/wiki/K-core .. [PSW1996] Boris Pittel, Joel Spencer and Nicholas Wormald. Sudden Emergence of a Giant k-Core in a Random Graph. (1996). J. Combinatorial Theory. Ser B 67. pages 111-151. [Online] Available: http://cs.nyu.edu/cs/faculty/spencer/papers/k-core.pdf .. [BZ] Vladimir Batagelj and Matjaz Zaversnik. An O(m)` Algorithm for Cores Decomposition of Networks. arXiv:cs/0310049v1. [Online] Available: http://arxiv.org/abs/cs/0310049 EXAMPLES:: sage: (graphs.FruchtGraph()).cores() [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3] sage: (graphs.FruchtGraph()).cores(with_labels=True) {0: 3, 1: 3, 2: 3, 3: 3, 4: 3, 5: 3, 6: 3, 7: 3, 8: 3, 9: 3, 10: 3, 11: 3} sage: a=random_matrix(ZZ,20,x=2,sparse=True, density=.1) sage: b=DiGraph(20) sage: b.add_edges(a.nonzero_positions()) sage: cores=b.cores(with_labels=True); cores {0: 3, 1: 3, 2: 3, 3: 3, 4: 2, 5: 2, 6: 3, 7: 1, 8: 3, 9: 3, 10: 3, 11: 3, 12: 3, 13: 3, 14: 2, 15: 3, 16: 3, 17: 3, 18: 3, 19: 3} sage: [v for v,c in cores.items() if c>=2] # the vertices in the 2-core [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] Checking the 2-core of a random lobster is indeed the empty set:: sage: g = graphs.RandomLobster(20,.5,.5) sage: ordering, core = g.cores(2) sage: len(core) == 0 True """ # compute the degrees of each vertex degrees=self.degree(labels=True) # sort vertices by degree.  Store in a list and keep track of # where a specific degree starts (effectively, the list is # sorted by bins). verts= sorted( degrees.keys(), key=lambda x: degrees[x]) bin_boundaries=[0] curr_degree=0 for i,v in enumerate(verts): if degrees[v]>curr_degree: bin_boundaries.extend([i]*(degrees[v]-curr_degree)) curr_degree=degrees[v] vert_pos = dict((v,pos) for pos,v in enumerate(verts)) # Set up initial guesses for core and lists of neighbors. core= degrees nbrs=dict((v,set(self.neighbors(v))) for v in self) # form vertex core building up from smallest for v in verts: # If all the vertices have a degree larger than k, we can # return our answer if k != None if k is not None and core[v] >= k: return verts[:vert_pos[v]], verts[vert_pos[v]:] for u in nbrs[v]: if core[u] > core[v]: nbrs[u].remove(v) # cleverly move u to the end of the next smallest # bin (i.e., subtract one from the degree of u). # We do this by swapping u with the first vertex # in the bin that contains u, then incrementing # the bin boundary for the bin that contains u. pos=vert_pos[u] bin_start=bin_boundaries[core[u]] vert_pos[u]=bin_start vert_pos[verts[bin_start]]=pos verts[bin_start],verts[pos]=verts[pos],verts[bin_start] bin_boundaries[core[u]]+=1 core[u] -= 1 if k is not None: return verts, [] if with_labels: return core else: return core.values() def modular_decomposition(self): r""" Returns the modular decomposition of the current graph.