passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
- passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
- passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
- passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
- passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
- passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
- passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
- sage/all__sagemath_graphs.py +39 -0
- sage/combinat/abstract_tree.py +2723 -0
- sage/combinat/all__sagemath_graphs.py +34 -0
- sage/combinat/binary_tree.py +5306 -0
- sage/combinat/cluster_algebra_quiver/all.py +22 -0
- sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
- sage/combinat/cluster_algebra_quiver/interact.py +124 -0
- sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
- sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
- sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
- sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
- sage/combinat/designs/MOLS_handbook_data.py +570 -0
- sage/combinat/designs/all.py +58 -0
- sage/combinat/designs/bibd.py +1655 -0
- sage/combinat/designs/block_design.py +1071 -0
- sage/combinat/designs/covering_array.py +269 -0
- sage/combinat/designs/covering_design.py +530 -0
- sage/combinat/designs/database.py +5615 -0
- sage/combinat/designs/design_catalog.py +122 -0
- sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/designs_pyx.pxd +21 -0
- sage/combinat/designs/designs_pyx.pyx +993 -0
- sage/combinat/designs/difference_family.py +3951 -0
- sage/combinat/designs/difference_matrices.py +279 -0
- sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
- sage/combinat/designs/ext_rep.py +1064 -0
- sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
- sage/combinat/designs/group_divisible_designs.py +361 -0
- sage/combinat/designs/incidence_structures.py +2357 -0
- sage/combinat/designs/latin_squares.py +581 -0
- sage/combinat/designs/orthogonal_arrays.py +2244 -0
- sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
- sage/combinat/designs/resolvable_bibd.py +815 -0
- sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
- sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/subhypergraph_search.pyx +530 -0
- sage/combinat/designs/twographs.py +306 -0
- sage/combinat/finite_state_machine.py +14874 -0
- sage/combinat/finite_state_machine_generators.py +2006 -0
- sage/combinat/graph_path.py +448 -0
- sage/combinat/interval_posets.py +3908 -0
- sage/combinat/nu_tamari_lattice.py +269 -0
- sage/combinat/ordered_tree.py +1446 -0
- sage/combinat/posets/all.py +46 -0
- sage/combinat/posets/bubble_shuffle.py +247 -0
- sage/combinat/posets/cartesian_product.py +493 -0
- sage/combinat/posets/d_complete.py +182 -0
- sage/combinat/posets/elements.py +273 -0
- sage/combinat/posets/forest.py +30 -0
- sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +174 -0
- sage/combinat/posets/hasse_diagram.py +3672 -0
- sage/combinat/posets/hochschild_lattice.py +158 -0
- sage/combinat/posets/incidence_algebras.py +794 -0
- sage/combinat/posets/lattices.py +5117 -0
- sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/linear_extension_iterator.pyx +292 -0
- sage/combinat/posets/linear_extensions.py +1037 -0
- sage/combinat/posets/mobile.py +275 -0
- sage/combinat/posets/moebius_algebra.py +776 -0
- sage/combinat/posets/poset_examples.py +2178 -0
- sage/combinat/posets/posets.py +9360 -0
- sage/combinat/rooted_tree.py +1070 -0
- sage/combinat/shard_order.py +239 -0
- sage/combinat/tamari_lattices.py +384 -0
- sage/combinat/yang_baxter_graph.py +923 -0
- sage/databases/all__sagemath_graphs.py +1 -0
- sage/databases/knotinfo_db.py +1231 -0
- sage/ext_data/all__sagemath_graphs.py +1 -0
- sage/ext_data/graphs/graph_plot_js.html +330 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/graphs/all.py +42 -0
- sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/asteroidal_triples.pyx +320 -0
- sage/graphs/base/all.py +1 -0
- sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/boost_graph.pxd +106 -0
- sage/graphs/base/boost_graph.pyx +3045 -0
- sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/c_graph.pxd +106 -0
- sage/graphs/base/c_graph.pyx +5096 -0
- sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/dense_graph.pxd +28 -0
- sage/graphs/base/dense_graph.pyx +801 -0
- sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/graph_backends.pxd +5 -0
- sage/graphs/base/graph_backends.pyx +797 -0
- sage/graphs/base/overview.py +85 -0
- sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/sparse_graph.pxd +90 -0
- sage/graphs/base/sparse_graph.pyx +1653 -0
- sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_dense_graph.pxd +5 -0
- sage/graphs/base/static_dense_graph.pyx +1032 -0
- sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_backend.pxd +27 -0
- sage/graphs/base/static_sparse_backend.pyx +1583 -0
- sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_graph.pxd +37 -0
- sage/graphs/base/static_sparse_graph.pyx +1375 -0
- sage/graphs/bipartite_graph.py +2732 -0
- sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/centrality.pyx +1038 -0
- sage/graphs/cographs.py +519 -0
- sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/comparability.pyx +851 -0
- sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/connectivity.pxd +157 -0
- sage/graphs/connectivity.pyx +4813 -0
- sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/convexity_properties.pxd +16 -0
- sage/graphs/convexity_properties.pyx +870 -0
- sage/graphs/digraph.py +4754 -0
- sage/graphs/digraph_generators.py +1993 -0
- sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/distances_all_pairs.pxd +12 -0
- sage/graphs/distances_all_pairs.pyx +2938 -0
- sage/graphs/domination.py +1363 -0
- sage/graphs/dot2tex_utils.py +100 -0
- sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/edge_connectivity.pyx +1215 -0
- sage/graphs/generators/all.py +1 -0
- sage/graphs/generators/basic.py +1769 -0
- sage/graphs/generators/chessboard.py +538 -0
- sage/graphs/generators/classical_geometries.py +1611 -0
- sage/graphs/generators/degree_sequence.py +235 -0
- sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generators/distance_regular.pyx +2846 -0
- sage/graphs/generators/families.py +4759 -0
- sage/graphs/generators/intersection.py +565 -0
- sage/graphs/generators/platonic_solids.py +262 -0
- sage/graphs/generators/random.py +2623 -0
- sage/graphs/generators/smallgraphs.py +5741 -0
- sage/graphs/generators/world_map.py +724 -0
- sage/graphs/generic_graph.py +26867 -0
- sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generic_graph_pyx.pxd +34 -0
- sage/graphs/generic_graph_pyx.pyx +1673 -0
- sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/genus.pyx +622 -0
- sage/graphs/graph.py +9645 -0
- sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_coloring.pyx +2284 -0
- sage/graphs/graph_database.py +1177 -0
- sage/graphs/graph_decompositions/all.py +1 -0
- sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
- sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
- sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
- sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +508 -0
- sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
- sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
- sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
- sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
- sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
- sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
- sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
- sage/graphs/graph_editor.py +82 -0
- sage/graphs/graph_generators.py +3314 -0
- sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_generators_pyx.pyx +95 -0
- sage/graphs/graph_input.py +812 -0
- sage/graphs/graph_latex.py +2064 -0
- sage/graphs/graph_list.py +410 -0
- sage/graphs/graph_plot.py +1756 -0
- sage/graphs/graph_plot_js.py +338 -0
- sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/hyperbolicity.pyx +1704 -0
- sage/graphs/hypergraph_generators.py +364 -0
- sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/independent_sets.pxd +13 -0
- sage/graphs/independent_sets.pyx +402 -0
- sage/graphs/isgci.py +1033 -0
- sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +489 -0
- sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/line_graph.pyx +743 -0
- sage/graphs/lovasz_theta.py +77 -0
- sage/graphs/matching.py +1633 -0
- sage/graphs/matching_covered_graph.py +3590 -0
- sage/graphs/orientations.py +1489 -0
- sage/graphs/partial_cube.py +459 -0
- sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/path_enumeration.pyx +2040 -0
- sage/graphs/pq_trees.py +1129 -0
- sage/graphs/print_graphs.py +201 -0
- sage/graphs/schnyder.py +865 -0
- sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/spanning_tree.pyx +1457 -0
- sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/strongly_regular_db.pyx +3340 -0
- sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/traversals.pxd +9 -0
- sage/graphs/traversals.pyx +1872 -0
- sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/trees.pxd +15 -0
- sage/graphs/trees.pyx +310 -0
- sage/graphs/tutte_polynomial.py +713 -0
- sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/views.pyx +794 -0
- sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/weakly_chordal.pyx +604 -0
- sage/groups/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
- sage/knots/all.py +6 -0
- sage/knots/free_knotinfo_monoid.py +507 -0
- sage/knots/gauss_code.py +291 -0
- sage/knots/knot.py +682 -0
- sage/knots/knot_table.py +284 -0
- sage/knots/knotinfo.py +2900 -0
- sage/knots/link.py +4715 -0
- sage/sandpiles/all.py +13 -0
- sage/sandpiles/examples.py +225 -0
- sage/sandpiles/sandpile.py +6365 -0
- sage/topology/all.py +22 -0
- sage/topology/cell_complex.py +1214 -0
- sage/topology/cubical_complex.py +1976 -0
- sage/topology/delta_complex.py +1806 -0
- sage/topology/filtered_simplicial_complex.py +744 -0
- sage/topology/moment_angle_complex.py +823 -0
- sage/topology/simplicial_complex.py +5160 -0
- sage/topology/simplicial_complex_catalog.py +92 -0
- sage/topology/simplicial_complex_examples.py +1680 -0
- sage/topology/simplicial_complex_homset.py +205 -0
- sage/topology/simplicial_complex_morphism.py +836 -0
- sage/topology/simplicial_set.py +4102 -0
- sage/topology/simplicial_set_catalog.py +55 -0
- sage/topology/simplicial_set_constructions.py +2954 -0
- sage/topology/simplicial_set_examples.py +865 -0
- sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,753 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
# cython: binding=True
|
3
|
+
r"""
|
4
|
+
Cutwidth
|
5
|
+
|
6
|
+
This module implements several algorithms to compute the cutwidth of a graph and
|
7
|
+
the corresponding ordering of the vertices. It also implements tests functions
|
8
|
+
for evaluation the width of a linear ordering (or layout).
|
9
|
+
|
10
|
+
Given an ordering
|
11
|
+
`v_1,\cdots, v_n` of the vertices of `V(G)`, its *cost* is defined as:
|
12
|
+
|
13
|
+
.. MATH::
|
14
|
+
|
15
|
+
c(v_1, ..., v_n) = \max_{1\leq i \leq n-1} c'(\{v_1, ..., v_i\})
|
16
|
+
|
17
|
+
Where
|
18
|
+
|
19
|
+
.. MATH::
|
20
|
+
|
21
|
+
c'(S) = |\{(u,w)\in E(G)\mid u\in S\text{ and }w\in V(G)\backslash S\}|
|
22
|
+
|
23
|
+
The *cutwidth* of a graph `G` is equal to the minimum cost of an ordering of its
|
24
|
+
vertices.
|
25
|
+
|
26
|
+
|
27
|
+
**This module contains the following methods**
|
28
|
+
|
29
|
+
.. csv-table::
|
30
|
+
:class: contentstable
|
31
|
+
:widths: 30, 70
|
32
|
+
:delim: |
|
33
|
+
|
34
|
+
:meth:`cutwidth` | Return the cutwidth of the graph and the corresponding vertex ordering.
|
35
|
+
:meth:`cutwidth_dyn` | Compute the cutwidth of `G` using an exponential time and space algorithm based on dynamic programming
|
36
|
+
:meth:`cutwidth_MILP` | Compute the cutwidth of `G` and the optimal ordering of its vertices using an MILP formulation
|
37
|
+
:meth:`width_of_cut_decomposition` | Return the width of the cut decomposition induced by the linear ordering `L` of the vertices of `G`
|
38
|
+
|
39
|
+
|
40
|
+
Exponential algorithm for cutwidth
|
41
|
+
----------------------------------
|
42
|
+
|
43
|
+
In order to find an optimal ordering of the vertices for the vertex separation,
|
44
|
+
this algorithm tries to save time by computing the function `c'(S)` **at most
|
45
|
+
once** once for each of the sets `S\subseteq V(G)`. These values are stored in
|
46
|
+
an array of size `2^n` where reading the value of `c'(S)` or updating it can be
|
47
|
+
done in constant time.
|
48
|
+
|
49
|
+
Assuming that we can compute the cost of a set `S` and remember it, finding an
|
50
|
+
optimal ordering is an easy task. Indeed, we can think of the sequence `v_1,
|
51
|
+
..., v_n` of vertices as a sequence of *sets* `\{v_1\}, \{v_1,v_2\}, ...,
|
52
|
+
\{v_1,...,v_n\}`, whose cost is precisely `\max c'(\{v_1\}), c'(\{v_1,v_2\}),
|
53
|
+
... , c'(\{v_1,...,v_n\})`. Hence, when considering the digraph on the `2^n`
|
54
|
+
sets `S\subseteq V(G)` where there is an arc from `S` to `S'` if `S'=S\cap
|
55
|
+
\{v\}` for some `v` (that is, if the sets `S` and `S'` can be consecutive in a
|
56
|
+
sequence), an ordering of the vertices of `G` corresponds to a *path* from
|
57
|
+
`\emptyset` to `\{v_1,...,v_n\}`. In this setting, checking whether there exists
|
58
|
+
a ordering of cost less than `k` can be achieved by checking whether there
|
59
|
+
exists a directed path `\emptyset` to `\{v_1,...,v_n\}` using only sets of cost
|
60
|
+
less than `k`. This is just a depth-first-search, for each `k`.
|
61
|
+
|
62
|
+
**Lazy evaluation of** `c'`
|
63
|
+
|
64
|
+
In the previous algorithm, most of the time is actually spent on the computation
|
65
|
+
of `c'(S)` for each set `S\subseteq V(G)` -- i.e. `2^n` computations of
|
66
|
+
neighborhoods. This can be seen as a huge waste of time when noticing that it is
|
67
|
+
useless to know that the value `c'(S)` for a set `S` is less than `k` if all the
|
68
|
+
paths leading to `S` have a cost greater than `k`. For this reason, the value of
|
69
|
+
`c'(S)` is computed lazily during the depth-first search. Explanation :
|
70
|
+
|
71
|
+
When the depth-first search discovers a set of size less than `k`, the costs of
|
72
|
+
its out-neighbors (the potential sets that could follow it in the optimal
|
73
|
+
ordering) are evaluated. When an out-neighbor is found that has a cost smaller
|
74
|
+
than `k`, the depth-first search continues with this set, which is explored with
|
75
|
+
the hope that it could lead to a path toward `\{v_1,...,v_n\}`. On the other
|
76
|
+
hand, if an out-neighbour has a cost larger than `k` it is useless to attempt to
|
77
|
+
build a cheap sequence going though this set, and the exploration stops
|
78
|
+
there. This way, a large number of sets will never be evaluated and *a lot* of
|
79
|
+
computational time is saved this way.
|
80
|
+
|
81
|
+
Besides, some improvement is also made by "improving" the values found by
|
82
|
+
`c'`. Indeed, `c'(S)` is a lower bound on the cost of a sequence containing the
|
83
|
+
set `S`, but if all out-neighbors of `S` have a cost of `c'(S) + 5` then one
|
84
|
+
knows that having `S` in a sequence means a total cost of at least `c'(S) +
|
85
|
+
5`. For this reason, for each set `S` we store the value of `c'(S)`, and replace
|
86
|
+
it by `\max (c'(S), \min_{\text{next}})` (where `\min_{\text{next}}` is the
|
87
|
+
minimum of the costs of the out-neighbors of `S`) once the costs of these
|
88
|
+
out-neighbors have been evaluated by the algorithm.
|
89
|
+
|
90
|
+
This algorithm and its implementation are very similar to
|
91
|
+
:meth:`sage.graphs.graph_decompositions.vertex_separation.vertex_separation_exp`.
|
92
|
+
The main difference is in the computation of `c'(S)`. See the :mod:`vertex
|
93
|
+
separation module's documentation
|
94
|
+
<sage.graphs.graph_decompositions.vertex_separation>` for more details on this
|
95
|
+
algorithm.
|
96
|
+
|
97
|
+
.. NOTE::
|
98
|
+
|
99
|
+
Because of its current implementation, this algorithm only works on graphs
|
100
|
+
on strictly less than 32 vertices. This can be changed to 64 if necessary,
|
101
|
+
but 32 vertices already require 4GB of memory.
|
102
|
+
|
103
|
+
|
104
|
+
MILP formulation for the cutwidth
|
105
|
+
---------------------------------
|
106
|
+
|
107
|
+
We describe a mixed integer linear program (MILP) for determining an
|
108
|
+
optimal layout for the cutwidth of `G`.
|
109
|
+
|
110
|
+
**Variables:**
|
111
|
+
|
112
|
+
- `x_v^k` -- variable set to 1 if vertex `v` is placed in the ordering at
|
113
|
+
position `i` with `i\leq k`, and 0 otherwise
|
114
|
+
|
115
|
+
- `y_{u,v}^{k}` -- variable set to 1 if one of `u` or `v` is at a position
|
116
|
+
`i\leq k` and the other is at a position `j>k`, and so we have to count edge
|
117
|
+
`uv` at position `k`. Otherwise, `y_{u,v}^{k}=0`. The value of `y_{u,v}^{k}`
|
118
|
+
is a xor of the values of `x_u^k` and `x_v^k`.
|
119
|
+
|
120
|
+
- `z` -- objective value to minimize. It is equal to the maximum over all
|
121
|
+
position `k` of the number of edges with one extremity at position at most `k`
|
122
|
+
and the other at position strictly more than `k`, that is `\sum_{uv\in
|
123
|
+
E}y_{u,v}^{k}`.
|
124
|
+
|
125
|
+
|
126
|
+
**MILP formulation:**
|
127
|
+
|
128
|
+
.. MATH::
|
129
|
+
:nowrap:
|
130
|
+
|
131
|
+
\begin{alignat*}{2}
|
132
|
+
\intertext{Minimize:}
|
133
|
+
&z&\\
|
134
|
+
\intertext{Subject to:}
|
135
|
+
\sum_{i=0}^{k-1}x_v^i &\leq k*x_v^{k} & \forall v\in V,\ k\in[1,n-1] \quad(1)\\
|
136
|
+
x_v^n & =1 & \quad \forall v\in V \quad(2)\\
|
137
|
+
\sum_{v\in V}x_v^k & = k+1 &\quad \forall k\in[0,n-1] \quad(3)\\
|
138
|
+
x_u^k - x_v^k & \leq y_{u,v}^k &\quad \forall uv\in E,\ \forall k\in[0,n-1] \quad(4)\\
|
139
|
+
x_v^k - x_u^k & \leq y_{u,v}^k &\quad \forall uv\in E,\ \forall k\in[0,n-1] \quad(5)\\
|
140
|
+
\sum_{uv\in E}y_{u,v}^k &\leq z &\quad \forall k\in[0,n-1] \quad(6)\\
|
141
|
+
0 \leq z &\leq |E|
|
142
|
+
\end{alignat*}
|
143
|
+
|
144
|
+
Constraints (1)-(3) ensure that all vertices have a distinct position.
|
145
|
+
Constraints (4)-(5) force variable `y_{u,v}^k` to 1 if the edge is in the cut.
|
146
|
+
Constraint (6) count the number of edges starting at position at most `k` and
|
147
|
+
ending at a position strictly larger than `k`.
|
148
|
+
|
149
|
+
This formulation corresponds to method :meth:`cutwidth_MILP`.
|
150
|
+
|
151
|
+
|
152
|
+
Authors
|
153
|
+
-------
|
154
|
+
|
155
|
+
- David Coudert (2015-06): Initial version
|
156
|
+
|
157
|
+
|
158
|
+
Methods
|
159
|
+
-------
|
160
|
+
"""
|
161
|
+
|
162
|
+
# ****************************************************************************
|
163
|
+
# Copyright (C) 2015 David Coudert <david.coudert@inria.fr>
|
164
|
+
#
|
165
|
+
# This program is free software: you can redistribute it and/or modify
|
166
|
+
# it under the terms of the GNU General Public License as published by
|
167
|
+
# the Free Software Foundation, either version 2 of the License, or
|
168
|
+
# (at your option) any later version.
|
169
|
+
# http://www.gnu.org/licenses/
|
170
|
+
# ****************************************************************************
|
171
|
+
|
172
|
+
from libc.stdint cimport uint8_t
|
173
|
+
from libc.string cimport memset
|
174
|
+
from cysignals.memory cimport check_allocarray, sig_free
|
175
|
+
from cysignals.signals cimport sig_check
|
176
|
+
|
177
|
+
from sage.graphs.graph_decompositions.fast_digraph cimport FastDigraph, popcount32
|
178
|
+
from sage.graphs.graph_decompositions.vertex_separation cimport find_order
|
179
|
+
from sage.graphs.graph_decompositions.vertex_separation import is_valid_ordering
|
180
|
+
from sage.rings.integer_ring import ZZ
|
181
|
+
|
182
|
+
|
183
|
+
################################################################################
|
184
|
+
# Measurement function of the width of some layout
|
185
|
+
################################################################################
|
186
|
+
|
187
|
+
def width_of_cut_decomposition(G, L):
|
188
|
+
r"""
|
189
|
+
Return the width of the cut decomposition induced by the linear ordering
|
190
|
+
`L` of the vertices of `G`.
|
191
|
+
|
192
|
+
If `G` is an instance of :mod:`Graph <sage.graphs.graph>`, this function
|
193
|
+
returns the width `cw_L(G)` of the cut decomposition induced by the linear
|
194
|
+
ordering `L` of the vertices of `G`.
|
195
|
+
|
196
|
+
.. MATH::
|
197
|
+
|
198
|
+
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]\}|
|
199
|
+
|
200
|
+
INPUT:
|
201
|
+
|
202
|
+
- ``G`` -- a Graph
|
203
|
+
|
204
|
+
- ``L`` -- a linear ordering of the vertices of ``G``
|
205
|
+
|
206
|
+
EXAMPLES:
|
207
|
+
|
208
|
+
Cut decomposition of a Cycle graph::
|
209
|
+
|
210
|
+
sage: from sage.graphs.graph_decompositions import cutwidth
|
211
|
+
sage: G = graphs.CycleGraph(6)
|
212
|
+
sage: L = G.vertices(sort=False)
|
213
|
+
sage: cutwidth.width_of_cut_decomposition(G, L)
|
214
|
+
2
|
215
|
+
|
216
|
+
Cut decomposition of a Path graph::
|
217
|
+
|
218
|
+
sage: from sage.graphs.graph_decompositions import cutwidth
|
219
|
+
sage: P = graphs.PathGraph(6)
|
220
|
+
sage: cutwidth.width_of_cut_decomposition(P, [0, 1, 2, 3, 4, 5])
|
221
|
+
1
|
222
|
+
sage: cutwidth.width_of_cut_decomposition(P, [5, 0, 1, 2, 3, 4])
|
223
|
+
2
|
224
|
+
sage: cutwidth.width_of_cut_decomposition(P, [0, 2, 4, 1, 3, 5])
|
225
|
+
5
|
226
|
+
|
227
|
+
TESTS:
|
228
|
+
|
229
|
+
Giving a wrong linear ordering::
|
230
|
+
|
231
|
+
sage: from sage.graphs.graph_decompositions import cutwidth
|
232
|
+
sage: cutwidth.width_of_cut_decomposition(Graph(), ['a','b'])
|
233
|
+
Traceback (most recent call last):
|
234
|
+
...
|
235
|
+
ValueError: the input linear vertex ordering L is not valid for G
|
236
|
+
"""
|
237
|
+
if not is_valid_ordering(G, L):
|
238
|
+
raise ValueError("the input linear vertex ordering L is not valid for G")
|
239
|
+
elif G.order() <= 1:
|
240
|
+
return 0
|
241
|
+
|
242
|
+
cdef int i, x, y
|
243
|
+
cdef dict position = {u: i for i, u in enumerate(L)}
|
244
|
+
|
245
|
+
# We count for each position `i` the number of edges going from vertices at
|
246
|
+
# positions in `0..i` to vertices at positions in `i+1..n-1`, for each
|
247
|
+
# `x\leq i<n-1`.
|
248
|
+
cdef list cpt = [0] * G.order()
|
249
|
+
for u, v in G.edge_iterator(labels=None):
|
250
|
+
x, y = position[u], position[v]
|
251
|
+
if x > y:
|
252
|
+
x, y = y, x
|
253
|
+
# Edge (u,v) contributes 1 to the number of edges going from vertices at
|
254
|
+
# positions `0..i` to vertices at positions `i+1..n-1` for each `x\leq
|
255
|
+
# i < n-1`.
|
256
|
+
for i in range(x, y):
|
257
|
+
cpt[i] += 1
|
258
|
+
|
259
|
+
# The width of L is the maximum computed value.
|
260
|
+
return max(cpt)
|
261
|
+
|
262
|
+
|
263
|
+
################################################################################
|
264
|
+
# Front end method for cutwidth
|
265
|
+
################################################################################
|
266
|
+
|
267
|
+
def cutwidth(G, algorithm='exponential', cut_off=0, solver=None, verbose=False,
|
268
|
+
*, integrality_tolerance=1e-3):
|
269
|
+
r"""
|
270
|
+
Return the cutwidth of the graph and the corresponding vertex ordering.
|
271
|
+
|
272
|
+
INPUT:
|
273
|
+
|
274
|
+
- ``G`` -- a Graph or a DiGraph
|
275
|
+
|
276
|
+
- ``algorithm`` -- string (default: ``'exponential'``); algorithm to use
|
277
|
+
among:
|
278
|
+
|
279
|
+
- ``exponential`` -- use an exponential time and space algorithm based on
|
280
|
+
dynamic programming. This algorithm only works on graphs with strictly
|
281
|
+
less than 32 vertices.
|
282
|
+
|
283
|
+
- ``MILP`` -- use a mixed integer linear programming formulation. This
|
284
|
+
algorithm has no size restriction but could take a very long time
|
285
|
+
|
286
|
+
- ``cut_off`` -- integer (default: 0); used to stop the search as soon as a
|
287
|
+
solution with width at most ``cut_off`` is found, if any. If this bound
|
288
|
+
cannot be reached, the best solution found is returned.
|
289
|
+
|
290
|
+
- ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
|
291
|
+
Programming (MILP) solver to be used. If set to ``None``, the default one
|
292
|
+
is used. For more information on MILP solvers and which default solver is
|
293
|
+
used, see the method :meth:`solve
|
294
|
+
<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
|
295
|
+
:class:`MixedIntegerLinearProgram
|
296
|
+
<sage.numerical.mip.MixedIntegerLinearProgram>`.
|
297
|
+
|
298
|
+
- ``verbose`` -- boolean (default: ``False``); whether to display
|
299
|
+
information on the computations
|
300
|
+
|
301
|
+
- ``integrality_tolerance`` -- float; parameter for use with MILP solvers
|
302
|
+
over an inexact base ring; see
|
303
|
+
:meth:`MixedIntegerLinearProgram.get_values`.
|
304
|
+
|
305
|
+
OUTPUT:
|
306
|
+
|
307
|
+
A pair ``(cost, ordering)`` representing the optimal ordering of the
|
308
|
+
vertices and its cost.
|
309
|
+
|
310
|
+
EXAMPLES:
|
311
|
+
|
312
|
+
Cutwidth of a Complete Graph::
|
313
|
+
|
314
|
+
sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth
|
315
|
+
sage: G = graphs.CompleteGraph(5)
|
316
|
+
sage: cw,L = cutwidth(G); cw
|
317
|
+
6
|
318
|
+
sage: K = graphs.CompleteGraph(6)
|
319
|
+
sage: cw,L = cutwidth(K); cw
|
320
|
+
9
|
321
|
+
sage: cw,L = cutwidth(K+K); cw
|
322
|
+
9
|
323
|
+
|
324
|
+
The cutwidth of a `p\times q` Grid Graph with `p\leq q` is `p+1`::
|
325
|
+
|
326
|
+
sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth
|
327
|
+
sage: G = graphs.Grid2dGraph(3,3)
|
328
|
+
sage: cw,L = cutwidth(G); cw
|
329
|
+
4
|
330
|
+
sage: G = graphs.Grid2dGraph(3,5)
|
331
|
+
sage: cw,L = cutwidth(G); cw
|
332
|
+
4
|
333
|
+
|
334
|
+
TESTS:
|
335
|
+
|
336
|
+
Comparison of algorithms::
|
337
|
+
|
338
|
+
sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth
|
339
|
+
sage: for i in range(2): # long time # needs sage.numerical.mip
|
340
|
+
....: G = graphs.RandomGNP(7, 0.3)
|
341
|
+
....: ve, le = cutwidth(G, algorithm='exponential')
|
342
|
+
....: vm, lm = cutwidth(G, algorithm='MILP', solver='GLPK')
|
343
|
+
....: if ve != vm:
|
344
|
+
....: raise ValueError("Something goes wrong!")
|
345
|
+
|
346
|
+
Given a wrong algorithm::
|
347
|
+
|
348
|
+
sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth
|
349
|
+
sage: cutwidth(graphs.PathGraph(2), algorithm='SuperFast')
|
350
|
+
Traceback (most recent call last):
|
351
|
+
...
|
352
|
+
ValueError: algorithm "SuperFast" has not been implemented yet, please contribute
|
353
|
+
|
354
|
+
Given anything else than a Graph::
|
355
|
+
|
356
|
+
sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth
|
357
|
+
sage: cutwidth(range(4))
|
358
|
+
Traceback (most recent call last):
|
359
|
+
...
|
360
|
+
ValueError: the first parameter must be a Graph
|
361
|
+
|
362
|
+
Giving a wrong type cut off::
|
363
|
+
|
364
|
+
sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth
|
365
|
+
sage: cutwidth(Graph(), cut_off='toto')
|
366
|
+
Traceback (most recent call last):
|
367
|
+
...
|
368
|
+
ValueError: the specified cut off parameter must be an integer
|
369
|
+
|
370
|
+
Cutwidth of a graph with one edge (:issue:`32131`)::
|
371
|
+
|
372
|
+
sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth
|
373
|
+
sage: G = Graph([(0, 1)])
|
374
|
+
sage: cutwidth(G, algorithm='exponential')
|
375
|
+
(1, [0, 1])
|
376
|
+
sage: cutwidth(G, algorithm='MILP', solver='GLPK') # needs sage.numerical.mip
|
377
|
+
(1, [0, 1])
|
378
|
+
|
379
|
+
Cutwidth of a disconnected graph::
|
380
|
+
|
381
|
+
sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth
|
382
|
+
sage: G = Graph(5)
|
383
|
+
sage: G.add_edge(2, 3)
|
384
|
+
sage: cutwidth(G, algorithm='exponential')
|
385
|
+
(1, [2, 3, 0, 1, 4])
|
386
|
+
sage: cutwidth(G, algorithm='MILP', solver='GLPK') # needs sage.numerical.mip
|
387
|
+
(1, [2, 3, 0, 1, 4])
|
388
|
+
"""
|
389
|
+
from sage.graphs.graph import Graph
|
390
|
+
|
391
|
+
if not isinstance(G, Graph):
|
392
|
+
raise ValueError('the first parameter must be a Graph')
|
393
|
+
|
394
|
+
if cut_off not in ZZ:
|
395
|
+
raise ValueError("the specified cut off parameter must be an integer")
|
396
|
+
elif G.size() <= cut_off:
|
397
|
+
# We have a trivial solution
|
398
|
+
return width_of_cut_decomposition(G, list(G)), list(G)
|
399
|
+
|
400
|
+
cdef list CC
|
401
|
+
if not G.is_connected():
|
402
|
+
CC = G.connected_components_subgraphs()
|
403
|
+
else:
|
404
|
+
CC = [G]
|
405
|
+
|
406
|
+
# If the graph has several connected components. We solve the problem on
|
407
|
+
# each of them and concatenate the partial orderings. The cutwidth is the
|
408
|
+
# maximum over all these subgraphs.
|
409
|
+
cdef int cw = 0
|
410
|
+
cdef list L = []
|
411
|
+
cdef int cwH
|
412
|
+
cdef list LH
|
413
|
+
cdef int this_cut_off = cut_off
|
414
|
+
|
415
|
+
for H in CC:
|
416
|
+
|
417
|
+
if H.size() <= this_cut_off:
|
418
|
+
# We can directly add the vertices to the solution
|
419
|
+
L.extend(H)
|
420
|
+
|
421
|
+
else:
|
422
|
+
# We have a connected graph and we call the desired algorithm
|
423
|
+
if algorithm == "exponential":
|
424
|
+
cwH, LH = cutwidth_dyn(H, lower_bound=this_cut_off)
|
425
|
+
|
426
|
+
elif algorithm == "MILP":
|
427
|
+
cwH, LH = cutwidth_MILP(H, lower_bound=this_cut_off, solver=solver,
|
428
|
+
verbose=verbose, integrality_tolerance=integrality_tolerance)
|
429
|
+
|
430
|
+
else:
|
431
|
+
raise ValueError('algorithm "{}" has not been implemented yet, please contribute'.format(algorithm))
|
432
|
+
|
433
|
+
# We update the cutwidth and ordering
|
434
|
+
cw = max(cw, cwH)
|
435
|
+
L.extend(LH)
|
436
|
+
this_cut_off = max(cw, this_cut_off)
|
437
|
+
|
438
|
+
return cw, L
|
439
|
+
|
440
|
+
|
441
|
+
################################################################################
|
442
|
+
# Dynamic Programming algorithm for cutwidth
|
443
|
+
################################################################################
|
444
|
+
|
445
|
+
def cutwidth_dyn(G, lower_bound=0):
|
446
|
+
r"""
|
447
|
+
Dynamic programming algorithm for the cutwidth of a Graph.
|
448
|
+
|
449
|
+
This function uses dynamic programming algorithm for determining an optimal
|
450
|
+
layout for the cutwidth of `G`. See the :mod:`module's documentation
|
451
|
+
<sage.graphs.graph_decompositions.cutwidth>` for more details on this
|
452
|
+
method.
|
453
|
+
|
454
|
+
INPUT:
|
455
|
+
|
456
|
+
- ``G`` -- a Graph
|
457
|
+
|
458
|
+
- ``lower_bound`` -- integer (default: 0); the algorithm returns immediately
|
459
|
+
if it finds a solution lower or equal to ``lower_bound`` (in which case it
|
460
|
+
may not be optimal).
|
461
|
+
|
462
|
+
OUTPUT:
|
463
|
+
|
464
|
+
A pair ``(cost, ordering)`` representing the optimal ordering of the
|
465
|
+
vertices and its cost.
|
466
|
+
|
467
|
+
.. NOTE::
|
468
|
+
|
469
|
+
Because of its current implementation, this algorithm only works on
|
470
|
+
graphs on strictly less than 32 vertices. This can be changed to 63 if
|
471
|
+
necessary, but 32 vertices already require 4GB of memory.
|
472
|
+
|
473
|
+
TESTS:
|
474
|
+
|
475
|
+
Giving anything else than a Graph::
|
476
|
+
|
477
|
+
sage: from sage.graphs.graph_decompositions import cutwidth
|
478
|
+
sage: cutwidth.cutwidth_dyn([])
|
479
|
+
Traceback (most recent call last):
|
480
|
+
...
|
481
|
+
ValueError: the parameter must be a Graph
|
482
|
+
|
483
|
+
Giving a too large Graph::
|
484
|
+
|
485
|
+
sage: from sage.graphs.graph_decompositions import cutwidth
|
486
|
+
sage: cutwidth.cutwidth_dyn(graphs.PathGraph(40))
|
487
|
+
Traceback (most recent call last):
|
488
|
+
...
|
489
|
+
ValueError: the graph should have at most 31 vertices
|
490
|
+
|
491
|
+
Giving a wrong type lower bound::
|
492
|
+
|
493
|
+
sage: from sage.graphs.graph_decompositions import cutwidth
|
494
|
+
sage: cutwidth.cutwidth_dyn(Graph(), lower_bound='toto')
|
495
|
+
Traceback (most recent call last):
|
496
|
+
...
|
497
|
+
ValueError: the specified lower bound must be an integer
|
498
|
+
"""
|
499
|
+
from sage.graphs.graph import Graph
|
500
|
+
if not isinstance(G, Graph):
|
501
|
+
raise ValueError("the parameter must be a Graph")
|
502
|
+
|
503
|
+
if G.order() >= 32:
|
504
|
+
raise ValueError("the graph should have at most 31 vertices")
|
505
|
+
|
506
|
+
if lower_bound not in ZZ:
|
507
|
+
raise ValueError("the specified lower bound must be an integer")
|
508
|
+
|
509
|
+
cdef FastDigraph g = FastDigraph(G)
|
510
|
+
|
511
|
+
cdef unsigned int mem = 1 << g.n
|
512
|
+
cdef uint8_t* neighborhoods = <uint8_t*> check_allocarray(mem, sizeof(uint8_t))
|
513
|
+
|
514
|
+
memset(neighborhoods, <uint8_t> -1, mem)
|
515
|
+
|
516
|
+
cdef int i, k
|
517
|
+
cdef list order
|
518
|
+
|
519
|
+
try:
|
520
|
+
for k in range(lower_bound, G.size() + 1):
|
521
|
+
for i in range(g.n):
|
522
|
+
sig_check()
|
523
|
+
if exists(g, neighborhoods, 0, 0, i, k) <= k:
|
524
|
+
order = find_order(g, neighborhoods, k)
|
525
|
+
return k, [g.int_to_vertices[i] for i in order]
|
526
|
+
|
527
|
+
order = find_order(g, neighborhoods, k)
|
528
|
+
return k, [g.int_to_vertices[i] for i in order]
|
529
|
+
finally:
|
530
|
+
sig_free(neighborhoods)
|
531
|
+
|
532
|
+
|
533
|
+
cdef inline int exists(FastDigraph g, uint8_t* neighborhoods, int S, int cost_S, int v, int k) noexcept:
|
534
|
+
r"""
|
535
|
+
Check whether an ordering with the given cost `k` exists, and updates data
|
536
|
+
in the neighborhoods array at the same time. See the module's documentation.
|
537
|
+
|
538
|
+
INPUT:
|
539
|
+
|
540
|
+
- ``g`` -- a FastDiGraph
|
541
|
+
|
542
|
+
- ``neighborhoods`` -- an array of size `2^(g.n)`; stores for each subset
|
543
|
+
`X\subseteq V` of vertices of the graph the number of edges from `X` to
|
544
|
+
`V\setminus X`
|
545
|
+
|
546
|
+
- ``S`` -- integer; encodes the predecessor subset of vertices (from which
|
547
|
+
is issued the current call)
|
548
|
+
|
549
|
+
- ``cost_S`` -- integer; the number of edges from `S` to `V\setminus S`
|
550
|
+
|
551
|
+
- ``v`` -- integer; a vertex such that the current subset of vertices is
|
552
|
+
`current==S\cup\{v\}`
|
553
|
+
|
554
|
+
- ``k`` -- integer; the maximum admissible cost for a solution
|
555
|
+
"""
|
556
|
+
cdef int current = S | 1 << v
|
557
|
+
# If this is true, it means the set has not been evaluated yet
|
558
|
+
if neighborhoods[current] == <uint8_t> -1:
|
559
|
+
# The number of edges from `current` to `V\setminus current` is the
|
560
|
+
# number of edges from `S` to `V\setminus S`, minus the number of edges
|
561
|
+
# from `S` to vertex `v`, plus the number of edges from `v` to
|
562
|
+
# `V\setminus (S\cup \{v\})`. This can be computed adding the degree of
|
563
|
+
# `c` to `cost_S`, and then removing twice the number of edges from `S`
|
564
|
+
# to `v`.
|
565
|
+
neighborhoods[current] = cost_S + g.degree[v] - 2 * popcount32(S & g.graph[v])
|
566
|
+
|
567
|
+
# If the cost of this set is too high, there is no point in going further.
|
568
|
+
# Same thing if the current set is the whole vertex set.
|
569
|
+
if neighborhoods[current] > k or (current == (1 << g.n) - 1):
|
570
|
+
return neighborhoods[current]
|
571
|
+
|
572
|
+
# Minimum of the costs of the outneighbors, initialized with large constant.
|
573
|
+
cdef int mini = (<uint8_t> -1)
|
574
|
+
|
575
|
+
cdef int i
|
576
|
+
|
577
|
+
# For each possible extension of the current set with a vertex, check whether
|
578
|
+
# there exists a cheap path toward {1..n}, and update the cost.
|
579
|
+
for i in range(g.n):
|
580
|
+
if (current >> i) & 1: # if i in S
|
581
|
+
continue
|
582
|
+
|
583
|
+
mini = min(mini, exists(g, neighborhoods, current, neighborhoods[current], i, k))
|
584
|
+
|
585
|
+
# We have found a path !
|
586
|
+
if mini <= k:
|
587
|
+
return mini
|
588
|
+
|
589
|
+
# Updating the cost of the current set with the minimum of the cost of its
|
590
|
+
# outneighbors.
|
591
|
+
neighborhoods[current] = mini
|
592
|
+
|
593
|
+
return neighborhoods[current]
|
594
|
+
|
595
|
+
|
596
|
+
################################################################################
|
597
|
+
# MILP formulations for cutwidth
|
598
|
+
################################################################################
|
599
|
+
|
600
|
+
def cutwidth_MILP(G, lower_bound=0, solver=None, verbose=0,
|
601
|
+
*, integrality_tolerance=1e-3):
|
602
|
+
r"""
|
603
|
+
MILP formulation for the cutwidth of a Graph.
|
604
|
+
|
605
|
+
This method uses a mixed integer linear program (MILP) for determining an
|
606
|
+
optimal layout for the cutwidth of `G`. See the :mod:`module's documentation
|
607
|
+
<sage.graphs.graph_decompositions.cutwidth>` for more details on this MILP
|
608
|
+
formulation.
|
609
|
+
|
610
|
+
INPUT:
|
611
|
+
|
612
|
+
- ``G`` -- a Graph
|
613
|
+
|
614
|
+
- ``lower_bound`` -- integer (default: 0); the algorithm searches for a
|
615
|
+
solution with cost larger or equal to ``lower_bound``. If the given bound
|
616
|
+
is larger than the optimal solution the returned solution might not be
|
617
|
+
optimal. If the given bound is too high, the algorithm might not be able
|
618
|
+
to find a feasible solution.
|
619
|
+
|
620
|
+
- ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
|
621
|
+
Programming (MILP) solver to be used. If set to ``None``, the default one
|
622
|
+
is used. For more information on MILP solvers and which default solver is
|
623
|
+
used, see the method :meth:`solve
|
624
|
+
<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
|
625
|
+
:class:`MixedIntegerLinearProgram
|
626
|
+
<sage.numerical.mip.MixedIntegerLinearProgram>`.
|
627
|
+
|
628
|
+
- ``verbose`` -- integer (default: 0); sets the level of verbosity. Set
|
629
|
+
to 0 by default, which means quiet.
|
630
|
+
|
631
|
+
- ``integrality_tolerance`` -- float; parameter for use with MILP solvers
|
632
|
+
over an inexact base ring; see
|
633
|
+
:meth:`MixedIntegerLinearProgram.get_values`.
|
634
|
+
|
635
|
+
OUTPUT:
|
636
|
+
|
637
|
+
A pair ``(cost, ordering)`` representing the optimal ordering of the
|
638
|
+
vertices and its cost.
|
639
|
+
|
640
|
+
EXAMPLES:
|
641
|
+
|
642
|
+
Cutwidth of a Cycle graph::
|
643
|
+
|
644
|
+
sage: from sage.graphs.graph_decompositions import cutwidth
|
645
|
+
sage: G = graphs.CycleGraph(5)
|
646
|
+
sage: cw, L = cutwidth.cutwidth_MILP(G); cw # needs sage.numerical.mip
|
647
|
+
2
|
648
|
+
sage: cw == cutwidth.width_of_cut_decomposition(G, L) # needs sage.numerical.mip
|
649
|
+
True
|
650
|
+
sage: cwe, Le = cutwidth.cutwidth_dyn(G); cwe
|
651
|
+
2
|
652
|
+
|
653
|
+
Cutwidth of a Complete graph::
|
654
|
+
|
655
|
+
sage: from sage.graphs.graph_decompositions import cutwidth
|
656
|
+
sage: G = graphs.CompleteGraph(4)
|
657
|
+
sage: cw, L = cutwidth.cutwidth_MILP(G); cw # needs sage.numerical.mip
|
658
|
+
4
|
659
|
+
sage: cw == cutwidth.width_of_cut_decomposition(G, L) # needs sage.numerical.mip
|
660
|
+
True
|
661
|
+
|
662
|
+
Cutwidth of a Path graph::
|
663
|
+
|
664
|
+
sage: from sage.graphs.graph_decompositions import cutwidth
|
665
|
+
sage: G = graphs.PathGraph(3)
|
666
|
+
sage: cw, L = cutwidth.cutwidth_MILP(G); cw # needs sage.numerical.mip
|
667
|
+
1
|
668
|
+
sage: cw == cutwidth.width_of_cut_decomposition(G, L) # needs sage.numerical.mip
|
669
|
+
True
|
670
|
+
|
671
|
+
TESTS:
|
672
|
+
|
673
|
+
Comparison with exponential algorithm::
|
674
|
+
|
675
|
+
sage: from sage.graphs.graph_decompositions import cutwidth
|
676
|
+
sage: for i in range(2): # long time # needs sage.numerical.mip
|
677
|
+
....: G = graphs.RandomGNP(7, 0.3)
|
678
|
+
....: ve, le = cutwidth.cutwidth_dyn(G)
|
679
|
+
....: vm, lm = cutwidth.cutwidth_MILP(G, solver='GLPK')
|
680
|
+
....: if ve != vm:
|
681
|
+
....: print("The solution is not optimal!")
|
682
|
+
|
683
|
+
Giving a too large lower bound::
|
684
|
+
|
685
|
+
sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth_MILP
|
686
|
+
sage: G = graphs.CycleGraph(3)
|
687
|
+
sage: cutwidth_MILP(G, lower_bound=G.size()+1) # needs sage.numerical.mip
|
688
|
+
Traceback (most recent call last):
|
689
|
+
...
|
690
|
+
MIPSolverException: ...
|
691
|
+
|
692
|
+
Giving anything else than a Graph::
|
693
|
+
|
694
|
+
sage: from sage.graphs.graph_decompositions.cutwidth import cutwidth_MILP
|
695
|
+
sage: cutwidth_MILP([])
|
696
|
+
Traceback (most recent call last):
|
697
|
+
...
|
698
|
+
ValueError: the first input parameter must be a Graph
|
699
|
+
"""
|
700
|
+
from sage.graphs.graph import Graph
|
701
|
+
if not isinstance(G, Graph):
|
702
|
+
raise ValueError("the first input parameter must be a Graph")
|
703
|
+
|
704
|
+
from sage.numerical.mip import MixedIntegerLinearProgram
|
705
|
+
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
|
706
|
+
|
707
|
+
# Declaration of variables.
|
708
|
+
x = p.new_variable(binary=True, nonnegative=True)
|
709
|
+
y = p.new_variable(binary=True, nonnegative=True)
|
710
|
+
z = p.new_variable(integer=True, nonnegative=True)
|
711
|
+
|
712
|
+
N = G.order()
|
713
|
+
|
714
|
+
# All vertices at different positions
|
715
|
+
for v in G:
|
716
|
+
for k in range(N - 1):
|
717
|
+
p.add_constraint(p.sum(x[v, i] for i in range(k)) <= k * x[v, k])
|
718
|
+
p.add_constraint(x[v, N - 1] == 1)
|
719
|
+
for k in range(N):
|
720
|
+
p.add_constraint(p.sum(x[v, k] for v in G) == k + 1)
|
721
|
+
|
722
|
+
# Edge uv counts at position i if one of u or v is placed at a position in
|
723
|
+
# [0,i] and the other is placed at a position in [i+1,n].
|
724
|
+
for u, v in G.edge_iterator(labels=None):
|
725
|
+
for i in range(N):
|
726
|
+
p.add_constraint(x[u, i] - x[v, i] <= y[u, v, i])
|
727
|
+
p.add_constraint(x[v, i] - x[u, i] <= y[u, v, i])
|
728
|
+
|
729
|
+
# Lower bound on the solution
|
730
|
+
p.add_constraint(lower_bound <= z['z'])
|
731
|
+
|
732
|
+
# Objective
|
733
|
+
p.add_constraint(z['z'] <= G.size())
|
734
|
+
for i in range(N):
|
735
|
+
p.add_constraint(p.sum(y[u, v, i] for u, v in G.edge_iterator(labels=None)) <= z['z'])
|
736
|
+
|
737
|
+
p.set_objective(z['z'])
|
738
|
+
|
739
|
+
_ = p.solve(log=verbose)
|
740
|
+
|
741
|
+
# We now extract the ordering and the cost of the solution
|
742
|
+
val_x = p.get_values(x, convert=bool, tolerance=integrality_tolerance)
|
743
|
+
cdef int cw = p.get_values(z['z'], convert=True, tolerance=integrality_tolerance)
|
744
|
+
cdef list seq = []
|
745
|
+
cdef set to_see = set(G)
|
746
|
+
for k in range(N):
|
747
|
+
for u in to_see:
|
748
|
+
if val_x[u, k]:
|
749
|
+
seq.append(u)
|
750
|
+
to_see.discard(u)
|
751
|
+
break
|
752
|
+
|
753
|
+
return cw, seq
|