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:
Where
The vertex separation of a digraph \(G\) is equal to the minimum cost of an ordering of its vertices.
Vertex separation and pathwidth
The vertex separation is defined on a digraph, but one can obtain from a graph \(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\), also called a layout, encodes an optimal path-decomposition of \(G\). This is a result of Kinnersley [Kin92] and Bodlaender [Bod98].
This module contains the following methods
path_decomposition() | Returns the pathwidth of the given graph and the ordering of the vertices resulting in a corresponding path decomposition |
vertex_separation() | Returns an optimal ordering of the vertices and its cost for vertex-separation |
vertex_separation_MILP() | Computes the vertex separation of \(G\) and the optimal ordering of its vertices using an MILP formulation |
vertex_separation_BAB() | Computes the vertex separation of \(G\) and the optimal ordering of its vertices using a branch and bound algorithm |
lower_bound() | Returns a lower bound on the vertex separation of \(G\) |
is_valid_ordering() | Test if the linear vertex ordering \(L\) is valid for (di)graph \(G\) |
width_of_path_decomposition() | Returns the width of the path decomposition induced by the linear ordering \(L\) of the vertices of \(G\) |
In order to find an optimal ordering of the vertices for the vertex separation, this algorithm tries to save time by computing the function \(c'(S)\) at most once once for each of the sets \(S\subseteq V(G)\). These values are stored in an array of size \(2^n\) where reading the value of \(c'(S)\) or updating it can be done in constant (and small) time.
Assuming that we can compute the cost of a set \(S\) and remember it, finding an optimal ordering is an easy task. Indeed, we can think of the sequence \(v_1, ..., v_n\) of vertices as a sequence of sets \(\{v_1\}, \{v_1,v_2\}, ..., \{v_1,...,v_n\}\), whose cost is precisely \(\max c'(\{v_1\}), c'(\{v_1,v_2\}), ... , c'(\{v_1,...,v_n\})\). Hence, when considering the digraph on the \(2^n\) sets \(S\subseteq V(G)\) where there is an arc from \(S\) to \(S'\) if \(S'=S\cap \{v\}\) for some \(v\) (that is, if the sets \(S\) and \(S'\) can be consecutive in a sequence), an ordering of the vertices of \(G\) corresponds to a path from \(\emptyset\) to \(\{v_1,...,v_n\}\). In this setting, checking whether there exists a ordering of cost less than \(k\) can be achieved by checking whether there exists a directed path \(\emptyset\) to \(\{v_1,...,v_n\}\) using only sets of cost less than \(k\). This is just a depth-first-search, for each \(k\).
Lazy evaluation of \(c'\)
In the previous algorithm, most of the time is actually spent on the computation of \(c'(S)\) for each set \(S\subseteq V(G)\) – i.e. \(2^n\) computations of neighborhoods. This can be seen as a huge waste of time when noticing that it is useless to know that the value \(c'(S)\) for a set \(S\) is less than \(k\) if all the paths leading to \(S\) have a cost greater than \(k\). For this reason, the value of \(c'(S)\) is computed lazily during the depth-first search. Explanation :
When the depth-first search discovers a set of size less than \(k\), the costs of its out-neighbors (the potential sets that could follow it in the optimal ordering) are evaluated. When an out-neighbor is found that has a cost smaller than \(k\), the depth-first search continues with this set, which is explored with the hope that it could lead to a path toward \(\{v_1,...,v_n\}\). On the other hand, if an out-neighbour has a cost larger than \(k\) it is useless to attempt to build a cheap sequence going though this set, and the exploration stops there. This way, a large number of sets will never be evaluated and a lot of computational time is saved this way.
Besides, some improvement is also made by “improving” the values found by \(c'\). Indeed, \(c'(S)\) is a lower bound on the cost of a sequence containing the set \(S\), but if all out-neighbors of \(S\) have a cost of \(c'(S) + 5\) then one knows that having \(S\) in a sequence means a total cost of at least \(c'(S) + 5\). For this reason, for each set \(S\) we store the value of \(c'(S)\), and replace it by \(\max (c'(S), \min_{\text{next}})\) (where \(\min_{\text{next}}\) is the minimum of the costs of the out-neighbors of \(S\)) once the costs of these out-neighbors have been evaluated by the algrithm.
Note
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. 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
One can obtain a lower bound on the vertex separation of a graph in exponential time but small memory by computing once the cost of each set \(S\). Indeed, the cost of a sequence \(v_1, ..., v_n\) corresponding to sets \(\{v_1\}, \{v_1,v_2\}, ..., \{v_1,...,v_n\}\) is
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.
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:
MILP formulation:
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\).
We describe below the principle of a branch and bound algorithm (BAB) for determining an optimal ordering for the vertex separation of \(G\), as proposed in [CMN14].
Greedy steps:
Let us denote \({\cal L}(S)\) the set of all possible orderings of the vertices in \(S\), and let \({\cal L}_P(S)\subseteq {\cal L}(S)\) be the orderings starting with a prefix \(P\). Let also \(c(L)\) be the cost of the ordering \(L\in{\cal L}(V)\) as defined above.
Given a digraph \(D=(V,A)\), a set \(S\subset V\), and a prefix \(P\), it has been proved in [CMN14] that \(\min_{L\in{\cal L}_P(V)} c(L) = \min_{L\in{\cal L}_{P+v}(V)} c(L)\) holds in two (non exhaustive) cases:
In other words, if we find a vertex \(v\) satisfying above conditions, the best possible ordering with prefix \(P\) has the same cost as the best possible ordering with prefix \(P+v\). So we can greedily extend the prefix with vertices satisfying the conditions which results in a significant reduction of the search space.
The algorithm:
Given the current prefix \(P\) and the current upper bound \(UB\) (either an input upper bound or the cost of the best solution found so far), apply the following steps:
If a lower bound is passed to the algorithm, it will stop as soon as a solution with cost equal to that lower bound is found.
[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] | (1, 2) Lightpath Reconfiguration in WDM networks, Fernando Solano and Michal Pioro, IEEE/OSA Journal of Optical Communication and Networking 2(12):1010-1021, 2010. |
[CMN14] | (1, 2, 3) Experimental Evaluation of a Branch and Bound Algorithm for computing Pathwidth, David Coudert, Dorian Mazauric, and Nicolas Nisse. In Symposium on Experimental Algorithms (SEA), volume 8504 of LNCS, Copenhagen, Denmark, pages 46-58, June 2014, http://hal.inria.fr/hal-00943549/document |
Bases: object
x.__init__(...) initializes x; see help(type(x)) for signature
Displays the adjacency matrix of self.
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:
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'.
Returns a lower bound on the vertex separation of \(G\)
INPUT:
OUTPUT:
A lower bound on the vertex separation of \(D\) (see the module’s documentation).
Note
This method runs in exponential time but has no memory constraint.
EXAMPLE:
On a circuit:
sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound
sage: g = digraphs.Circuit(6)
sage: lower_bound(g)
1
TEST:
Given anything else than a DiGraph:
sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound
sage: g = graphs.CycleGraph(5)
sage: lower_bound(g)
Traceback (most recent call last):
...
ValueError: The parameter must be a DiGraph.
Returns the pathwidth of the given graph and the ordering of the vertices resulting in a corresponding path decomposition.
INPUT:
OUTPUT:
A pair (cost, ordering) representing the optimal ordering of the vertices and its cost.
Note
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.
See also
EXAMPLE:
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: pw, L = path_decomposition(g); pw
2
sage: pwm, Lm = path_decomposition(g, algorithm = "MILP"); pwm
2
TEST:
Given anything else than a Graph:
sage: from sage.graphs.graph_decompositions.vertex_separation import path_decomposition
sage: g = digraphs.Circuit(6)
sage: path_decomposition(g)
Traceback (most recent call last):
...
ValueError: The parameter must be a Graph.
Correction test for popcount32.
EXAMPLE:
sage: from sage.graphs.graph_decompositions.vertex_separation import test_popcount
sage: test_popcount() # not tested
Returns an optimal ordering of the vertices and its cost for vertex-separation.
INPUT:
OUTPUT:
A pair (cost, ordering) representing the optimal ordering of the vertices and its cost.
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.
EXAMPLE:
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])
TEST:
Given anything else than a DiGraph:
sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound
sage: g = graphs.CycleGraph(5)
sage: lower_bound(g)
Traceback (most recent call last):
...
ValueError: The parameter must be a DiGraph.
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'])
Branch and Bound algorithm for the vertex separation.
This method implements the branch and bound algorithm for the vertex separation of directed graphs and the pathwidth of undirected graphs proposed in [CMN14]. The implementation is valid for both Graph and DiGraph. See the documentation of the vertex_separation module.
INPUTS:
OUTPUTS:
EXAMPLES:
The algorithm is valid for the vertex separation:
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
sage: D = digraphs.RandomDirectedGNP(15, .2)
sage: vb, seqb = VS.vertex_separation_BAB(D)
sage: vd, seqd = VS.vertex_separation(D)
sage: vb == vd
True
sage: vb == VS.width_of_path_decomposition(D, seqb)
True
The vertex separation of a \(N\times N\) grid is \(N\):
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
sage: G = graphs.Grid2dGraph(4,4)
sage: vs, seq = VS.vertex_separation_BAB(G); vs
4
sage: vs == VS.width_of_path_decomposition(G, seq)
True
The vertex separation of a \(N\times M\) grid with \(N<M\) is \(N\):
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
sage: G = graphs.Grid2dGraph(3,5)
sage: vs, seq = VS.vertex_separation_BAB(G); vs
3
sage: vs == VS.width_of_path_decomposition(G, seq)
True
The vertex separation of circuit of order \(N\geq 2\) is 1:
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
sage: D = digraphs.Circuit(10)
sage: vs, seq = VS.vertex_separation_BAB(D); vs
1
sage: vs == VS.width_of_path_decomposition(D, seq)
True
The vertex separation of cycle of order \(N\geq 3\) is 2:
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
sage: G = graphs.CycleGraph(10)
sage: vs, seq = VS.vertex_separation_BAB(G); vs
2
The vertex separation of MycielskiGraph(5) is 10:
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
sage: G = graphs.MycielskiGraph(5)
sage: vs, seq = VS.vertex_separation_BAB(G); vs
10
Searching for any solution with width less or equal to cut_off:
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
sage: G = graphs.MycielskiGraph(5)
sage: vs, seq = VS.vertex_separation_BAB(G, cut_off=11); vs
11
sage: vs, seq = VS.vertex_separation_BAB(G, cut_off=10); vs
10
sage: vs, seq = VS.vertex_separation_BAB(G, cut_off=9); vs
10
Testing for the existence of a solution with width strictly less than upper_bound:
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
sage: G = graphs.MycielskiGraph(5)
sage: vs, seq = VS.vertex_separation_BAB(G, upper_bound=11); vs
10
sage: vs, seq = VS.vertex_separation_BAB(G, upper_bound=10); vs
-1
sage: vs, seq = VS.vertex_separation_BAB(G, cut_off=11, upper_bound=10); vs
-1
TESTS:
Giving anything else than a Graph or a DiGraph:
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
sage: VS.vertex_separation_BAB(range(5))
Traceback (most recent call last):
...
ValueError: The input parameter must be a Graph or a DiGraph.
Giving an empty Graph or DiGraph:
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
sage: VS.vertex_separation_BAB(Graph())
(0, [])
sage: VS.vertex_separation_BAB(DiGraph())
(0, [])
Giving a too low upper bound:
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
sage: VS.vertex_separation_BAB(digraphs.Circuit(3), upper_bound=0)
Traceback (most recent call last):
...
ValueError: The input upper bound must be at least 1.
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 module's documentation for more details on this MILP formulation.
INPUTS:
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): # long time (11s on sage.math, 2012)
....: 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 changes 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.
Returns the width of the path decomposition induced by the linear ordering \(L\) of the vertices of \(G\).
If \(G\) is an instance of 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 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
INPUT:
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.