Cutwidth#

This module implements several algorithms to compute the cutwidth of a graph and the corresponding ordering of the vertices. It also implements tests functions for evaluation the width of a linear ordering (or layout).

Given an ordering \(v_1,\cdots, v_n\) of the vertices of \(V(G)\), its cost is defined as:

\[c(v_1, ..., v_n) = \max_{1\leq i \leq n-1} c'(\{v_1, ..., v_i\})\]

Where

\[c'(S) = |\{(u,w)\in E(G)\mid u\in S\text{ and }w\in V(G)\backslash S\}|\]

The cutwidth of a graph \(G\) is equal to the minimum cost of an ordering of its vertices.

This module contains the following methods

cutwidth()

Return the cutwidth of the graph and the corresponding vertex ordering.

cutwidth_dyn()

Compute the cutwidth of \(G\) using an exponential time and space algorithm based on dynamic programming

cutwidth_MILP()

Compute the cutwidth of \(G\) and the optimal ordering of its vertices using an MILP formulation

width_of_cut_decomposition()

Return the width of the cut decomposition induced by the linear ordering \(L\) of the vertices of \(G\)

Exponential algorithm for cutwidth#

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 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 algorithm.

This algorithm and its implementation are very similar to sage.graphs.graph_decompositions.vertex_separation.vertex_separation_exp(). The main difference is in the computation of \(c'(S)\). See the vertex separation module's documentation for more details on this algorithm.

Note

Because of its current implementation, this algorithm only works on graphs on strictly less than 32 vertices. This can be changed to 64 if necessary, but 32 vertices already require 4GB of memory.

MILP formulation for the cutwidth#

We describe a mixed integer linear program (MILP) for determining an optimal layout for the cutwidth of \(G\).

Variables:

  • \(x_v^k\) – Variable set to 1 if vertex \(v\) is placed in the ordering at position \(i\) with \(i\leq k\), and 0 otherwise.

  • \(y_{u,v}^{k}\) – Variable set to 1 if one of \(u\) or \(v\) is at a position \(i\leq k\) and the other is at a position \(j>k\), and so we have to count edge \(uv\) at position \(k\). Otherwise, \(y_{u,v}^{k}=0\). The value of \(y_{u,v}^{k}\) is a xor of the values of \(x_u^k\) and \(x_v^k\).

  • \(z\) – Objective value to minimize. It is equal to the maximum over all position \(k\) of the number of edges with one extremity at position at most \(k\) and the other at position strictly more than \(k\), that is \(\sum_{uv\in E}y_{u,v}^{k}\).

MILP formulation:

\begin{alignat*}{2} \intertext{Minimize:} &z&\\ \intertext{Subject to:} \sum_{i=0}^{k-1}x_v^i &\leq k*x_v^{k} & \forall v\in V,\ k\in[1,n-1] \quad(1)\\ x_v^n & =1 & \quad \forall v\in V \quad(2)\\ \sum_{v\in V}x_v^k & = k+1 &\quad \forall k\in[0,n-1] \quad(3)\\ x_u^k - x_v^k & \leq y_{u,v}^k &\quad \forall uv\in E,\ \forall k\in[0,n-1] \quad(4)\\ x_v^k - x_u^k & \leq y_{u,v}^k &\quad \forall uv\in E,\ \forall k\in[0,n-1] \quad(5)\\ \sum_{uv\in E}y_{u,v}^k &\leq z &\quad \forall k\in[0,n-1] \quad(6)\\ 0 \leq z &\leq |E| \end{alignat*}

Constraints (1)-(3) ensure that all vertices have a distinct position. Constraints (4)-(5) force variable \(y_{u,v}^k\) to 1 if the edge is in the cut. Constraint (6) count the number of edges starting at position at most \(k\) and ending at a position strictly larger than \(k\).

This formulation corresponds to method cutwidth_MILP().

Authors#

  • David Coudert (2015-06): Initial version

Methods#

sage.graphs.graph_decompositions.cutwidth.cutwidth(G, algorithm='exponential', cut_off=0, solver=None, verbose=False, integrality_tolerance=0.001)[source]#

Return the cutwidth of the graph and the corresponding vertex ordering.

INPUT:

  • G – a Graph or a DiGraph

  • algorithm – string (default: "exponential"); algorithm to use among:

    • exponential – Use an exponential time and space algorithm based on dynamic programming. This algorithm only works on graphs with strictly 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.

  • cut_off – integer (default: 0); used to stop the search as soon as a solution with width at most cut_off is found, if any. If this bound cannot be reached, the best solution found is returned.

  • solver – string (default: None); specify a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – boolean (default: False); whether to display information on the computations.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT:

A pair (cost, ordering) representing the optimal ordering of the vertices and its cost.

EXAMPLES:

Cutwidth of a Complete Graph:

sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth
sage: G = graphs.CompleteGraph(5)
sage: cw,L = cutwidth(G); cw
6
sage: K = graphs.CompleteGraph(6)
sage: cw,L = cutwidth(K); cw
9
sage: cw,L = cutwidth(K+K); cw
9
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions.cutwidth import cutwidth
>>> G = graphs.CompleteGraph(Integer(5))
>>> cw,L = cutwidth(G); cw
6
>>> K = graphs.CompleteGraph(Integer(6))
>>> cw,L = cutwidth(K); cw
9
>>> cw,L = cutwidth(K+K); cw
9

The cutwidth of a \(p\times q\) Grid Graph with \(p\leq q\) is \(p+1\):

sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth
sage: G = graphs.Grid2dGraph(3,3)
sage: cw,L = cutwidth(G); cw
4
sage: G = graphs.Grid2dGraph(3,5)
sage: cw,L = cutwidth(G); cw
4
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions.cutwidth import cutwidth
>>> G = graphs.Grid2dGraph(Integer(3),Integer(3))
>>> cw,L = cutwidth(G); cw
4
>>> G = graphs.Grid2dGraph(Integer(3),Integer(5))
>>> cw,L = cutwidth(G); cw
4
sage.graphs.graph_decompositions.cutwidth.cutwidth_MILP(G, lower_bound=0, solver=None, verbose=0, integrality_tolerance=0.001)[source]#

MILP formulation for the cutwidth of a Graph.

This method uses a mixed integer linear program (MILP) for determining an optimal layout for the cutwidth of \(G\). See the module's documentation for more details on this MILP formulation.

INPUT:

  • G – a Graph

  • lower_bound – integer (default: 0); the algorithm searches for a solution with cost larger or equal to lower_bound. If the given bound is larger than the optimal solution the returned solution might not be optimal. If the given bound is too high, the algorithm might not be able to find a feasible solution.

  • solver – string (default: None); specify a Mixed Integer Linear Programming (MILP) solver to be used. If set to None, the default one is used. For more information on MILP solvers and which default solver is used, see the method solve of the class MixedIntegerLinearProgram.

  • verbose – integer (default: 0); sets the level of verbosity. Set to 0 by default, which means quiet.

  • integrality_tolerance – float; parameter for use with MILP solvers over an inexact base ring; see MixedIntegerLinearProgram.get_values().

OUTPUT:

A pair (cost, ordering) representing the optimal ordering of the vertices and its cost.

EXAMPLES:

Cutwidth of a Cycle graph:

sage: from sage.graphs.graph_decompositions import cutwidth
sage: G = graphs.CycleGraph(5)
sage: cw, L = cutwidth.cutwidth_MILP(G); cw                                     # needs sage.numerical.mip
2
sage: cw == cutwidth.width_of_cut_decomposition(G, L)                           # needs sage.numerical.mip
True
sage: cwe, Le = cutwidth.cutwidth_dyn(G); cwe
2
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions import cutwidth
>>> G = graphs.CycleGraph(Integer(5))
>>> cw, L = cutwidth.cutwidth_MILP(G); cw                                     # needs sage.numerical.mip
2
>>> cw == cutwidth.width_of_cut_decomposition(G, L)                           # needs sage.numerical.mip
True
>>> cwe, Le = cutwidth.cutwidth_dyn(G); cwe
2

Cutwidth of a Complete graph:

sage: from sage.graphs.graph_decompositions import cutwidth
sage: G = graphs.CompleteGraph(4)
sage: cw, L = cutwidth.cutwidth_MILP(G); cw                                     # needs sage.numerical.mip
4
sage: cw == cutwidth.width_of_cut_decomposition(G, L)                           # needs sage.numerical.mip
True
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions import cutwidth
>>> G = graphs.CompleteGraph(Integer(4))
>>> cw, L = cutwidth.cutwidth_MILP(G); cw                                     # needs sage.numerical.mip
4
>>> cw == cutwidth.width_of_cut_decomposition(G, L)                           # needs sage.numerical.mip
True

Cutwidth of a Path graph:

sage: from sage.graphs.graph_decompositions import cutwidth
sage: G = graphs.PathGraph(3)
sage: cw, L = cutwidth.cutwidth_MILP(G); cw                                     # needs sage.numerical.mip
1
sage: cw == cutwidth.width_of_cut_decomposition(G, L)                           # needs sage.numerical.mip
True
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions import cutwidth
>>> G = graphs.PathGraph(Integer(3))
>>> cw, L = cutwidth.cutwidth_MILP(G); cw                                     # needs sage.numerical.mip
1
>>> cw == cutwidth.width_of_cut_decomposition(G, L)                           # needs sage.numerical.mip
True
sage.graphs.graph_decompositions.cutwidth.cutwidth_dyn(G, lower_bound=0)[source]#

Dynamic programming algorithm for the cutwidth of a Graph.

This function uses dynamic programming algorithm for determining an optimal layout for the cutwidth of \(G\). See the module's documentation for more details on this method.

INPUT:

  • G – a Graph

  • lower_bound – integer (default: 0); the algorithm returns immediately if it finds a solution lower or equal to lower_bound (in which case it may not be optimal).

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 strictly less than 32 vertices. This can be changed to 63 if necessary, but 32 vertices already require 4GB of memory.

sage.graphs.graph_decompositions.cutwidth.width_of_cut_decomposition(G, L)[source]#

Return the width of the cut decomposition induced by the linear ordering \(L\) of the vertices of \(G\).

If \(G\) is an instance of Graph, this function returns the width \(cw_L(G)\) of the cut decomposition induced by the linear ordering \(L\) of the vertices of \(G\).

\[cw_L(G) = \max_{0\leq i< |V|-1} |\{(u,w)\in E(G)\mid u\in L[:i]\text{ and }w\in V(G)\setminus L[:i]\}|\]

INPUT:

  • G – a Graph

  • L – a linear ordering of the vertices of G

EXAMPLES:

Cut decomposition of a Cycle graph:

sage: from sage.graphs.graph_decompositions import cutwidth
sage: G = graphs.CycleGraph(6)
sage: L = G.vertices(sort=False)
sage: cutwidth.width_of_cut_decomposition(G, L)
2
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions import cutwidth
>>> G = graphs.CycleGraph(Integer(6))
>>> L = G.vertices(sort=False)
>>> cutwidth.width_of_cut_decomposition(G, L)
2

Cut decomposition of a Path graph:

sage: from sage.graphs.graph_decompositions import cutwidth
sage: P = graphs.PathGraph(6)
sage: cutwidth.width_of_cut_decomposition(P, [0, 1, 2, 3, 4, 5])
1
sage: cutwidth.width_of_cut_decomposition(P, [5, 0, 1, 2, 3, 4])
2
sage: cutwidth.width_of_cut_decomposition(P, [0, 2, 4, 1, 3, 5])
5
>>> from sage.all import *
>>> from sage.graphs.graph_decompositions import cutwidth
>>> P = graphs.PathGraph(Integer(6))
>>> cutwidth.width_of_cut_decomposition(P, [Integer(0), Integer(1), Integer(2), Integer(3), Integer(4), Integer(5)])
1
>>> cutwidth.width_of_cut_decomposition(P, [Integer(5), Integer(0), Integer(1), Integer(2), Integer(3), Integer(4)])
2
>>> cutwidth.width_of_cut_decomposition(P, [Integer(0), Integer(2), Integer(4), Integer(1), Integer(3), Integer(5)])
5