# Ticket #12716: trac_12716-combined.patch

File trac_12716-combined.patch, 22.6 KB (added by dcoudert, 9 years ago)
• ## sage/graphs/graph_decompositions/vertex_separation.pyx

# HG changeset patch
# User dcoudert <david.coudert@inria.fr>
# Date 1334084333 -7200
diff --git a/sage/graphs/graph_decompositions/vertex_separation.pyx b/sage/graphs/graph_decompositions/vertex_separation.pyx
 a r""" Vertex separation This module implements several algorithms to compute the vertex separation of a digraph and the corresponding ordering of the vertices. Given an ordering v_1, ..., v_n of the vertices of V(G), its *cost* is defined as: This module implements several algorithms to compute the vertex separation of a digraph and the corresponding ordering of the vertices. It also implements tests functions for evaluation the width of a linear ordering. Given an ordering v_1,\cdots, v_n of the vertices of V(G), its *cost* is defined as: .. MATH:: c(v_1, ..., v_n) = \max_{1\leq i \leq n} c'(\{v_1, ..., v_i\}) Where Where .. MATH:: c'(S) = |N^+_G(S)\backslash S| The *vertex separation* of a digraph G is equal to the minimum cost of an ordering of its vertices. The *vertex separation* of a digraph G is equal to the minimum cost of an ordering of its vertices. **Vertex separation and pathwidth** G a digraph D with the same vertex set, and in which each edge uv of G is replaced by two edges uv and vu in D. The vertex separation of D is equal to the pathwidth of G, and the corresponding ordering of the vertices of D encodes an optimal path-decomposition of G. D, also called a *layout*, encodes an optimal path-decomposition of G. This is a result of Kinnersley [Kin92]_ and Bodlaender [Bod98]_. **References** .. [Kin92] *The vertex separation number of a graph equals its path-width*, Nancy G. Kinnersley, Information Processing Letters, Volume 42, Issue 6, Pages 345-350, 24 July 1992 **This module contains the following methods** .. [Bod98] *A partial k-arboretum of graphs with bounded treewidth*, Hans L. Bodlaender, Theoretical Computer Science, Volume 209, Issues 1-2, Pages 1-45, 6 December 1998 .. csv-table:: :class: contentstable :widths: 30, 70 :delim: | **Authors** :meth:path_decomposition | Returns the pathwidth of the given graph and the ordering of the vertices resulting in a corresponding path decomposition :meth:vertex_separation | Returns an optimal ordering of the vertices and its cost for vertex-separation :meth:vertex_separation_MILP | Computes the vertex separation of G and the optimal ordering of its vertices using an MILP formulation :meth:lower_bound | Returns a lower bound on the vertex separation of G :meth:is_valid_ordering | Test if the linear vertex ordering L is valid for (di)graph G :meth:width_of_path_decomposition | Returns the width of the path decomposition induced by the linear ordering L of the vertices of G - Nathann Cohen Exponential algorithm for vertex separation ------------------------------------------- Because of its current implementation, this algorithm only works on graphs on less than 32 vertices. This can be changed to 64 if necessary, but 32 vertices already require 4GB of memory. vertices already require 4GB of memory. Running it on 64 bits is not expected to be doable by the computers of the next decade :-D **Lower bound on the vertex separation** \max c'(\{v_1\}),c'(\{v_1,v_2\}),...,c'(\{v_1,...,v_n\})\geq\max c'_1,...,c'_n where c_i is the minimum cost of a set S on i vertices. Evaluating the c_i can take time (and in particular more than the previous exact algorithm), but it does not need much memory to run. where c_i is the minimum cost of a set S on i vertices. Evaluating the c_i can take time (and in particular more than the previous exact algorithm), but it does not need much memory to run. Methods MILP formulation for the vertex separation ------------------------------------------ We describe below a mixed integer linear program (MILP) for determining an optimal layout for the vertex separation of G, which is an improved version of the formulation proposed in [SP10]_. It aims at building a sequence S_t of sets such that an ordering v_1, ..., v_n of the vertices correspond to S_0=\{v_1\}, S_2=\{v_1,v_2\}, ..., S_{n-1}=\{v_1,...,v_n\}. **Variables:** - y_v^t -- Variable set to 1 if v\in S_t, and 0 otherwise. The order of v in the layout is the smallest t such that y_v^t==1. - u_v^t -- Variable set to 1 if v\not \in S_t and v has an in-neighbor in S_t. It is set to 0 otherwise. - x_v^t -- Variable set to 1 if either v\in S_t or if v has an in-neighbor in S_t. It is set to 0 otherwise. - z -- Objective value to minimize. It is equal to the maximum over all step t of the number of vertices such that u_v^t==1. **MILP formulation:** .. MATH:: :nowrap: \begin{alignat}{2} \intertext{Minimize:} &z&\\ \intertext{Such that:} x_v^t &\leq x_v^{t+1}& \forall v\in V,\ 0\leq t\leq n-2\\ y_v^t &\leq y_v^{t+1}& \forall v\in V,\ 0\leq t\leq n-2\\ y_v^t &\leq x_w^t& \forall v\in V,\ \forall w\in N^+(v),\ 0\leq t\leq n-1\\ \sum_{v \in V} y_v^{t} &= t+1& 0\leq t\leq n-1\\ x_v^t-y_v^t&\leq u_v^t & \forall v \in V,\ 0\leq t\leq n-1\\ \sum_{v \in V} u_v^t &\leq z& 0\leq t\leq n-1\\ 0 \leq x_v^t &\leq 1& \forall v\in V,\ 0\leq t\leq n-1\\ 0 \leq u_v^t &\leq 1& \forall v\in V,\ 0\leq t\leq n-1\\ y_v^t &\in \{0,1\}& \forall v\in V,\ 0\leq t\leq n-1\\ 0 \leq z &\leq n& \end{alignat} The vertex separation of G is given by the value of z, and the order of vertex v in the optimal layout is given by the smallest t for which y_v^t==1. REFERENCES ---------- .. [Bod98] *A partial k-arboretum of graphs with bounded treewidth*, Hans L. Bodlaender, Theoretical Computer Science 209(1-2):1-45, 1998. .. [Kin92] *The vertex separation number of a graph equals its path-width*, Nancy G. Kinnersley, Information Processing Letters 42(6):345-350, 1992. .. [SP10] *Lightpath Reconfiguration in WDM networks*, Fernando Solano and Michal Pioro, IEEE/OSA Journal of Optical Communication and Networking 2(12):1010-1021, 2010. AUTHORS ------- - Nathann Cohen (2011-10): Initial version and exact exponential algorithm - David Coudert (2012-04): MILP formulation and tests functions METHODS ------- """ documentation). .. NOTE:: This method runs in exponential time but has no memory constraint. EXAMPLE: On a cycle:: On a circuit:: sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound sage: g = digraphs.Circuit(6) return min #################### # Exact algorithms # #################### ################################ # Exact exponential algorithms # ################################ def path_decomposition(G, verbose = False): def path_decomposition(G, algorithm = "exponential", verbose = False): r""" Returns the pathwidth of the given graph and the ordering of the vertices resulting in a corresponding path decomposition. - G -- a digraph - algorithm -- (default: "exponential") Specify the algorithm to use among - exponential -- Use an exponential time and space algorithm. This algorithm only works of graphs on less than 32 vertices. - MILP -- Use a mixed integer linear programming formulation. This algorithm has no size restriction but could take a very long time. - verbose (boolean) -- whether to display information on the computations. .. NOTE:: Because of its current implementation, this algorithm only works on graphs on less than 32 vertices. This can be changed to 54 if necessary, but 32 vertices already require 4GB of memory. Because of its current implementation, this exponential algorithm only works on graphs on less than 32 vertices. This can be changed to 54 if necessary, but 32 vertices already require 4GB of memory. EXAMPLE: The vertex separation of a circuit is equal to 2:: The vertex separation of a cycle is equal to 2:: sage: from sage.graphs.graph_decompositions.vertex_separation import path_decomposition sage: g = graphs.CycleGraph(6) sage: path_decomposition(g) (2, [0, 1, 2, 3, 4, 5]) sage: pw, L = path_decomposition(g); pw 2 sage: pwm, Lm = path_decomposition(g, algorithm = "MILP"); pwm 2 TEST: from sage.graphs.graph import Graph if not isinstance(G, Graph): raise ValueError("The parameter must be a Graph.") from sage.graphs.digraph import DiGraph return vertex_separation(DiGraph(G), verbose = verbose) if algorithm == "exponential": return vertex_separation(DiGraph(G), verbose = verbose) else: return vertex_separation_MILP(DiGraph(G), verbosity = (1 if verbose else 0)) def vertex_separation(G, verbose = False): r""" EXAMPLE: The vertex separation of a circuit is equal to 2:: The vertex separation of a circuit is equal to 1:: sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation sage: g = digraphs.Circuit(6) sage: vertex_separation(g) (1, [0, 1, 2, 3, 4, 5]) (1, [0, 1, 2, 3, 4, 5]) TEST: Graphs with non-integer vertices:: sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation sage: D=digraphs.DeBruijn(2,3) sage: vertex_separation(D) (2, ['000', '001', '100', '010', '101', '011', '110', '111']) sage_free(neighborhoods) _sig_off _sig_off return k, order return a else: return b ################################################################# # Function for testing the validity of a linear vertex ordering # ################################################################# def is_valid_ordering(G, L): r""" Test if the linear vertex ordering L is valid for (di)graph G. A linear ordering L of the vertices of a (di)graph G is valid if all vertices of G are in L, and if L contains no other vertex and no duplicated vertices. INPUT: - G -- a Graph or a DiGraph. - L -- an ordered list of the vertices of G. OUTPUT: Returns True if L is a valid vertex ordering for G, and False oterwise. EXAMPLE: Path decomposition of a cycle:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: G = graphs.CycleGraph(6) sage: L = [u for u in G.vertices()] sage: vertex_separation.is_valid_ordering(G, L) True sage: vertex_separation.is_valid_ordering(G, [1,2]) False TEST: Giving anything else than a Graph or a DiGraph:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: vertex_separation.is_valid_ordering(2, []) Traceback (most recent call last): ... ValueError: The input parameter must be a Graph or a DiGraph. Giving anything else than a list:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: G = graphs.CycleGraph(6) sage: vertex_separation.is_valid_ordering(G, {}) Traceback (most recent call last): ... ValueError: The second parameter must be of type 'list'. """ from sage.graphs.graph import Graph from sage.graphs.digraph import DiGraph if not isinstance(G, Graph) and not isinstance(G, DiGraph): raise ValueError("The input parameter must be a Graph or a DiGraph.") if not isinstance(L, list): raise ValueError("The second parameter must be of type 'list'.") return set(L) == set(G.vertices()) #################################################################### # Measurement functions of the widths of some graph decompositions # #################################################################### def width_of_path_decomposition(G, L): r""" Returns the width of the path decomposition induced by the linear ordering L of the vertices of G. If G is an instance of :mod:Graph , this function returns the width pw_L(G) of the path decomposition induced by the linear ordering L of the vertices of G. If G is a :mod:DiGraph , it returns instead the width vs_L(G) of the directed path decomposition induced by the linear ordering L of the vertices of G, where .. MATH:: vs_L(G) & =  \max_{0\leq i< |V|-1} | N^+(L[:i])\setminus L[:i] |\\ pw_L(G) & =  \max_{0\leq i< |V|-1} | N(L[:i])\setminus L[:i] |\\ INPUT: - G -- a Graph or a DiGraph - L -- a linear ordering of the vertices of G EXAMPLES: Path decomposition of a cycle:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: G = graphs.CycleGraph(6) sage: L = [u for u in G.vertices()] sage: vertex_separation.width_of_path_decomposition(G, L) 2 Directed path decomposition of a circuit:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: G = digraphs.Circuit(6) sage: L = [u for u in G.vertices()] sage: vertex_separation.width_of_path_decomposition(G, L) 1 TESTS: Path decomposition of a BalancedTree:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: G = graphs.BalancedTree(3,2) sage: pw, L = vertex_separation.path_decomposition(G) sage: pw == vertex_separation.width_of_path_decomposition(G, L) True sage: L.reverse() sage: pw == vertex_separation.width_of_path_decomposition(G, L) False Directed path decomposition of a circuit:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: G = digraphs.Circuit(8) sage: vs, L = vertex_separation.vertex_separation(G) sage: vs == vertex_separation.width_of_path_decomposition(G, L) True sage: L = [0,4,6,3,1,5,2,7] sage: vs == vertex_separation.width_of_path_decomposition(G, L) False Giving a wrong linear ordering:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: G = Graph() sage: vertex_separation.width_of_path_decomposition(G, ['a','b']) Traceback (most recent call last): ... ValueError: The input linear vertex ordering L is not valid for G. """ if not is_valid_ordering(G, L): raise ValueError("The input linear vertex ordering L is not valid for G.") vsL = 0 S = set() neighbors_of_S_in_V_minus_S = set() for u in L: # We remove u from the neighbors of S neighbors_of_S_in_V_minus_S.discard(u) # We add vertex u to the set S S.add(u) if G._directed: Nu = G.neighbors_out(u) else: Nu = G.neighbors(u) # We add the (out-)neighbors of u to the neighbors of S for v in Nu: if (not v in S): neighbors_of_S_in_V_minus_S.add(v) # We update the cost of the vertex separation vsL = max( vsL, len(neighbors_of_S_in_V_minus_S) ) return vsL ########################################## # MILP formulation for vertex separation # ########################################## def vertex_separation_MILP(G, integrality = False, solver = None, verbosity = 0): r""" Computes the vertex separation of G and the optimal ordering of its vertices using an MILP formulation. This function uses a mixed integer linear program (MILP) for determining an optimal layout for the vertex separation of G. This MILP is an improved version of the formulation proposed in [SP10]_. See the :mod:module's documentation  for more details on this MILP formulation. INPUTS: - G -- a DiGraph - integrality -- (default: False) Specify if variables x_v^t and u_v^t must be integral or if they can be relaxed. This has no impact on the validity of the solution, but it is sometimes faster to solve the problem using binary variables only. - 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. OUTPUT: A pair (cost, ordering) representing the optimal ordering of the vertices and its cost. EXAMPLE: Vertex separation of a De Bruijn digraph:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: G = digraphs.DeBruijn(2,3) sage: vs, L = vertex_separation.vertex_separation_MILP(G); vs 2 sage: vs == vertex_separation.width_of_path_decomposition(G, L) True sage: vse, Le = vertex_separation.vertex_separation(G); vse 2 The vertex separation of a circuit is 1:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: G = digraphs.Circuit(6) sage: vs, L = vertex_separation.vertex_separation_MILP(G); vs 1 TESTS: Comparison with exponential algorithm:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: for i in range(10): ...       G = digraphs.RandomDirectedGNP(10, 0.2) ...       ve, le = vertex_separation.vertex_separation(G) ...       vm, lm = vertex_separation.vertex_separation_MILP(G) ...       if ve != vm: ...          print "The solution is not optimal!" Comparison with Different values of the integrality parameter:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: for i in range(10): ...       G = digraphs.RandomDirectedGNP(10, 0.2) ...       va, la = vertex_separation.vertex_separation_MILP(G, integrality = False) ...       vb, lb = vertex_separation.vertex_separation_MILP(G, integrality = True) ...       if va != vb: ...          print "The integrality parameter change the result!" Giving anything else than a DiGraph:: sage: from sage.graphs.graph_decompositions import vertex_separation sage: vertex_separation.vertex_separation_MILP([]) Traceback (most recent call last): ... ValueError: The first input parameter must be a DiGraph. """ from sage.graphs.digraph import DiGraph if not isinstance(G, DiGraph): raise ValueError("The first input parameter must be a DiGraph.") from sage.numerical.mip import MixedIntegerLinearProgram, Sum, MIPSolverException p = MixedIntegerLinearProgram( maximization = False, solver = solver ) # Declaration of variables. x = p.new_variable( binary = integrality, dim = 2 ) u = p.new_variable( binary = integrality, dim = 2 ) y = p.new_variable( binary = True, dim = 2 ) z = p.new_variable( integer = True, dim = 1 ) N = G.num_verts() V = G.vertices() # (2) x[v][t] <= x[v][t+1]   for all v in V, and for t:=0..N-2 # (3) y[v][t] <= y[v][t+1]   for all v in V, and for t:=0..N-2 for v in V: for t in xrange(N-1): p.add_constraint( x[v][t] - x[v][t+1] <= 0 ) p.add_constraint( y[v][t] - y[v][t+1] <= 0 ) # (4) y[v][t] <= x[w][t]  for all v in V, for all w in N^+(v), and for all t:=0..N-1 for v in V: for w in G.neighbors_out(v): for t in xrange(N): p.add_constraint( y[v][t] - x[w][t] <= 0 ) # (5) sum_{v in V} y[v][t] == t+1 for t:=0..N-1 for t in xrange(N): p.add_constraint( Sum([ y[v][t] for v in V ]) == t+1 ) # (6) u[v][t] >= x[v][t]-y[v][t]    for all v in V, and for all t:=0..N-1 for v in V: for t in xrange(N): p.add_constraint( x[v][t] - y[v][t] - u[v][t] <= 0 ) # (7) z >= sum_{v in V} u[v][t]   for all t:=0..N-1 for t in xrange(N): p.add_constraint( Sum([ u[v][t] for v in V ]) - z['z'] <= 0 ) # (8)(9) 0 <= x[v][t] and u[v][t] <= 1 if not integrality: for v in V: for t in xrange(N): p.add_constraint( 0 <= x[v][t] <= 1 ) p.add_constraint( 0 <= u[v][t] <= 1 ) # (10) y[v][t] in {0,1} p.set_binary( y ) # (11) 0 <= z <= |V| p.add_constraint( z['z'] <= N ) #  (1) Minimize z p.set_objective( z['z'] ) try: obj = p.solve( log=verbosity ) except MIPSolverException: if integrality: raise ValueError("Unbounded or unexpected error") else: raise ValueError("Unbounded or unexpected error. Try with 'integrality = True'.") taby = p.get_values( y ) tabz = p.get_values( z ) # since exactly one vertex is processed per step, we can reconstruct the sequence seq = [] for t in xrange(N): for v in V: if (taby[v][t] > 0) and (not v in seq): seq.append(v) break vs = int(round( tabz['z'] )) return vs, seq