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.
Files changed (260) hide show
  1. passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
  2. passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
  3. passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
  5. passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
  6. passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
  7. passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
  8. sage/all__sagemath_graphs.py +39 -0
  9. sage/combinat/abstract_tree.py +2723 -0
  10. sage/combinat/all__sagemath_graphs.py +34 -0
  11. sage/combinat/binary_tree.py +5306 -0
  12. sage/combinat/cluster_algebra_quiver/all.py +22 -0
  13. sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
  14. sage/combinat/cluster_algebra_quiver/interact.py +124 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
  18. sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
  19. sage/combinat/designs/MOLS_handbook_data.py +570 -0
  20. sage/combinat/designs/all.py +58 -0
  21. sage/combinat/designs/bibd.py +1655 -0
  22. sage/combinat/designs/block_design.py +1071 -0
  23. sage/combinat/designs/covering_array.py +269 -0
  24. sage/combinat/designs/covering_design.py +530 -0
  25. sage/combinat/designs/database.py +5615 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  28. sage/combinat/designs/designs_pyx.pxd +21 -0
  29. sage/combinat/designs/designs_pyx.pyx +993 -0
  30. sage/combinat/designs/difference_family.py +3951 -0
  31. sage/combinat/designs/difference_matrices.py +279 -0
  32. sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  33. sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
  34. sage/combinat/designs/ext_rep.py +1064 -0
  35. sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
  36. sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
  37. sage/combinat/designs/group_divisible_designs.py +361 -0
  38. sage/combinat/designs/incidence_structures.py +2357 -0
  39. sage/combinat/designs/latin_squares.py +581 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2244 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
  44. sage/combinat/designs/resolvable_bibd.py +815 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
  47. sage/combinat/designs/subhypergraph_search.pyx +530 -0
  48. sage/combinat/designs/twographs.py +306 -0
  49. sage/combinat/finite_state_machine.py +14874 -0
  50. sage/combinat/finite_state_machine_generators.py +2006 -0
  51. sage/combinat/graph_path.py +448 -0
  52. sage/combinat/interval_posets.py +3908 -0
  53. sage/combinat/nu_tamari_lattice.py +269 -0
  54. sage/combinat/ordered_tree.py +1446 -0
  55. sage/combinat/posets/all.py +46 -0
  56. sage/combinat/posets/bubble_shuffle.py +247 -0
  57. sage/combinat/posets/cartesian_product.py +493 -0
  58. sage/combinat/posets/d_complete.py +182 -0
  59. sage/combinat/posets/elements.py +273 -0
  60. sage/combinat/posets/forest.py +30 -0
  61. sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
  62. sage/combinat/posets/hasse_cython.pyx +174 -0
  63. sage/combinat/posets/hasse_diagram.py +3672 -0
  64. sage/combinat/posets/hochschild_lattice.py +158 -0
  65. sage/combinat/posets/incidence_algebras.py +794 -0
  66. sage/combinat/posets/lattices.py +5117 -0
  67. sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
  68. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  69. sage/combinat/posets/linear_extensions.py +1037 -0
  70. sage/combinat/posets/mobile.py +275 -0
  71. sage/combinat/posets/moebius_algebra.py +776 -0
  72. sage/combinat/posets/poset_examples.py +2178 -0
  73. sage/combinat/posets/posets.py +9360 -0
  74. sage/combinat/rooted_tree.py +1070 -0
  75. sage/combinat/shard_order.py +239 -0
  76. sage/combinat/tamari_lattices.py +384 -0
  77. sage/combinat/yang_baxter_graph.py +923 -0
  78. sage/databases/all__sagemath_graphs.py +1 -0
  79. sage/databases/knotinfo_db.py +1231 -0
  80. sage/ext_data/all__sagemath_graphs.py +1 -0
  81. sage/ext_data/graphs/graph_plot_js.html +330 -0
  82. sage/ext_data/kenzo/CP2.txt +45 -0
  83. sage/ext_data/kenzo/CP3.txt +349 -0
  84. sage/ext_data/kenzo/CP4.txt +4774 -0
  85. sage/ext_data/kenzo/README.txt +49 -0
  86. sage/ext_data/kenzo/S4.txt +20 -0
  87. sage/graphs/all.py +42 -0
  88. sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
  89. sage/graphs/asteroidal_triples.pyx +320 -0
  90. sage/graphs/base/all.py +1 -0
  91. sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  92. sage/graphs/base/boost_graph.pxd +106 -0
  93. sage/graphs/base/boost_graph.pyx +3045 -0
  94. sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  95. sage/graphs/base/c_graph.pxd +106 -0
  96. sage/graphs/base/c_graph.pyx +5096 -0
  97. sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sage/graphs/base/dense_graph.pxd +28 -0
  99. sage/graphs/base/dense_graph.pyx +801 -0
  100. sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
  101. sage/graphs/base/graph_backends.pxd +5 -0
  102. sage/graphs/base/graph_backends.pyx +797 -0
  103. sage/graphs/base/overview.py +85 -0
  104. sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  105. sage/graphs/base/sparse_graph.pxd +90 -0
  106. sage/graphs/base/sparse_graph.pyx +1653 -0
  107. sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  108. sage/graphs/base/static_dense_graph.pxd +5 -0
  109. sage/graphs/base/static_dense_graph.pyx +1032 -0
  110. sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sage/graphs/base/static_sparse_backend.pxd +27 -0
  112. sage/graphs/base/static_sparse_backend.pyx +1583 -0
  113. sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  114. sage/graphs/base/static_sparse_graph.pxd +37 -0
  115. sage/graphs/base/static_sparse_graph.pyx +1375 -0
  116. sage/graphs/bipartite_graph.py +2732 -0
  117. sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
  118. sage/graphs/centrality.pyx +1038 -0
  119. sage/graphs/cographs.py +519 -0
  120. sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/comparability.pyx +851 -0
  122. sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  123. sage/graphs/connectivity.pxd +157 -0
  124. sage/graphs/connectivity.pyx +4813 -0
  125. sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
  126. sage/graphs/convexity_properties.pxd +16 -0
  127. sage/graphs/convexity_properties.pyx +870 -0
  128. sage/graphs/digraph.py +4754 -0
  129. sage/graphs/digraph_generators.py +1993 -0
  130. sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
  131. sage/graphs/distances_all_pairs.pxd +12 -0
  132. sage/graphs/distances_all_pairs.pyx +2938 -0
  133. sage/graphs/domination.py +1363 -0
  134. sage/graphs/dot2tex_utils.py +100 -0
  135. sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  136. sage/graphs/edge_connectivity.pyx +1215 -0
  137. sage/graphs/generators/all.py +1 -0
  138. sage/graphs/generators/basic.py +1769 -0
  139. sage/graphs/generators/chessboard.py +538 -0
  140. sage/graphs/generators/classical_geometries.py +1611 -0
  141. sage/graphs/generators/degree_sequence.py +235 -0
  142. sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
  143. sage/graphs/generators/distance_regular.pyx +2846 -0
  144. sage/graphs/generators/families.py +4759 -0
  145. sage/graphs/generators/intersection.py +565 -0
  146. sage/graphs/generators/platonic_solids.py +262 -0
  147. sage/graphs/generators/random.py +2623 -0
  148. sage/graphs/generators/smallgraphs.py +5741 -0
  149. sage/graphs/generators/world_map.py +724 -0
  150. sage/graphs/generic_graph.py +26867 -0
  151. sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  152. sage/graphs/generic_graph_pyx.pxd +34 -0
  153. sage/graphs/generic_graph_pyx.pyx +1673 -0
  154. sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
  155. sage/graphs/genus.pyx +622 -0
  156. sage/graphs/graph.py +9645 -0
  157. sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sage/graphs/graph_coloring.pyx +2284 -0
  159. sage/graphs/graph_database.py +1177 -0
  160. sage/graphs/graph_decompositions/all.py +1 -0
  161. sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  163. sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
  165. sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  167. sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
  168. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  169. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  170. sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/graph_products.pyx +508 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  173. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  174. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  176. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  177. sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  179. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  180. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  181. sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
  182. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  183. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  184. sage/graphs/graph_editor.py +82 -0
  185. sage/graphs/graph_generators.py +3314 -0
  186. sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  187. sage/graphs/graph_generators_pyx.pyx +95 -0
  188. sage/graphs/graph_input.py +812 -0
  189. sage/graphs/graph_latex.py +2064 -0
  190. sage/graphs/graph_list.py +410 -0
  191. sage/graphs/graph_plot.py +1756 -0
  192. sage/graphs/graph_plot_js.py +338 -0
  193. sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
  194. sage/graphs/hyperbolicity.pyx +1704 -0
  195. sage/graphs/hypergraph_generators.py +364 -0
  196. sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sage/graphs/independent_sets.pxd +13 -0
  198. sage/graphs/independent_sets.pyx +402 -0
  199. sage/graphs/isgci.py +1033 -0
  200. sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/isoperimetric_inequalities.pyx +489 -0
  202. sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  203. sage/graphs/line_graph.pyx +743 -0
  204. sage/graphs/lovasz_theta.py +77 -0
  205. sage/graphs/matching.py +1633 -0
  206. sage/graphs/matching_covered_graph.py +3590 -0
  207. sage/graphs/orientations.py +1489 -0
  208. sage/graphs/partial_cube.py +459 -0
  209. sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
  210. sage/graphs/path_enumeration.pyx +2040 -0
  211. sage/graphs/pq_trees.py +1129 -0
  212. sage/graphs/print_graphs.py +201 -0
  213. sage/graphs/schnyder.py +865 -0
  214. sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/spanning_tree.pyx +1457 -0
  216. sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/strongly_regular_db.pyx +3340 -0
  218. sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
  219. sage/graphs/traversals.pxd +9 -0
  220. sage/graphs/traversals.pyx +1872 -0
  221. sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sage/graphs/trees.pxd +15 -0
  223. sage/graphs/trees.pyx +310 -0
  224. sage/graphs/tutte_polynomial.py +713 -0
  225. sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/views.pyx +794 -0
  227. sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
  228. sage/graphs/weakly_chordal.pyx +604 -0
  229. sage/groups/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  231. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
  233. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  234. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  235. sage/knots/all.py +6 -0
  236. sage/knots/free_knotinfo_monoid.py +507 -0
  237. sage/knots/gauss_code.py +291 -0
  238. sage/knots/knot.py +682 -0
  239. sage/knots/knot_table.py +284 -0
  240. sage/knots/knotinfo.py +2900 -0
  241. sage/knots/link.py +4715 -0
  242. sage/sandpiles/all.py +13 -0
  243. sage/sandpiles/examples.py +225 -0
  244. sage/sandpiles/sandpile.py +6365 -0
  245. sage/topology/all.py +22 -0
  246. sage/topology/cell_complex.py +1214 -0
  247. sage/topology/cubical_complex.py +1976 -0
  248. sage/topology/delta_complex.py +1806 -0
  249. sage/topology/filtered_simplicial_complex.py +744 -0
  250. sage/topology/moment_angle_complex.py +823 -0
  251. sage/topology/simplicial_complex.py +5160 -0
  252. sage/topology/simplicial_complex_catalog.py +92 -0
  253. sage/topology/simplicial_complex_examples.py +1680 -0
  254. sage/topology/simplicial_complex_homset.py +205 -0
  255. sage/topology/simplicial_complex_morphism.py +836 -0
  256. sage/topology/simplicial_set.py +4102 -0
  257. sage/topology/simplicial_set_catalog.py +55 -0
  258. sage/topology/simplicial_set_constructions.py +2954 -0
  259. sage/topology/simplicial_set_examples.py +865 -0
  260. sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,1963 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # cython: binding=True
3
+ r"""
4
+ Vertex separation
5
+
6
+ This module implements several algorithms to compute the vertex separation of a
7
+ digraph and the corresponding ordering of the vertices. It also implements tests
8
+ functions for evaluation the width of a linear ordering.
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} c'(\{v_1, ..., v_i\})
16
+
17
+ Where
18
+
19
+ .. MATH::
20
+
21
+ c'(S) = |N^+_G(S)\backslash S|
22
+
23
+ The *vertex separation* of a digraph `G` is equal to the minimum cost of an
24
+ ordering of its vertices.
25
+
26
+ **Vertex separation and pathwidth**
27
+
28
+ The vertex separation is defined on a digraph, but one can obtain from a graph
29
+ `G` a digraph `D` with the same vertex set, and in which each edge `uv` of `G`
30
+ is replaced by two edges `uv` and `vu` in `D`. The vertex separation of `D` is
31
+ equal to the pathwidth of `G`, and the corresponding ordering of the vertices of
32
+ `D`, also called a *layout*, encodes an optimal path-decomposition of `G`.
33
+ This is a result of Kinnersley [Kin1992]_ and Bodlaender [Bod1998]_.
34
+
35
+
36
+ **This module contains the following methods**
37
+
38
+ .. csv-table::
39
+ :class: contentstable
40
+ :widths: 30, 70
41
+ :delim: |
42
+
43
+ :meth:`pathwidth` | Compute the pathwidth of ``self`` (and provides a decomposition)
44
+ :meth:`path_decomposition` | Return the pathwidth of the given graph and the ordering of the vertices resulting in a corresponding path decomposition
45
+ :meth:`vertex_separation` | Return an optimal ordering of the vertices and its cost for vertex-separation
46
+ :meth:`vertex_separation_exp` | Compute the vertex separation of `G` using an exponential time and space algorithm
47
+ :meth:`vertex_separation_MILP` | Compute the vertex separation of `G` and the optimal ordering of its vertices using an MILP formulation
48
+ :meth:`vertex_separation_BAB` | Compute the vertex separation of `G` and the optimal ordering of its vertices using a branch and bound algorithm
49
+ :meth:`lower_bound` | Return a lower bound on the vertex separation of `G`
50
+ :meth:`is_valid_ordering` | Test if the linear vertex ordering `L` is valid for (di)graph `G`
51
+ :meth:`width_of_path_decomposition` | Return the width of the path decomposition induced by the linear ordering `L` of the vertices of `G`
52
+ :meth:`linear_ordering_to_path_decomposition`| Return the path decomposition encoded in the ordering `L`
53
+
54
+
55
+ Exponential algorithm for vertex separation
56
+ -------------------------------------------
57
+
58
+ In order to find an optimal ordering of the vertices for the vertex separation,
59
+ this algorithm tries to save time by computing the function `c'(S)` **at most
60
+ once** once for each of the sets `S\subseteq V(G)`. These values are stored in
61
+ an array of size `2^n` where reading the value of `c'(S)` or updating it can be
62
+ done in constant (and small) time.
63
+
64
+ Assuming that we can compute the cost of a set `S` and remember it, finding an
65
+ optimal ordering is an easy task. Indeed, we can think of the sequence `v_1,
66
+ ..., v_n` of vertices as a sequence of *sets* `\{v_1\}, \{v_1,v_2\}, ...,
67
+ \{v_1,...,v_n\}`, whose cost is precisely `\max c'(\{v_1\}), c'(\{v_1,v_2\}),
68
+ ... , c'(\{v_1,...,v_n\})`. Hence, when considering the digraph on the `2^n`
69
+ sets `S\subseteq V(G)` where there is an arc from `S` to `S'` if `S'=S\cap
70
+ \{v\}` for some `v` (that is, if the sets `S` and `S'` can be consecutive in a
71
+ sequence), an ordering of the vertices of `G` corresponds to a *path* from
72
+ `\emptyset` to `\{v_1,...,v_n\}`. In this setting, checking whether there exists
73
+ a ordering of cost less than `k` can be achieved by checking whether there
74
+ exists a directed path `\emptyset` to `\{v_1,...,v_n\}` using only sets of cost
75
+ less than `k`. This is just a depth-first-search, for each `k`.
76
+
77
+ **Lazy evaluation of** `c'`
78
+
79
+ In the previous algorithm, most of the time is actually spent on the computation
80
+ of `c'(S)` for each set `S\subseteq V(G)` -- i.e. `2^n` computations of
81
+ neighborhoods. This can be seen as a huge waste of time when noticing that it is
82
+ useless to know that the value `c'(S)` for a set `S` is less than `k` if all the
83
+ paths leading to `S` have a cost greater than `k`. For this reason, the value of
84
+ `c'(S)` is computed lazily during the depth-first search. Explanation :
85
+
86
+ When the depth-first search discovers a set of size less than `k`, the costs of
87
+ its out-neighbors (the potential sets that could follow it in the optimal
88
+ ordering) are evaluated. When an out-neighbor is found that has a cost smaller
89
+ than `k`, the depth-first search continues with this set, which is explored with
90
+ the hope that it could lead to a path toward `\{v_1,...,v_n\}`. On the other
91
+ hand, if an out-neighbour has a cost larger than `k` it is useless to attempt to
92
+ build a cheap sequence going though this set, and the exploration stops
93
+ there. This way, a large number of sets will never be evaluated and *a lot* of
94
+ computational time is saved this way.
95
+
96
+ Besides, some improvement is also made by "improving" the values found by
97
+ `c'`. Indeed, `c'(S)` is a lower bound on the cost of a sequence containing the
98
+ set `S`, but if all out-neighbors of `S` have a cost of `c'(S) + 5` then one
99
+ knows that having `S` in a sequence means a total cost of at least `c'(S) +
100
+ 5`. For this reason, for each set `S` we store the value of `c'(S)`, and replace
101
+ it by `\max (c'(S), \min_{\text{next}})` (where `\min_{\text{next}}` is the
102
+ minimum of the costs of the out-neighbors of `S`) once the costs of these
103
+ out-neighbors have been evaluated by the algorithm.
104
+
105
+ .. NOTE::
106
+
107
+ Because of its current implementation, this algorithm only works on graphs
108
+ on less than 32 vertices. This can be changed to 64 if necessary, but 32
109
+ vertices already require 4GB of memory. Running it on 64 bits is not
110
+ expected to be doable by the computers of the next decade `:-D`
111
+
112
+ **Lower bound on the vertex separation**
113
+
114
+ One can obtain a lower bound on the vertex separation of a graph in exponential
115
+ time but *small* memory by computing once the cost of each set `S`. Indeed, the
116
+ cost of a sequence `v_1, ..., v_n` corresponding to sets `\{v_1\}, \{v_1,v_2\},
117
+ ..., \{v_1,...,v_n\}` is
118
+
119
+ .. MATH::
120
+
121
+ \max c'(\{v_1\}),c'(\{v_1,v_2\}),...,c'(\{v_1,...,v_n\})\geq\max c'_1,...,c'_n
122
+
123
+ where `c_i` is the minimum cost of a set `S` on `i` vertices. Evaluating the
124
+ `c_i` can take time (and in particular more than the previous exact algorithm),
125
+ but it does not need much memory to run.
126
+
127
+
128
+ MILP formulation for the vertex separation
129
+ ------------------------------------------
130
+
131
+ We describe below a mixed integer linear program (MILP) for determining an
132
+ optimal layout for the vertex separation of `G`, which is an improved version of
133
+ the formulation proposed in [SP2010]_. It aims at building a sequence `S_t` of
134
+ sets such that an ordering `v_1, ..., v_n` of the vertices correspond to
135
+ `S_0=\{v_1\}, S_2=\{v_1,v_2\}, ..., S_{n-1}=\{v_1,...,v_n\}`.
136
+
137
+ **Variables:**
138
+
139
+
140
+ - `y_v^t` -- variable set to 1 if `v\in S_t`, and 0 otherwise. The order of
141
+ `v` in the layout is the smallest `t` such that `y_v^t==1`.
142
+
143
+ - `u_v^t` -- variable set to 1 if `v\not \in S_t` and `v` has an in-neighbor in
144
+ `S_t`. It is set to 0 otherwise.
145
+
146
+ - `x_v^t` -- variable set to 1 if either `v\in S_t` or if `v` has an in-neighbor
147
+ in `S_t`. It is set to 0 otherwise.
148
+
149
+ - `z` -- objective value to minimize. It is equal to the maximum over all step
150
+ `t` of the number of vertices such that `u_v^t==1`.
151
+
152
+ **MILP formulation:**
153
+
154
+ .. MATH::
155
+ :nowrap:
156
+
157
+ \begin{alignat}{2}
158
+ \text{Minimize:}
159
+ &z&\\
160
+ \text{Such that:}
161
+ x_v^t &\leq x_v^{t+1}& \forall v\in V,\ 0\leq t\leq n-2\\
162
+ y_v^t &\leq y_v^{t+1}& \forall v\in V,\ 0\leq t\leq n-2\\
163
+ y_v^t &\leq x_w^t& \forall v\in V,\ \forall w\in N^+(v),\ 0\leq t\leq n-1\\
164
+ \sum_{v \in V} y_v^{t} &= t+1& 0\leq t\leq n-1\\
165
+ x_v^t-y_v^t&\leq u_v^t & \forall v \in V,\ 0\leq t\leq n-1\\
166
+ \sum_{v \in V} u_v^t &\leq z& 0\leq t\leq n-1\\
167
+ 0 \leq x_v^t &\leq 1& \forall v\in V,\ 0\leq t\leq n-1\\
168
+ 0 \leq u_v^t &\leq 1& \forall v\in V,\ 0\leq t\leq n-1\\
169
+ y_v^t &\in \{0,1\}& \forall v\in V,\ 0\leq t\leq n-1\\
170
+ 0 \leq z &\leq n&
171
+ \end{alignat}
172
+
173
+ The vertex separation of `G` is given by the value of `z`, and the order of
174
+ vertex `v` in the optimal layout is given by the smallest `t` for which
175
+ `y_v^t==1`.
176
+
177
+
178
+ Branch and Bound algorithm for the vertex separation
179
+ ----------------------------------------------------
180
+
181
+ We describe below the principle of a branch and bound algorithm (BAB) for
182
+ determining an optimal ordering for the vertex separation of `G`, as proposed in
183
+ [CMN2014]_.
184
+
185
+ **Greedy steps:**
186
+
187
+ Let us denote `{\cal L}(S)` the set of all possible orderings of the vertices in
188
+ `S`, and let `{\cal L}_P(S)\subseteq {\cal L}(S)` be the orderings starting with
189
+ a prefix `P`. Let also `c(L)` be the cost of the ordering `L\in{\cal L}(V)` as
190
+ defined above.
191
+
192
+ Given a digraph `D=(V,A)`, a set `S\subset V`, and a prefix `P`, it has been
193
+ proved in [CMN2014]_ that `\min_{L\in{\cal L}_P(V)} c(L) = \min_{L\in{\cal
194
+ L}_{P+v}(V)} c(L)` holds in two (non exhaustive) cases:
195
+
196
+ .. MATH::
197
+
198
+ \text{or} \begin{cases}
199
+ N^+(v)\subseteq S\cup N^+(S)\\
200
+ v\in N^+(S)\text{ and }N^+(v)\setminus(S\cup N^+(S)) = \{w\}
201
+ \end{cases}
202
+
203
+ In other words, if we find a vertex `v` satisfying the above conditions, the
204
+ best possible ordering with prefix `P` has the same cost as the best possible
205
+ ordering with prefix `P+v`. So we can greedily extend the prefix with vertices
206
+ satisfying the conditions which results in a significant reduction of the search
207
+ space.
208
+
209
+
210
+ **The algorithm:**
211
+
212
+ Given the current prefix `P` and the current upper bound `UB` (either an input
213
+ upper bound or the cost of the best solution found so far), apply the following
214
+ steps:
215
+
216
+ - Extend the prefix `P` into a prefix `P'` using the greedy steps as described
217
+ above.
218
+
219
+ - Sort the vertices `v\in V\setminus P'` by increasing values of `|N^+(P+v)|`,
220
+ and prune the vertices with a value larger or equal to `UB`. Let `\Delta` be
221
+ the resulting sorted list.
222
+
223
+ - Repeat with prefix `P'+v` for all `v\in\Delta` and keep the best found
224
+ solution.
225
+
226
+ If a lower bound is passed to the algorithm, it will stop as soon as a solution
227
+ with cost equal to that lower bound is found.
228
+
229
+
230
+ **Storing prefixes:**
231
+
232
+ If for a prefix `P` we have `c(P)<\min_{L\in{\cal L}_P(V)} c(L)=C`, then for any
233
+ permutation `P'` of `P` we have `\min_{L\in{\cal L}_{P'}(V)} c(L)\geq C`.
234
+
235
+ Thus, given such a prefix `P` there is no need to explore any of the orderings
236
+ starting with one of its permutations. To do so, we store `P` (as a set of
237
+ vertices) to cut branches later. See [CMN2014]_ for more details.
238
+
239
+ Since the number of stored sets can get very large, one can control the maximum
240
+ length and the maximum number of stored prefixes.
241
+
242
+
243
+ Authors
244
+ -------
245
+
246
+ - Nathann Cohen (2011-10): Initial version and exact exponential algorithm
247
+
248
+ - David Coudert (2012-04): MILP formulation and tests functions
249
+
250
+ - David Coudert (2015-01): BAB formulation and tests functions
251
+
252
+
253
+ Methods
254
+ -------
255
+ """
256
+
257
+ # ****************************************************************************
258
+ # Copyright (C) 2011 Nathann Cohen <nathann.cohen@gmail.com>
259
+ #
260
+ # This program is free software: you can redistribute it and/or modify
261
+ # it under the terms of the GNU General Public License as published by
262
+ # the Free Software Foundation, either version 2 of the License, or
263
+ # (at your option) any later version.
264
+ # http://www.gnu.org/licenses/
265
+ # ****************************************************************************
266
+
267
+ from libc.string cimport memset
268
+ from cysignals.memory cimport check_malloc, sig_malloc, sig_free
269
+ from cysignals.signals cimport sig_check, sig_on, sig_off
270
+
271
+ from sage.graphs.graph_decompositions.fast_digraph cimport FastDigraph, compute_out_neighborhood_cardinality, popcount32
272
+ from libc.stdint cimport uint8_t
273
+ from sage.data_structures.binary_matrix cimport *
274
+ from sage.graphs.base.static_dense_graph cimport dense_graph_init
275
+
276
+
277
+ ###############
278
+ # Lower Bound #
279
+ ###############
280
+
281
+ def lower_bound(G):
282
+ r"""
283
+ Return a lower bound on the vertex separation of `G`.
284
+
285
+ INPUT:
286
+
287
+ - ``G`` -- a Graph or a DiGraph
288
+
289
+ OUTPUT:
290
+
291
+ A lower bound on the vertex separation of `D` (see the module's
292
+ documentation).
293
+
294
+ .. NOTE::
295
+
296
+ This method runs in exponential time but has no memory constraint.
297
+
298
+ EXAMPLES:
299
+
300
+ On a circuit::
301
+
302
+ sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound
303
+ sage: g = digraphs.Circuit(6)
304
+ sage: lower_bound(g)
305
+ 1
306
+
307
+ TESTS:
308
+
309
+ Given anything else than a Graph or a DiGraph::
310
+
311
+ sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound
312
+ sage: lower_bound(range(2))
313
+ Traceback (most recent call last):
314
+ ...
315
+ ValueError: the parameter must be a Graph or a DiGraph
316
+
317
+ Given a too large graph::
318
+
319
+ sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound
320
+ sage: lower_bound(graphs.PathGraph(50))
321
+ Traceback (most recent call last):
322
+ ...
323
+ ValueError: the (di)graph can have at most 31 vertices
324
+ """
325
+ from sage.graphs.graph import Graph
326
+ from sage.graphs.digraph import DiGraph
327
+ if not isinstance(G, Graph) and not isinstance(G, DiGraph):
328
+ raise ValueError("the parameter must be a Graph or a DiGraph")
329
+
330
+ if G.order() >= 32:
331
+ raise ValueError("the (di)graph can have at most 31 vertices")
332
+
333
+ cdef FastDigraph FD = FastDigraph(G)
334
+ cdef unsigned int n = <unsigned int>FD.n
335
+
336
+ # minimums[i] is means to store the value of c'_{i+1}
337
+ cdef uint8_t* minimums = <uint8_t*> check_malloc(n * sizeof(uint8_t))
338
+ cdef unsigned int i
339
+
340
+ # They are initialized to n
341
+ for i in range(n):
342
+ minimums[i] = n
343
+
344
+ cdef uint8_t tmp, tmp_count
345
+
346
+ # We go through all sets
347
+ for i in range(1, <unsigned int> (1 << n)):
348
+ tmp_count = <uint8_t> popcount32(i)
349
+ tmp = <uint8_t> compute_out_neighborhood_cardinality(FD, i)
350
+
351
+ # And update the costs
352
+ minimums[tmp_count-1] = minimum(minimums[tmp_count-1], tmp)
353
+
354
+ # We compute the maximum of all those values
355
+ for i in range(1, n):
356
+ minimums[0] = maximum(minimums[0], minimums[i])
357
+
358
+ cdef int lb = minimums[0]
359
+
360
+ sig_free(minimums)
361
+
362
+ return lb
363
+
364
+
365
+ ###################################################################
366
+ # Method for turning an ordering to a path decomposition and back #
367
+ ###################################################################
368
+
369
+ def linear_ordering_to_path_decomposition(G, L):
370
+ """
371
+ Return the path decomposition encoded in the ordering L.
372
+
373
+ INPUT:
374
+
375
+ - ``G`` -- a Graph
376
+
377
+ - ``L`` -- a linear ordering for G
378
+
379
+ OUTPUT: a path graph whose vertices are the bags of the path decomposition
380
+
381
+ EXAMPLES:
382
+
383
+ The bags of an optimal path decomposition of a path-graph have two vertices
384
+ each::
385
+
386
+ sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
387
+ sage: from sage.graphs.graph_decompositions.vertex_separation import linear_ordering_to_path_decomposition
388
+ sage: g = graphs.PathGraph(5)
389
+ sage: pw, L = vertex_separation(g, algorithm = "BAB"); pw
390
+ 1
391
+ sage: h = linear_ordering_to_path_decomposition(g, L)
392
+ sage: sorted(h, key=str)
393
+ [{0, 1}, {1, 2}, {2, 3}, {3, 4}]
394
+ sage: sorted(h.edge_iterator(labels=None), key=str)
395
+ [({0, 1}, {1, 2}), ({1, 2}, {2, 3}), ({2, 3}, {3, 4})]
396
+
397
+ Giving a non-optimal linear ordering::
398
+
399
+ sage: g = graphs.PathGraph(5)
400
+ sage: L = [1, 4, 0, 2, 3]
401
+ sage: from sage.graphs.graph_decompositions.vertex_separation import width_of_path_decomposition
402
+ sage: width_of_path_decomposition(g, L)
403
+ 3
404
+ sage: h = linear_ordering_to_path_decomposition(g, L)
405
+ sage: h.vertices(sort=True)
406
+ [{0, 2, 3, 4}, {0, 1, 2}]
407
+
408
+ The bags of the path decomposition of a cycle have three vertices each::
409
+
410
+ sage: g = graphs.CycleGraph(6)
411
+ sage: pw, L = vertex_separation(g, algorithm = "BAB"); pw
412
+ 2
413
+ sage: h = linear_ordering_to_path_decomposition(g, L)
414
+ sage: sorted(h, key=str)
415
+ [{0, 1, 5}, {1, 2, 5}, {2, 3, 4}, {2, 4, 5}]
416
+ sage: sorted(h.edge_iterator(labels=None), key=str)
417
+ [({0, 1, 5}, {1, 2, 5}), ({1, 2, 5}, {2, 4, 5}), ({2, 4, 5}, {2, 3, 4})]
418
+
419
+
420
+ TESTS::
421
+
422
+ sage: linear_ordering_to_path_decomposition(Graph(), [])
423
+ Graph on 0 vertices
424
+ sage: linear_ordering_to_path_decomposition(DiGraph(), [])
425
+ Traceback (most recent call last):
426
+ ...
427
+ ValueError: the first parameter must be a Graph
428
+ sage: g = graphs.CycleGraph(6)
429
+ sage: linear_ordering_to_path_decomposition(g, list(range(7)))
430
+ Traceback (most recent call last):
431
+ ...
432
+ ValueError: the input linear vertex ordering L is not valid for G
433
+ """
434
+ from sage.graphs.graph import Graph
435
+ if not isinstance(G, Graph):
436
+ raise ValueError("the first parameter must be a Graph")
437
+ if not G:
438
+ return Graph()
439
+ if not is_valid_ordering(G, L):
440
+ raise ValueError("the input linear vertex ordering L is not valid for G")
441
+
442
+ cdef set seen = set() # already treated vertices
443
+ cdef set covered = set() # vertices in the neighborhood of seen but not in seen
444
+ cdef list bags = list() # The bags of the path decomposition
445
+
446
+ # We build the bags of the path-decomposition, and avoid adding useless bags
447
+ for u in L:
448
+ seen.add(u)
449
+ covered.update(G.neighbor_iterator(u))
450
+ covered.difference_update(seen)
451
+ new_bag = covered.union([u])
452
+ if bags:
453
+ if new_bag.issubset(bags[-1]):
454
+ continue
455
+ if new_bag.issuperset(bags[-1]):
456
+ bags.pop()
457
+
458
+ bags.append(new_bag)
459
+
460
+ # We now build a graph whose vertices are bags
461
+ from sage.sets.set import Set
462
+ H = Graph()
463
+ H.add_path([Set(bag) for bag in bags])
464
+ return H
465
+
466
+
467
+ ##################################################################
468
+ # Front end methods for path decomposition and vertex separation #
469
+ ##################################################################
470
+
471
+ def pathwidth(self, k=None, certificate=False, algorithm='BAB', verbose=False,
472
+ max_prefix_length=20, max_prefix_number=10**6, *, solver=None):
473
+ r"""
474
+ Compute the pathwidth of ``self`` (and provides a decomposition).
475
+
476
+ INPUT:
477
+
478
+ - ``k`` -- integer (default: ``None``); the width to be considered. When
479
+ ``k`` is an integer, the method checks that the graph has pathwidth
480
+ `\leq k`. If ``k`` is ``None`` (default), the method computes the optimal
481
+ pathwidth.
482
+
483
+ - ``certificate`` -- boolean (default: ``False``); whether to return the
484
+ path-decomposition itself
485
+
486
+ - ``algorithm`` -- string (default: ``'BAB'``); algorithm to use among:
487
+
488
+ - ``'BAB'`` -- use a branch-and-bound algorithm. This algorithm has no
489
+ size restriction but could take a very long time on large graphs. It can
490
+ also be used to test is the input graph has pathwidth `\leq k`, in which
491
+ cas it will return the first found solution with width `\leq k` is
492
+ ``certificate==True``.
493
+
494
+ - ``exponential`` -- use an exponential time and space algorithm. This
495
+ algorithm only works of graphs on less than 32 vertices
496
+
497
+ - ``MILP`` -- use a mixed integer linear programming formulation. This
498
+ algorithm has no size restriction but could take a very long time
499
+
500
+ - ``verbose`` -- boolean (default: ``False``); whether to display
501
+ information on the computations
502
+
503
+ - ``max_prefix_length`` -- integer (default: 20); limits the length of the
504
+ stored prefixes to prevent storing too many prefixes. This parameter is
505
+ used only when ``algorithm=="BAB"``.
506
+
507
+ - ``max_prefix_number`` -- integer (default: 10**6); upper bound on the
508
+ number of stored prefixes used to prevent using too much memory. This
509
+ parameter is used only when ``algorithm=="BAB"``.
510
+
511
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
512
+ Programming (MILP) solver to be used. If set to ``None``, the default one
513
+ is used. For more information on MILP solvers and which default solver is
514
+ used, see the method :meth:`solve
515
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
516
+ :class:`MixedIntegerLinearProgram
517
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
518
+
519
+ OUTPUT:
520
+
521
+ Return the pathwidth of ``self``. When ``k`` is specified, it returns
522
+ ``False`` when no path-decomposition of width `\leq k` exists or ``True``
523
+ otherwise. When ``certificate=True``, the path-decomposition is also
524
+ returned.
525
+
526
+ .. SEEALSO::
527
+
528
+ * :meth:`Graph.treewidth` -- computes the treewidth of a graph
529
+ * :meth:`~sage.graphs.graph_decompositions.vertex_separation.vertex_separation`
530
+ -- computes the vertex separation of a (di)graph
531
+
532
+ EXAMPLES:
533
+
534
+ The pathwidth of a cycle is equal to 2::
535
+
536
+ sage: g = graphs.CycleGraph(6)
537
+ sage: g.pathwidth()
538
+ 2
539
+ sage: pw, decomp = g.pathwidth(certificate=True)
540
+ sage: sorted(decomp, key=str)
541
+ [{0, 1, 5}, {1, 2, 5}, {2, 3, 4}, {2, 4, 5}]
542
+
543
+ The pathwidth of a Petersen graph is 5::
544
+
545
+ sage: g = graphs.PetersenGraph()
546
+ sage: g.pathwidth()
547
+ 5
548
+ sage: g.pathwidth(k=2)
549
+ False
550
+ sage: g.pathwidth(k=6)
551
+ True
552
+ sage: g.pathwidth(k=6, certificate=True)
553
+ (True, Graph on 5 vertices)
554
+
555
+ TESTS:
556
+
557
+ Given anything else than a Graph::
558
+
559
+ sage: from sage.graphs.graph_decompositions.vertex_separation import pathwidth
560
+ sage: pathwidth(DiGraph())
561
+ Traceback (most recent call last):
562
+ ...
563
+ ValueError: the parameter must be a Graph
564
+
565
+ Given a wrong algorithm::
566
+
567
+ sage: from sage.graphs.graph_decompositions.vertex_separation import pathwidth
568
+ sage: pathwidth(Graph(), algorithm='SuperFast')
569
+ Traceback (most recent call last):
570
+ ...
571
+ ValueError: algorithm "SuperFast" has not been implemented yet, please contribute
572
+
573
+ Using a specific solver::
574
+
575
+ sage: g = graphs.PetersenGraph()
576
+ sage: g.pathwidth(solver='SCIP') # optional - pyscipopt
577
+ 5
578
+ """
579
+ from sage.graphs.graph import Graph
580
+ if not isinstance(self, Graph):
581
+ raise ValueError("the parameter must be a Graph")
582
+
583
+ pw, L = vertex_separation(self, algorithm=algorithm, verbose=verbose,
584
+ cut_off=k, upper_bound=None if k is None else (k+1),
585
+ max_prefix_length=max_prefix_length,
586
+ max_prefix_number=max_prefix_number,
587
+ solver=solver)
588
+
589
+ if k is None:
590
+ return (pw, linear_ordering_to_path_decomposition(self, L)) if certificate else pw
591
+ if pw < 0:
592
+ # no solution found
593
+ return (False, Graph()) if certificate else False
594
+ return (pw <= k, linear_ordering_to_path_decomposition(self, L)) if certificate else pw <= k
595
+
596
+
597
+ def path_decomposition(G, algorithm='BAB', cut_off=None, upper_bound=None, verbose=False,
598
+ max_prefix_length=20, max_prefix_number=10**6):
599
+ r"""
600
+ Return the pathwidth of the given graph and the ordering of the vertices
601
+ resulting in a corresponding path decomposition.
602
+
603
+ INPUT:
604
+
605
+ - ``G`` -- a Graph
606
+
607
+ - ``algorithm`` -- string (default: ``'BAB'``); algorithm to use among:
608
+
609
+ - ``'BAB'`` -- use a branch-and-bound algorithm. This algorithm has no
610
+ size restriction but could take a very long time on large graphs. It can
611
+ also be used to test is the input (di)graph has vertex separation at
612
+ most ``upper_bound`` or to return the first found solution with vertex
613
+ separation less or equal to a ``cut_off`` value.
614
+
615
+ - ``exponential`` -- use an exponential time and space algorithm. This
616
+ algorithm only works of graphs on less than 32 vertices
617
+
618
+ - ``MILP`` -- use a mixed integer linear programming formulation. This
619
+ algorithm has no size restriction but could take a very long time
620
+
621
+ - ``upper_bound`` -- integer (default: ``None``); parameter used by the
622
+ ``'BAB'`` algorithm. If specified, the algorithm searches for a solution
623
+ with ``width < upper_bound``. It helps cutting branches. However, if the
624
+ given upper bound is too low, the algorithm may not be able to find a
625
+ solution.
626
+
627
+ - ``cut_off`` -- integer (default: ``None``); parameter used by the
628
+ ``'BAB'`` algorithm. This bound allows us to stop the search as soon as a
629
+ solution with width at most ``cut_off`` is found, if any. If this bound
630
+ cannot be reached, the best solution found is returned, unless a too low
631
+ ``upper_bound`` is given.
632
+
633
+ - ``verbose`` -- boolean (default: ``False``); whether to display
634
+ information on the computations
635
+
636
+ - ``max_prefix_length`` -- integer (default: 20); limits the length of the
637
+ stored prefixes to prevent storing too many prefixes. This parameter is
638
+ used only when ``algorithm=="BAB"``.
639
+
640
+ - ``max_prefix_number`` -- integer (default: 10**6); upper bound on the
641
+ number of stored prefixes used to prevent using too much memory. This
642
+ parameter is used only when ``algorithm=="BAB"``.
643
+
644
+ OUTPUT:
645
+
646
+ A pair ``(cost, ordering)`` representing the optimal ordering of the
647
+ vertices and its cost.
648
+
649
+ .. SEEALSO::
650
+
651
+ * :meth:`Graph.treewidth` -- computes the treewidth of a graph
652
+
653
+ EXAMPLES:
654
+
655
+ The pathwidth of a cycle is equal to 2::
656
+
657
+ sage: from sage.graphs.graph_decompositions.vertex_separation import path_decomposition
658
+ sage: g = graphs.CycleGraph(6)
659
+ sage: pw, L = path_decomposition(g, algorithm='BAB'); pw
660
+ 2
661
+ sage: pw, L = path_decomposition(g, algorithm='exponential'); pw
662
+ 2
663
+ sage: pw, L = path_decomposition(g, algorithm='MILP'); pw # needs sage.numerical.mip
664
+ 2
665
+
666
+ TESTS:
667
+
668
+ Given anything else than a Graph::
669
+
670
+ sage: from sage.graphs.graph_decompositions.vertex_separation import path_decomposition
671
+ sage: path_decomposition(DiGraph())
672
+ Traceback (most recent call last):
673
+ ...
674
+ ValueError: the parameter must be a Graph
675
+
676
+ Given a wrong algorithm::
677
+
678
+ sage: from sage.graphs.graph_decompositions.vertex_separation import path_decomposition
679
+ sage: path_decomposition(Graph(), algorithm='SuperFast')
680
+ Traceback (most recent call last):
681
+ ...
682
+ ValueError: algorithm "SuperFast" has not been implemented yet, please contribute
683
+ """
684
+ from sage.graphs.graph import Graph
685
+ if not isinstance(G, Graph):
686
+ raise ValueError("the parameter must be a Graph")
687
+
688
+ return vertex_separation(G, algorithm=algorithm, cut_off=cut_off, upper_bound=upper_bound,
689
+ verbose=verbose, max_prefix_length=max_prefix_length,
690
+ max_prefix_number=max_prefix_number)
691
+
692
+
693
+ def vertex_separation(G, algorithm='BAB', cut_off=None, upper_bound=None, verbose=False,
694
+ max_prefix_length=20, max_prefix_number=10**6,
695
+ *, solver=None, integrality_tolerance=1e-3):
696
+ r"""
697
+ Return an optimal ordering of the vertices and its cost for
698
+ vertex-separation.
699
+
700
+ INPUT:
701
+
702
+ - ``G`` -- a Graph or a DiGraph
703
+
704
+ - ``algorithm`` -- string (default: ``'BAB'``); algorithm to use among:
705
+
706
+ - ``'BAB'`` -- use a branch-and-bound algorithm. This algorithm has no
707
+ size restriction but could take a very long time on large graphs. It can
708
+ also be used to test is the input (di)graph has vertex separation at
709
+ most ``upper_bound`` or to return the first found solution with vertex
710
+ separation less or equal to a ``cut_off`` value.
711
+
712
+ - ``exponential`` -- use an exponential time and space algorithm. This
713
+ algorithm only works of graphs on less than 32 vertices
714
+
715
+ - ``MILP`` -- use a mixed integer linear programming formulation. This
716
+ algorithm has no size restriction but could take a very long time
717
+
718
+ - ``upper_bound`` -- integer (default: ``None``); parameter used by the
719
+ ``'BAB'`` algorithm. If specified, the algorithm searches for a solution
720
+ with ``width < upper_bound``. It helps cutting branches. However, if the
721
+ given upper bound is too low, the algorithm may not be able to find a
722
+ solution.
723
+
724
+ - ``cut_off`` -- integer (default: ``None``); parameter used by the
725
+ ``'BAB'`` algorithm. This bound allows us to stop the search as soon as a
726
+ solution with width at most ``cut_off`` is found, if any. If this bound
727
+ cannot be reached, the best solution found is returned, unless a too low
728
+ ``upper_bound`` is given.
729
+
730
+ - ``verbose`` -- boolean (default: ``False``); whether to display
731
+ information on the computations
732
+
733
+ - ``max_prefix_length`` -- integer (default: 20); limits the length of the
734
+ stored prefixes to prevent storing too many prefixes. This parameter is
735
+ used only when ``algorithm=="BAB"``.
736
+
737
+ - ``max_prefix_number`` -- integer (default: 10**6); upper bound on the
738
+ number of stored prefixes used to prevent using too much memory. This
739
+ parameter is used only when ``algorithm=="BAB"``.
740
+
741
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
742
+ Programming (MILP) solver to be used. If set to ``None``, the default one
743
+ is used. For more information on MILP solvers and which default solver is
744
+ used, see the method :meth:`solve
745
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
746
+ :class:`MixedIntegerLinearProgram
747
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
748
+
749
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
750
+ over an inexact base ring; see
751
+ :meth:`MixedIntegerLinearProgram.get_values`.
752
+
753
+ OUTPUT:
754
+
755
+ A pair ``(cost, ordering)`` representing the optimal ordering of the
756
+ vertices and its cost.
757
+
758
+ EXAMPLES:
759
+
760
+ Comparison of methods::
761
+
762
+ sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
763
+
764
+ sage: # needs sage.combinat
765
+ sage: G = digraphs.DeBruijn(2,3)
766
+ sage: vs,L = vertex_separation(G, algorithm='BAB'); vs
767
+ 2
768
+ sage: vs,L = vertex_separation(G, algorithm='exponential'); vs
769
+ 2
770
+ sage: vs,L = vertex_separation(G, algorithm='MILP'); vs # needs sage.numerical.mip
771
+ 2
772
+
773
+ sage: G = graphs.Grid2dGraph(3,3)
774
+ sage: vs,L = vertex_separation(G, algorithm='BAB'); vs
775
+ 3
776
+ sage: vs,L = vertex_separation(G, algorithm='exponential'); vs
777
+ 3
778
+ sage: vs,L = vertex_separation(G, algorithm='MILP'); vs # needs sage.numerical.mip
779
+ 3
780
+
781
+ Digraphs with multiple strongly connected components::
782
+
783
+ sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
784
+ sage: D = digraphs.Path(8)
785
+ sage: print(vertex_separation(D))
786
+ (0, [7, 6, 5, 4, 3, 2, 1, 0])
787
+ sage: D = digraphs.RandomDirectedAcyclicGraph(10, .5)
788
+ sage: vs,L = vertex_separation(D); vs
789
+ 0
790
+ sage: K4 = DiGraph( graphs.CompleteGraph(4) )
791
+ sage: D = K4+K4
792
+ sage: D.add_edge(0, 4)
793
+ sage: print(vertex_separation(D))
794
+ (3, [4, 5, 6, 7, 0, 1, 2, 3])
795
+ sage: D = K4+K4+K4
796
+ sage: D.add_edge(0, 4)
797
+ sage: D.add_edge(0, 8)
798
+ sage: print(vertex_separation(D))
799
+ (3, [10, 11, 8, 9, 4, 5, 6, 7, 0, 1, 2, 3])
800
+
801
+ Using a specific MILP solver::
802
+
803
+ sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
804
+ sage: G = graphs.PetersenGraph()
805
+ sage: vs, L = vertex_separation(G, algorithm='MILP', solver='SCIP'); vs # optional - pyscipopt, needs sage.numerical.mip
806
+ 5
807
+
808
+ TESTS:
809
+
810
+ Given a wrong algorithm::
811
+
812
+ sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
813
+ sage: vertex_separation(Graph(), algorithm='SuperFast')
814
+ Traceback (most recent call last):
815
+ ...
816
+ ValueError: algorithm "SuperFast" has not been implemented yet, please contribute
817
+
818
+ Given anything else than a Graph or a DiGraph::
819
+
820
+ sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
821
+ sage: vertex_separation(range(4))
822
+ Traceback (most recent call last):
823
+ ...
824
+ ValueError: the parameter must be a Graph or a DiGraph
825
+ """
826
+ from sage.graphs.graph import Graph
827
+ from sage.graphs.digraph import DiGraph
828
+
829
+ CC = []
830
+ if isinstance(G, Graph):
831
+ if not G.is_connected():
832
+ # We decompose the graph into connected components.
833
+ CC = G.connected_components()
834
+
835
+ elif isinstance(G, DiGraph):
836
+ if not G.is_strongly_connected():
837
+ # We decompose the digraph into strongly connected components and
838
+ # arrange them in the inverse order of the topological sort of the
839
+ # digraph of the strongly connected components.
840
+ scc_digraph = G.strongly_connected_components_digraph()
841
+ CC = scc_digraph.topological_sort()[::-1]
842
+
843
+ else:
844
+ raise ValueError('the parameter must be a Graph or a DiGraph')
845
+
846
+ if cut_off is None:
847
+ cut_off = 0
848
+
849
+ if CC:
850
+ # The graph has several (strongly) connected components. We solve the
851
+ # problem on each of them and order partial solutions in the same order
852
+ # than in list CC. The vertex separation is the maximum over all these
853
+ # subgraphs.
854
+ vs, L = 0, []
855
+ for V in CC:
856
+
857
+ if len(V) == 1:
858
+ # We can directly add this vertex to the solution
859
+ L.extend(V)
860
+
861
+ else:
862
+ # We build the (strongly) connected subgraph and do a recursive
863
+ # call to get its vertex separation and corresponding ordering
864
+ H = G.subgraph(V)
865
+ vsH, LH = vertex_separation(H, algorithm=algorithm,
866
+ cut_off=cut_off,
867
+ upper_bound=upper_bound,
868
+ verbose=verbose,
869
+ max_prefix_length=max_prefix_length,
870
+ max_prefix_number=max_prefix_number,
871
+ solver=solver,
872
+ integrality_tolerance=integrality_tolerance)
873
+
874
+ if vsH == -1:
875
+ # We have not been able to find a solution. This case
876
+ # happens when a too low upper bound is given.
877
+ return -1, []
878
+
879
+ # We update the vertex separation and ordering
880
+ vs = max(vs, vsH)
881
+ L.extend(LH)
882
+
883
+ # We also update the cut_off parameter that could speed up
884
+ # resolution for other components (used when algorithm=='BAB')
885
+ cut_off = max(cut_off, vs)
886
+
887
+ return vs, L
888
+
889
+ # We have a (strongly) connected graph and we call the desired algorithm
890
+ if algorithm == "exponential":
891
+ return vertex_separation_exp(G, verbose=verbose)
892
+
893
+ elif algorithm == "MILP":
894
+ return vertex_separation_MILP(G, solver=solver, verbose=verbose,
895
+ integrality_tolerance=integrality_tolerance)
896
+
897
+ elif algorithm == "BAB":
898
+ return vertex_separation_BAB(G, cut_off=cut_off, upper_bound=upper_bound, verbose=verbose,
899
+ max_prefix_length=max_prefix_length, max_prefix_number=max_prefix_number)
900
+
901
+ else:
902
+ raise ValueError('algorithm "{}" has not been implemented yet, please contribute'.format(algorithm))
903
+
904
+
905
+ ################################
906
+ # Exact exponential algorithms #
907
+ ################################
908
+
909
+ def vertex_separation_exp(G, verbose=False):
910
+ r"""
911
+ Return an optimal ordering of the vertices and its cost for
912
+ vertex-separation.
913
+
914
+ INPUT:
915
+
916
+ - ``G`` -- a Graph or a DiGraph
917
+
918
+ - ``verbose`` -- boolean (default: ``False``); whether to display
919
+ information on the computations
920
+
921
+ OUTPUT:
922
+
923
+ A pair ``(cost, ordering)`` representing the optimal ordering of the
924
+ vertices and its cost.
925
+
926
+ .. NOTE::
927
+
928
+ Because of its current implementation, this algorithm only works on
929
+ graphs on less than 32 vertices. This can be changed to 54 if necessary,
930
+ but 32 vertices already require 4GB of memory.
931
+
932
+ EXAMPLES:
933
+
934
+ The vertex separation of a circuit is equal to 1::
935
+
936
+ sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation_exp
937
+ sage: g = digraphs.Circuit(6)
938
+ sage: vertex_separation_exp(g)
939
+ (1, [0, 1, 2, 3, 4, 5])
940
+
941
+ TESTS:
942
+
943
+ Given anything else than a Graph or a DiGraph::
944
+
945
+ sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation_exp
946
+ sage: vertex_separation_exp(range(3))
947
+ Traceback (most recent call last):
948
+ ...
949
+ ValueError: the parameter must be a Graph or a DiGraph
950
+
951
+ Graphs with non-integer vertices::
952
+
953
+ sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation_exp
954
+ sage: D = digraphs.DeBruijn(2,3) # needs sage.combinat
955
+ sage: vertex_separation_exp(D) # needs sage.combinat
956
+ (2, ['000', '001', '100', '010', '101', '011', '110', '111'])
957
+
958
+ Given a too large graph::
959
+
960
+ sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation_exp
961
+ sage: vertex_separation_exp(graphs.PathGraph(50))
962
+ Traceback (most recent call last):
963
+ ...
964
+ ValueError: the graph should have at most 31 vertices
965
+ """
966
+ from sage.graphs.graph import Graph
967
+ from sage.graphs.digraph import DiGraph
968
+ if not isinstance(G, Graph) and not isinstance(G, DiGraph):
969
+ raise ValueError("the parameter must be a Graph or a DiGraph")
970
+
971
+ if G.order() >= 32:
972
+ raise ValueError("the graph should have at most 31 vertices")
973
+
974
+ cdef FastDigraph g = FastDigraph(G)
975
+
976
+ if verbose:
977
+ print("Memory allocation")
978
+ g.print_adjacency_matrix()
979
+
980
+ cdef unsigned int mem = 1 << g.n
981
+ cdef uint8_t * neighborhoods = <uint8_t *>check_malloc(mem)
982
+
983
+ memset(neighborhoods, <uint8_t> -1, mem)
984
+
985
+ cdef int i, k
986
+ for k in range(g.n):
987
+ if verbose:
988
+ print("Looking for a strategy of cost", str(k))
989
+
990
+ sig_check()
991
+ if exists(g, neighborhoods, 0, k) <= k:
992
+ break
993
+
994
+ if verbose:
995
+ print("... Found !")
996
+ print("Now computing the ordering")
997
+
998
+ cdef list order = find_order(g, neighborhoods, k)
999
+
1000
+ sig_free(neighborhoods)
1001
+
1002
+ return k, [g.int_to_vertices[i] for i in order]
1003
+
1004
+
1005
+ ###############################################################################
1006
+ # Actual algorithm, breadth-first search and updates of the costs of the sets #
1007
+ ###############################################################################
1008
+
1009
+ cdef inline int exists(FastDigraph g, uint8_t* neighborhoods, int current, int cost) noexcept:
1010
+ """
1011
+ Check whether an ordering with the given cost exists, and updates data in
1012
+ the neighborhoods array at the same time. See the module's documentation.
1013
+ """
1014
+ # If this is true, it means the set has not been evaluated yet
1015
+ if neighborhoods[current] == <uint8_t> -1:
1016
+ neighborhoods[current] = compute_out_neighborhood_cardinality(g, current)
1017
+
1018
+ # If the cost of this set is too high, there is no point in going further.
1019
+ # Same thing if the current set is the whole vertex set.
1020
+ if neighborhoods[current] > cost or (current == (1 << g.n) - 1):
1021
+ return neighborhoods[current]
1022
+
1023
+ # Minimum of the costs of the outneighbors
1024
+ cdef int mini = g.n
1025
+
1026
+ cdef int i
1027
+ cdef int next_set
1028
+
1029
+ for i in range(g.n):
1030
+ if (current >> i) & 1:
1031
+ continue
1032
+
1033
+ # For each of the out-neighbors next_set of current
1034
+ next_set = current | 1 << i
1035
+
1036
+ # Check whether there exists a cheap path toward {1..n}, and updated the
1037
+ # cost.
1038
+ mini = minimum(mini, exists(g, neighborhoods, next_set, cost))
1039
+
1040
+ # We have found a path !
1041
+ if mini <= cost:
1042
+ return mini
1043
+
1044
+ # Updating the cost of the current set with the minimum of the cost of its
1045
+ # outneighbors.
1046
+ neighborhoods[current] = mini
1047
+
1048
+ return neighborhoods[current]
1049
+
1050
+
1051
+ cdef list find_order(FastDigraph g, uint8_t* neighborhoods, int cost):
1052
+ """
1053
+ Return the ordering once we are sure it exists
1054
+ """
1055
+ cdef list ordering = []
1056
+ cdef int current = 0
1057
+ cdef int n = g.n
1058
+ cdef int i
1059
+
1060
+ while n:
1061
+ # We look for n vertices
1062
+ for i in range(g.n):
1063
+ if (current >> i) & 1:
1064
+ continue
1065
+
1066
+ # Find the next set with small cost (we know it exists)
1067
+ next_set = current | 1 << i
1068
+ if neighborhoods[next_set] <= cost:
1069
+ ordering.append(i)
1070
+ current = next_set
1071
+ break
1072
+
1073
+ # One less to find
1074
+ n -= 1
1075
+
1076
+ return ordering
1077
+
1078
+
1079
+ # Min/Max functions
1080
+
1081
+ cdef inline int minimum(int a, int b) noexcept:
1082
+ if a < b:
1083
+ return a
1084
+ else:
1085
+ return b
1086
+
1087
+
1088
+ cdef inline int maximum(int a, int b) noexcept:
1089
+ if a > b:
1090
+ return a
1091
+ else:
1092
+ return b
1093
+
1094
+
1095
+ #################################################################
1096
+ # Function for testing the validity of a linear vertex ordering #
1097
+ #################################################################
1098
+
1099
+ def is_valid_ordering(G, L):
1100
+ r"""
1101
+ Test if the linear vertex ordering `L` is valid for (di)graph `G`.
1102
+
1103
+ A linear ordering `L` of the vertices of a (di)graph `G` is valid if all
1104
+ vertices of `G` are in `L`, and if `L` contains no other vertex and no
1105
+ duplicated vertices.
1106
+
1107
+ INPUT:
1108
+
1109
+ - ``G`` -- a Graph or a DiGraph
1110
+
1111
+ - ``L`` -- an ordered list of the vertices of ``G``
1112
+
1113
+ OUTPUT:
1114
+
1115
+ Returns ``True`` if `L` is a valid vertex ordering for `G`, and ``False``
1116
+ otherwise.
1117
+
1118
+ EXAMPLES:
1119
+
1120
+ Path decomposition of a cycle::
1121
+
1122
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1123
+ sage: G = graphs.CycleGraph(6)
1124
+ sage: L = G.vertices(sort=True)
1125
+ sage: vertex_separation.is_valid_ordering(G, L)
1126
+ True
1127
+ sage: vertex_separation.is_valid_ordering(G, [1,2])
1128
+ False
1129
+
1130
+ TESTS:
1131
+
1132
+ Giving anything else than a Graph or a DiGraph::
1133
+
1134
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1135
+ sage: vertex_separation.is_valid_ordering(2, [])
1136
+ Traceback (most recent call last):
1137
+ ...
1138
+ ValueError: the input parameter must be a Graph or a DiGraph
1139
+
1140
+ Giving anything else than a list::
1141
+
1142
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1143
+ sage: G = graphs.CycleGraph(6)
1144
+ sage: vertex_separation.is_valid_ordering(G, {})
1145
+ Traceback (most recent call last):
1146
+ ...
1147
+ ValueError: the second parameter must be of type 'list'
1148
+ """
1149
+ from sage.graphs.graph import Graph
1150
+ from sage.graphs.digraph import DiGraph
1151
+ if not isinstance(G, Graph) and not isinstance(G, DiGraph):
1152
+ raise ValueError("the input parameter must be a Graph or a DiGraph")
1153
+ if not isinstance(L, list):
1154
+ raise ValueError("the second parameter must be of type 'list'")
1155
+
1156
+ return set(L) == set(G)
1157
+
1158
+
1159
+ ####################################################################
1160
+ # Measurement functions of the widths of some graph decompositions #
1161
+ ####################################################################
1162
+
1163
+ def width_of_path_decomposition(G, L):
1164
+ r"""
1165
+ Return the width of the path decomposition induced by the linear ordering
1166
+ `L` of the vertices of `G`.
1167
+
1168
+ If `G` is an instance of :mod:`Graph <sage.graphs.graph>`, this function
1169
+ returns the width `pw_L(G)` of the path decomposition induced by the linear
1170
+ ordering `L` of the vertices of `G`. If `G` is a :mod:`DiGraph
1171
+ <sage.graphs.digraph>`, it returns instead the width `vs_L(G)` of the
1172
+ directed path decomposition induced by the linear ordering `L` of the
1173
+ vertices of `G`, where
1174
+
1175
+ .. MATH::
1176
+
1177
+ vs_L(G) & = \max_{0\leq i< |V|-1} | N^+(L[:i])\setminus L[:i] |\\
1178
+ pw_L(G) & = \max_{0\leq i< |V|-1} | N(L[:i])\setminus L[:i] |\\
1179
+
1180
+ INPUT:
1181
+
1182
+ - ``G`` -- a Graph or a DiGraph
1183
+
1184
+ - ``L`` -- a linear ordering of the vertices of ``G``
1185
+
1186
+ EXAMPLES:
1187
+
1188
+ Path decomposition of a cycle::
1189
+
1190
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1191
+ sage: G = graphs.CycleGraph(6)
1192
+ sage: L = G.vertices(sort=True)
1193
+ sage: vertex_separation.width_of_path_decomposition(G, L)
1194
+ 2
1195
+
1196
+ Directed path decomposition of a circuit::
1197
+
1198
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1199
+ sage: G = digraphs.Circuit(6)
1200
+ sage: L = G.vertices(sort=True)
1201
+ sage: vertex_separation.width_of_path_decomposition(G, L)
1202
+ 1
1203
+
1204
+ TESTS:
1205
+
1206
+ Path decomposition of a BalancedTree::
1207
+
1208
+ sage: # needs networkx
1209
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1210
+ sage: G = graphs.BalancedTree(3,2)
1211
+ sage: pw, L = vertex_separation.path_decomposition(G)
1212
+ sage: pw == vertex_separation.width_of_path_decomposition(G, L)
1213
+ True
1214
+ sage: L.reverse()
1215
+ sage: pw == vertex_separation.width_of_path_decomposition(G, L)
1216
+ False
1217
+
1218
+ Directed path decomposition of a circuit::
1219
+
1220
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1221
+ sage: G = digraphs.Circuit(8)
1222
+ sage: vs, L = vertex_separation.vertex_separation(G)
1223
+ sage: vs == vertex_separation.width_of_path_decomposition(G, L)
1224
+ True
1225
+ sage: L = [0,4,6,3,1,5,2,7]
1226
+ sage: vs == vertex_separation.width_of_path_decomposition(G, L)
1227
+ False
1228
+
1229
+ Giving a wrong linear ordering::
1230
+
1231
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1232
+ sage: G = Graph()
1233
+ sage: vertex_separation.width_of_path_decomposition(G, ['a','b'])
1234
+ Traceback (most recent call last):
1235
+ ...
1236
+ ValueError: the input linear vertex ordering L is not valid for G
1237
+ """
1238
+ if not is_valid_ordering(G, L):
1239
+ raise ValueError("the input linear vertex ordering L is not valid for G")
1240
+
1241
+ neighbors = G.neighbors_out if G.is_directed() else G.neighbors
1242
+
1243
+ cdef int vsL = 0
1244
+ cdef set S = set()
1245
+ cdef set neighbors_of_S_in_V_minus_S = set()
1246
+
1247
+ for u in L:
1248
+
1249
+ # We remove u from the neighbors of S
1250
+ neighbors_of_S_in_V_minus_S.discard(u)
1251
+
1252
+ # We add vertex u to the set S
1253
+ S.add(u)
1254
+
1255
+ # We add the (out-)neighbors of u to the neighbors of S
1256
+ for v in neighbors(u):
1257
+ if v not in S:
1258
+ neighbors_of_S_in_V_minus_S.add(v)
1259
+
1260
+ # We update the cost of the vertex separation
1261
+ vsL = max(vsL, len(neighbors_of_S_in_V_minus_S))
1262
+
1263
+ return vsL
1264
+
1265
+
1266
+ ##########################################
1267
+ # MILP formulation for vertex separation #
1268
+ ##########################################
1269
+
1270
+ def _vertex_separation_MILP_formulation(G, integrality=False, solver=None):
1271
+ r"""
1272
+ MILP formulation of the vertex separation of `G` and the optimal ordering of its vertices.
1273
+
1274
+ This MILP is an improved version of the formulation proposed in [SP2010]_. See the
1275
+ :mod:`module's documentation <sage.graphs.graph_decompositions.vertex_separation>` for
1276
+ more details on this MILP formulation.
1277
+
1278
+ INPUT:
1279
+
1280
+ - ``G`` -- a Graph or a DiGraph
1281
+
1282
+ - ``integrality`` -- boolean (default: ``False``); specify if variables
1283
+ `x_v^t` and `u_v^t` must be integral or if they can be relaxed. This has
1284
+ no impact on the validity of the solution, but it is sometimes faster to
1285
+ solve the problem using binary variables only.
1286
+
1287
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
1288
+ Programming (MILP) solver to be used. If set to ``None``, the default one
1289
+ is used. For more information on MILP solvers and which default solver is
1290
+ used, see the method :meth:`solve
1291
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1292
+ :class:`MixedIntegerLinearProgram
1293
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1294
+
1295
+ OUTPUT: the :class:`~sage.numerical.mip.MixedIntegerLinearProgram`
1296
+
1297
+ - :class:`sage.numerical.mip.MIPVariable` objects ``x``, ``u``, ``y``, ``z``.
1298
+
1299
+ EXAMPLES::
1300
+
1301
+ sage: from sage.graphs.graph_decompositions.vertex_separation import _vertex_separation_MILP_formulation
1302
+ sage: G = digraphs.DeBruijn(2,3) # needs sage.combinat
1303
+ sage: p, x, u, y, z = _vertex_separation_MILP_formulation(G) # needs sage.combinat sage.numerical.mip
1304
+ sage: p # needs sage.combinat sage.numerical.mip
1305
+ Mixed Integer Program (minimization, 193 variables, 449 constraints)
1306
+ """
1307
+ from sage.graphs.graph import Graph
1308
+ from sage.graphs.digraph import DiGraph
1309
+ if not isinstance(G, Graph) and not isinstance(G, DiGraph):
1310
+ raise ValueError("the first input parameter must be a Graph or a DiGraph")
1311
+
1312
+ from sage.numerical.mip import MixedIntegerLinearProgram
1313
+ p = MixedIntegerLinearProgram(maximization=False, solver=solver)
1314
+
1315
+ # Declaration of variables.
1316
+ x = p.new_variable(binary=integrality, nonnegative=True)
1317
+ u = p.new_variable(binary=integrality, nonnegative=True)
1318
+ y = p.new_variable(binary=True)
1319
+ z = p.new_variable(integer=True, nonnegative=True)
1320
+
1321
+ N = G.order()
1322
+ V = list(G)
1323
+ neighbors_out = G.neighbors_out if G.is_directed() else G.neighbors
1324
+
1325
+ # (2) x[v,t] <= x[v,t+1] for all v in V, and for t:=0..N-2
1326
+ # (3) y[v,t] <= y[v,t+1] for all v in V, and for t:=0..N-2
1327
+ for v in V:
1328
+ for t in range(N - 1):
1329
+ p.add_constraint(x[v, t] - x[v, t + 1] <= 0)
1330
+ p.add_constraint(y[v, t] - y[v, t + 1] <= 0)
1331
+
1332
+ # (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
1333
+ for v in V:
1334
+ for w in neighbors_out(v):
1335
+ for t in range(N):
1336
+ p.add_constraint(y[v, t] - x[w, t] <= 0)
1337
+
1338
+ # (5) sum_{v in V} y[v,t] == t+1 for t:=0..N-1
1339
+ for t in range(N):
1340
+ p.add_constraint(p.sum(y[v, t] for v in V) == t + 1)
1341
+
1342
+ # (6) u[v,t] >= x[v,t]-y[v,t] for all v in V, and for all t:=0..N-1
1343
+ for v in V:
1344
+ for t in range(N):
1345
+ p.add_constraint(x[v, t] - y[v, t] - u[v, t] <= 0)
1346
+
1347
+ # (7) z >= sum_{v in V} u[v,t] for all t:=0..N-1
1348
+ for t in range(N):
1349
+ p.add_constraint(p.sum(u[v, t] for v in V) - z['z'] <= 0)
1350
+
1351
+ # (8)(9) 0 <= x[v,t] and u[v,t] <= 1
1352
+ if not integrality:
1353
+ for v in V:
1354
+ for t in range(N):
1355
+ p.add_constraint(x[v, t], min=0, max=1)
1356
+ p.add_constraint(u[v, t], min=0, max=1)
1357
+
1358
+ # (10) y[v,t] in {0,1}
1359
+ # already declared
1360
+
1361
+ # (11) 0 <= z <= |V|
1362
+ p.add_constraint(z['z'] <= N)
1363
+
1364
+ # (1) Minimize z
1365
+ p.set_objective(z['z'])
1366
+
1367
+ return p, x, u, y, z
1368
+
1369
+
1370
+ def vertex_separation_MILP(G, integrality=False, solver=None, verbose=0,
1371
+ *, integrality_tolerance=1e-3):
1372
+ r"""
1373
+ Compute the vertex separation of `G` and the optimal ordering of its
1374
+ vertices using an MILP formulation.
1375
+
1376
+ This function uses a mixed integer linear program (MILP) for determining an
1377
+ optimal layout for the vertex separation of `G`. This MILP is an improved
1378
+ version of the formulation proposed in [SP2010]_. See the :mod:`module's
1379
+ documentation <sage.graphs.graph_decompositions.vertex_separation>` for more
1380
+ details on this MILP formulation.
1381
+
1382
+ INPUT:
1383
+
1384
+ - ``G`` -- a Graph or a DiGraph
1385
+
1386
+ - ``integrality`` -- boolean (default: ``False``); specify if variables
1387
+ `x_v^t` and `u_v^t` must be integral or if they can be relaxed. This has
1388
+ no impact on the validity of the solution, but it is sometimes faster to
1389
+ solve the problem using binary variables only.
1390
+
1391
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
1392
+ Programming (MILP) solver to be used. If set to ``None``, the default one
1393
+ is used. For more information on MILP solvers and which default solver is
1394
+ used, see the method :meth:`solve
1395
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1396
+ :class:`MixedIntegerLinearProgram
1397
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1398
+
1399
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity. Set
1400
+ to 0 by default, which means quiet.
1401
+
1402
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
1403
+ over an inexact base ring; see
1404
+ :meth:`MixedIntegerLinearProgram.get_values`.
1405
+
1406
+ OUTPUT:
1407
+
1408
+ A pair ``(cost, ordering)`` representing the optimal ordering of the
1409
+ vertices and its cost.
1410
+
1411
+ EXAMPLES:
1412
+
1413
+ Vertex separation of a De Bruijn digraph::
1414
+
1415
+ sage: # needs sage.combinat
1416
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1417
+ sage: G = digraphs.DeBruijn(2,3)
1418
+ sage: vs, L = vertex_separation.vertex_separation_MILP(G); vs # needs sage.numerical.mip
1419
+ 2
1420
+ sage: vs == vertex_separation.width_of_path_decomposition(G, L) # needs sage.numerical.mip
1421
+ True
1422
+ sage: vse, Le = vertex_separation.vertex_separation(G); vse
1423
+ 2
1424
+
1425
+ The vertex separation of a circuit is 1::
1426
+
1427
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1428
+ sage: G = digraphs.Circuit(6)
1429
+ sage: vs, L = vertex_separation.vertex_separation_MILP(G); vs # needs sage.numerical.mip
1430
+ 1
1431
+
1432
+ TESTS:
1433
+
1434
+ Comparison with exponential algorithm::
1435
+
1436
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1437
+ sage: for i in range(10): # needs sage.numerical.mip
1438
+ ....: G = digraphs.RandomDirectedGNP(10, 0.2)
1439
+ ....: ve, le = vertex_separation.vertex_separation(G)
1440
+ ....: vm, lm = vertex_separation.vertex_separation_MILP(G)
1441
+ ....: if ve != vm:
1442
+ ....: raise ValueError("the solution is not optimal")
1443
+
1444
+ Comparison with different values of the integrality parameter::
1445
+
1446
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1447
+ sage: for i in range(10): # long time (11s on sage.math, 2012), needs sage.numerical.mip
1448
+ ....: G = digraphs.RandomDirectedGNP(10, 0.2)
1449
+ ....: va, la = vertex_separation.vertex_separation_MILP(G, integrality=False)
1450
+ ....: vb, lb = vertex_separation.vertex_separation_MILP(G, integrality=True)
1451
+ ....: if va != vb:
1452
+ ....: raise ValueError("the integrality parameter changes the result")
1453
+
1454
+ Giving anything else than a Graph or a DiGraph::
1455
+
1456
+ sage: from sage.graphs.graph_decompositions import vertex_separation
1457
+ sage: vertex_separation.vertex_separation_MILP([]) # needs sage.numerical.mip
1458
+ Traceback (most recent call last):
1459
+ ...
1460
+ ValueError: the first input parameter must be a Graph or a DiGraph
1461
+ """
1462
+ from sage.numerical.mip import MIPSolverException
1463
+
1464
+ p, _, _, y, z = _vertex_separation_MILP_formulation(G, integrality=integrality, solver=solver)
1465
+ N = G.order()
1466
+ V = list(G)
1467
+
1468
+ try:
1469
+ _ = p.solve(log=verbose)
1470
+ except MIPSolverException:
1471
+ if integrality:
1472
+ raise ValueError("unbounded or unexpected error")
1473
+ else:
1474
+ raise ValueError("unbounded or unexpected error, try with 'integrality = True'")
1475
+
1476
+ taby = p.get_values(y, convert=bool, tolerance=integrality_tolerance)
1477
+ vs = p.get_values(z, convert=True, tolerance=integrality_tolerance)['z']
1478
+ # since exactly one vertex is processed per step, we can reconstruct the sequence
1479
+ seq = []
1480
+ seen = set()
1481
+ for t in range(N):
1482
+ for v in V:
1483
+ if taby[v, t] and v not in seen:
1484
+ seq.append(v)
1485
+ seen.add(v)
1486
+ break
1487
+
1488
+ return vs, seq
1489
+
1490
+
1491
+ ##########################################
1492
+ # Branch and Bound for vertex separation #
1493
+ ##########################################
1494
+
1495
+ def vertex_separation_BAB(G,
1496
+ cut_off=None,
1497
+ upper_bound=None,
1498
+ max_prefix_length=20,
1499
+ max_prefix_number=10**6,
1500
+ verbose=False):
1501
+ r"""
1502
+ Branch and Bound algorithm for the vertex separation.
1503
+
1504
+ This method implements the branch and bound algorithm for the vertex
1505
+ separation of directed graphs and the pathwidth of undirected graphs
1506
+ proposed in [CMN2014]_. The implementation is valid for both Graph and
1507
+ DiGraph. See the documentation of the
1508
+ :mod:`~sage.graphs.graph_decompositions.vertex_separation` module.
1509
+
1510
+ INPUT:
1511
+
1512
+ - ``G`` -- a Graph or a DiGraph
1513
+
1514
+ - ``cut_off`` -- integer (default: ``None``); bound to consider in the
1515
+ branch and bound algorithm. This allows us to stop the search as soon as a
1516
+ solution with width at most ``cut_off`` is found, if any. If this bound
1517
+ cannot be reached, the best solution found is returned, unless a too low
1518
+ ``upper_bound`` is given.
1519
+
1520
+ - ``upper_bound`` -- integer (default: ``None``); if specified, the
1521
+ algorithm searches for a solution with ``width < upper_bound``. It helps
1522
+ cutting branches. However, if the given upper bound is too low, the
1523
+ algorithm may not be able to find a solution.
1524
+
1525
+ - ``max_prefix_length`` -- integer (default: 20); limits the length of the
1526
+ stored prefixes to prevent storing too many prefixes
1527
+
1528
+ - ``max_prefix_number`` -- integer (default: 10**6); upper bound on the
1529
+ number of stored prefixes used to prevent using too much memory
1530
+
1531
+ - ``verbose`` -- boolean (default: ``False``); display some information when
1532
+ set to ``True``
1533
+
1534
+ OUTPUT: ``width`` -- the computed vertex separation
1535
+
1536
+ - ``seq`` -- an ordering of the vertices of width ``width``
1537
+
1538
+ EXAMPLES:
1539
+
1540
+ The algorithm is valid for the vertex separation::
1541
+
1542
+ sage: from sage.graphs.graph_decompositions import vertex_separation as VS
1543
+ sage: D = digraphs.RandomDirectedGNP(15, .2)
1544
+ sage: vb, seqb = VS.vertex_separation_BAB(D)
1545
+ sage: vd, seqd = VS.vertex_separation_exp(D)
1546
+ sage: vb == vd
1547
+ True
1548
+ sage: vb == VS.width_of_path_decomposition(D, seqb)
1549
+ True
1550
+
1551
+ The vertex separation of a `N\times N` grid is `N`::
1552
+
1553
+ sage: from sage.graphs.graph_decompositions import vertex_separation as VS
1554
+ sage: G = graphs.Grid2dGraph(4,4)
1555
+ sage: vs, seq = VS.vertex_separation_BAB(G); vs
1556
+ 4
1557
+ sage: vs == VS.width_of_path_decomposition(G, seq)
1558
+ True
1559
+
1560
+ The vertex separation of a `N\times M` grid with `N<M` is `N`::
1561
+
1562
+ sage: from sage.graphs.graph_decompositions import vertex_separation as VS
1563
+ sage: G = graphs.Grid2dGraph(3,5)
1564
+ sage: vs, seq = VS.vertex_separation_BAB(G); vs
1565
+ 3
1566
+ sage: vs == VS.width_of_path_decomposition(G, seq)
1567
+ True
1568
+
1569
+ The vertex separation of circuit of order `N\geq 2` is 1::
1570
+
1571
+ sage: from sage.graphs.graph_decompositions import vertex_separation as VS
1572
+ sage: D = digraphs.Circuit(10)
1573
+ sage: vs, seq = VS.vertex_separation_BAB(D); vs
1574
+ 1
1575
+ sage: vs == VS.width_of_path_decomposition(D, seq)
1576
+ True
1577
+
1578
+ The vertex separation of cycle of order `N\geq 3` is 2::
1579
+
1580
+ sage: from sage.graphs.graph_decompositions import vertex_separation as VS
1581
+ sage: G = graphs.CycleGraph(10)
1582
+ sage: vs, seq = VS.vertex_separation_BAB(G); vs
1583
+ 2
1584
+
1585
+ The vertex separation of ``MycielskiGraph(5)`` is 10::
1586
+
1587
+ sage: from sage.graphs.graph_decompositions import vertex_separation as VS
1588
+ sage: G = graphs.MycielskiGraph(5)
1589
+ sage: vs, seq = VS.vertex_separation_BAB(G); vs
1590
+ 10
1591
+
1592
+ Searching for any solution with width less or equal to ``cut_off``::
1593
+
1594
+ sage: from sage.graphs.graph_decompositions import vertex_separation as VS
1595
+ sage: G = graphs.MycielskiGraph(5)
1596
+ sage: VS.vertex_separation_BAB(G, cut_off=11)[0] <= 11
1597
+ True
1598
+ sage: VS.vertex_separation_BAB(G, cut_off=10)[0] <= 10
1599
+ True
1600
+ sage: VS.vertex_separation_BAB(G, cut_off=9)[0] <= 9
1601
+ False
1602
+
1603
+ Testing for the existence of a solution with width strictly less than ``upper_bound``::
1604
+
1605
+ sage: from sage.graphs.graph_decompositions import vertex_separation as VS
1606
+ sage: G = graphs.MycielskiGraph(5)
1607
+ sage: vs, seq = VS.vertex_separation_BAB(G, upper_bound=11); vs
1608
+ 10
1609
+ sage: vs, seq = VS.vertex_separation_BAB(G, upper_bound=10); vs
1610
+ -1
1611
+ sage: vs, seq = VS.vertex_separation_BAB(G, cut_off=11, upper_bound=10); vs
1612
+ -1
1613
+
1614
+ Changing the parameters of the prefix storage::
1615
+
1616
+ sage: from sage.graphs.graph_decompositions import vertex_separation as VS
1617
+ sage: G = graphs.MycielskiGraph(5)
1618
+ sage: vs, seq = VS.vertex_separation_BAB(G, max_prefix_length=0); vs
1619
+ 10
1620
+ sage: vs, seq = VS.vertex_separation_BAB(G, max_prefix_number=5); vs
1621
+ 10
1622
+ sage: vs, seq = VS.vertex_separation_BAB(G, max_prefix_number=0); vs
1623
+ 10
1624
+
1625
+ TESTS:
1626
+
1627
+ Giving anything else than a Graph or a DiGraph::
1628
+
1629
+ sage: from sage.graphs.graph_decompositions import vertex_separation as VS
1630
+ sage: VS.vertex_separation_BAB(range(5))
1631
+ Traceback (most recent call last):
1632
+ ...
1633
+ ValueError: the input parameter must be a Graph or a DiGraph
1634
+
1635
+ Giving an empty Graph or DiGraph::
1636
+
1637
+ sage: from sage.graphs.graph_decompositions import vertex_separation as VS
1638
+ sage: VS.vertex_separation_BAB(Graph())
1639
+ (0, [])
1640
+ sage: VS.vertex_separation_BAB(DiGraph())
1641
+ (0, [])
1642
+
1643
+ Giving a too low upper bound::
1644
+
1645
+ sage: from sage.graphs.graph_decompositions import vertex_separation as VS
1646
+ sage: VS.vertex_separation_BAB(digraphs.Circuit(3), upper_bound=0)
1647
+ Traceback (most recent call last):
1648
+ ...
1649
+ ValueError: the input upper bound must be at least 1
1650
+ """
1651
+ from sage.graphs.graph import Graph
1652
+ from sage.graphs.digraph import DiGraph
1653
+ if not isinstance(G, DiGraph) and not isinstance(G, Graph):
1654
+ raise ValueError("the input parameter must be a Graph or a DiGraph")
1655
+
1656
+ cdef int n = G.order()
1657
+ if not n:
1658
+ return 0, []
1659
+
1660
+ cut_off = 0 if cut_off is None else cut_off
1661
+ upper_bound = n if upper_bound is None else upper_bound
1662
+ if upper_bound < 1:
1663
+ raise ValueError("the input upper bound must be at least 1")
1664
+
1665
+ # ==> Allocate and initialize some data structures
1666
+
1667
+ # We use a binary matrix to store the (di)graph. This way the neighborhood
1668
+ # of a vertex is stored in one bitset.
1669
+ cdef binary_matrix_t H
1670
+ cdef int i
1671
+ cdef list int_to_vertex = list(G)
1672
+ cdef dict vertex_to_int = {v: i for i, v in enumerate(int_to_vertex)}
1673
+ dense_graph_init(H, G, translation=vertex_to_int)
1674
+
1675
+ # We need 2 bitsets here + 3 per call to vertex_separation_BAB_C, so overall
1676
+ # 3*n + 2. We use another binary matrix as a pool of bitsets.
1677
+ cdef binary_matrix_t bm_pool
1678
+ binary_matrix_init(bm_pool, 3 * n + 2, n)
1679
+
1680
+ cdef int * prefix = <int *>sig_malloc(n * sizeof(int))
1681
+ cdef int * positions = <int *>sig_malloc(n * sizeof(int))
1682
+ if not prefix or not positions:
1683
+ sig_free(prefix)
1684
+ sig_free(positions)
1685
+ binary_matrix_free(H)
1686
+ binary_matrix_free(bm_pool)
1687
+ raise MemoryError("unable to allocate data structures")
1688
+
1689
+ cdef list best_seq = list(range(n))
1690
+ for i in range(n):
1691
+ prefix[i] = i
1692
+ positions[i] = i
1693
+
1694
+ cdef int width = upper_bound
1695
+ cdef list order = []
1696
+ cdef set prefix_storage = set()
1697
+
1698
+ try:
1699
+ # ==> Call the cython method
1700
+ sig_on()
1701
+ width = vertex_separation_BAB_C(H=H,
1702
+ n=n,
1703
+ prefix=prefix,
1704
+ positions=positions,
1705
+ best_seq=best_seq,
1706
+ level=0,
1707
+ b_prefix=bm_pool.rows[3 * n],
1708
+ b_prefix_and_neighborhood=bm_pool.rows[3 * n + 1],
1709
+ cut_off=cut_off,
1710
+ upper_bound=upper_bound,
1711
+ current_cost=0,
1712
+ bm_pool=bm_pool,
1713
+ prefix_storage=prefix_storage,
1714
+ max_prefix_length=max_prefix_length,
1715
+ max_prefix_number=max_prefix_number,
1716
+ verbose=verbose)
1717
+
1718
+ sig_off()
1719
+
1720
+ # ==> Build the final ordering
1721
+ order = [int_to_vertex[best_seq[i]] for i in range(n)]
1722
+
1723
+ finally:
1724
+ if verbose:
1725
+ print('Stored prefixes: {}'.format(len(prefix_storage)))
1726
+ sig_free(prefix)
1727
+ sig_free(positions)
1728
+ binary_matrix_free(H)
1729
+ binary_matrix_free(bm_pool)
1730
+
1731
+ return (width if width < upper_bound else -1), order
1732
+
1733
+
1734
+ cdef inline _my_invert_positions(int *prefix, int *positions, int pos_a, int pos_b):
1735
+ """
1736
+ Permute vertices at positions ``pos_a`` and ``pos_b`` in array ``prefix``,
1737
+ and record the new positions in array ``positions``.
1738
+ """
1739
+ if pos_a != pos_b:
1740
+ positions[prefix[pos_a]], positions[prefix[pos_b]] = positions[prefix[pos_b]], positions[prefix[pos_a]]
1741
+ prefix[pos_a], prefix[pos_b] = prefix[pos_b], prefix[pos_a]
1742
+
1743
+
1744
+ cdef int vertex_separation_BAB_C(binary_matrix_t H,
1745
+ int n,
1746
+ int *prefix,
1747
+ int *positions,
1748
+ list best_seq,
1749
+ int level,
1750
+ bitset_t b_prefix,
1751
+ bitset_t b_prefix_and_neighborhood,
1752
+ int cut_off,
1753
+ int upper_bound,
1754
+ int current_cost,
1755
+ binary_matrix_t bm_pool,
1756
+ set prefix_storage,
1757
+ int max_prefix_length,
1758
+ int max_prefix_number,
1759
+ bint verbose) noexcept:
1760
+ r"""
1761
+ Branch and Bound algorithm for the process number and the vertex separation.
1762
+
1763
+ INPUT:
1764
+
1765
+ - ``H`` -- a binary matrix storing the adjacency of the (di)graph
1766
+
1767
+ - ``n`` -- integer; the number of vertices of the (di)graph
1768
+
1769
+ - ``prefix`` -- array of `n` integers; contains a permutation of the
1770
+ vertices. The vertices forming the current prefix under consideration are
1771
+ stored in cells ``[0, level - 1]``.
1772
+
1773
+ - ``positions`` -- array of `n` integers; associates to each vertex its
1774
+ index in array ``prefix``
1775
+
1776
+ - ``best_seq`` -- array of `n` integers; stores the best ordering found so
1777
+ far
1778
+
1779
+ - ``level`` -- integer; specifies the length of the current prefix
1780
+
1781
+ - ``b_prefix`` -- bitset of size `n`; records the vertices in the current
1782
+ prefix (in cells ``[0,level-1]``)
1783
+
1784
+ - ``b_prefix_and_neighborhood`` -- bitset of size `n`; records the
1785
+ vertices in the current prefix and the vertices in its neighborhood
1786
+
1787
+ - ``cut_off`` -- integer; bound to consider in the branch and bound
1788
+ algorithm. This allows us to stop the search as soon as a solution with
1789
+ width at most ``cut_off`` is found, if any.
1790
+
1791
+ - ``upper_bound`` -- integer; the algorithm searches for a solution with
1792
+ ``width < upper_bound``. It helps cutting branches. Each time a new
1793
+ solution is found, the upper bound is reduced.
1794
+
1795
+ - ``bm_pool`` -- a binary matrix with `3*n+2` rows of size `n`; each rows is
1796
+ a bitset of size `n`. This data structure is used as a pool of
1797
+ initialized bitsets. Each call of this method needs 3 bitsets for local
1798
+ operations, so it uses rows ``[3 * level, 3 * level + 2]``.
1799
+
1800
+ - ``prefix_storage`` -- set; used to store prefixes
1801
+
1802
+ - ``max_prefix_length`` -- integer; maximum length of the stored prefixes to
1803
+ prevent storing too many prefixes
1804
+
1805
+ - ``max_prefix_number`` -- integer; upper bound on the number of stored
1806
+ prefixes used to prevent using too much memory
1807
+
1808
+ - ``verbose`` -- boolean (default: ``False``); display some information when
1809
+ set to ``True``
1810
+ """
1811
+ cdef int i
1812
+
1813
+ # ==> Test termination
1814
+
1815
+ if level == n:
1816
+ if current_cost < upper_bound:
1817
+ for i in range(n):
1818
+ best_seq[i] = prefix[i]
1819
+ if verbose:
1820
+ print("New upper bound: {}".format(current_cost))
1821
+
1822
+ return current_cost
1823
+
1824
+ cdef int delta_i, j, v, select_it
1825
+ cdef list delta = list()
1826
+ cdef int loc_level = level
1827
+
1828
+ # ==> Allocate local data structures
1829
+
1830
+ cdef bitset_s *loc_b_prefix = bm_pool.rows[3 * level]
1831
+ cdef bitset_s *loc_b_pref_and_neigh = bm_pool.rows[3 * level + 1]
1832
+ cdef bitset_s *b_tmp = bm_pool.rows[3 * level + 2]
1833
+ bitset_copy(loc_b_prefix, b_prefix)
1834
+ bitset_copy(loc_b_pref_and_neigh, b_prefix_and_neighborhood)
1835
+
1836
+ # ==> Greedy steps
1837
+ #
1838
+ # We extend the current prefix with all vertices u such that either
1839
+ # (i) All out-neighbors of u are in the prefix or in its out-neighborhood
1840
+ # (ii) or u is an out-neighbor of the prefix and all but one of its
1841
+ # out-neighbors are in the prefix or in its out-neighborhood.
1842
+
1843
+ select_it = 0
1844
+ i = loc_level
1845
+ while i < n:
1846
+
1847
+ j = prefix[i]
1848
+
1849
+ if bitset_issubset(H.rows[j], loc_b_pref_and_neigh):
1850
+ # (i) Vertex j is such that all its out-neighbors are in the prefix
1851
+ # or in its out-neighborhood (so in loc_b_pref_and_neigh).
1852
+ bitset_add(loc_b_pref_and_neigh, j)
1853
+ select_it = 1
1854
+
1855
+ elif bitset_in(loc_b_pref_and_neigh, j) and not bitset_in(loc_b_prefix, j):
1856
+ bitset_difference(b_tmp, H.rows[j], loc_b_pref_and_neigh)
1857
+ if bitset_len(b_tmp) == 1:
1858
+ # (ii) Vertex j is an out-neighbor of the prefix and all but one
1859
+ # of its out-neighbors are in the prefix or in its
1860
+ # out-neighborhood.
1861
+ v = bitset_first(b_tmp)
1862
+ bitset_add(loc_b_pref_and_neigh, v)
1863
+ select_it = 1
1864
+
1865
+ if select_it:
1866
+ # We add j to the prefix and update neighborhoods
1867
+ _my_invert_positions(prefix, positions, i, loc_level)
1868
+ loc_level += 1
1869
+ bitset_add(loc_b_prefix, j)
1870
+ select_it = 0
1871
+ # We search for vertices that can now be selected
1872
+ i = loc_level
1873
+ else:
1874
+ i += 1
1875
+
1876
+ # ==> Test termination
1877
+ #
1878
+ if loc_level == n:
1879
+ if current_cost < upper_bound:
1880
+ for i in range(n):
1881
+ best_seq[i] = prefix[i]
1882
+ if verbose:
1883
+ print("New upper bound: {}".format(current_cost))
1884
+
1885
+ return current_cost
1886
+
1887
+ # ==> Test if the prefix is in prefix_storage
1888
+ #
1889
+ # The set S of vertices of a prefix P is in prefix_storage if the branch
1890
+ # with prefix P is such that c(P)<\min_{L\in{\cal L}_P(V)} c(L). In such
1891
+ # case, there is no need to continue exploration for the current branch.
1892
+ cdef frozenset frozen_prefix
1893
+
1894
+ if loc_level <= max_prefix_length:
1895
+ frozen_prefix = frozenset(prefix[i] for i in range(loc_level))
1896
+ if frozen_prefix in prefix_storage:
1897
+ return upper_bound
1898
+
1899
+ # ==> Sort and Prune
1900
+ #
1901
+ # We compute for each remaining vertex v a lower bound on the width of any
1902
+ # ordering with prefix prefix+v
1903
+ for i in range(loc_level, n):
1904
+ j = prefix[i]
1905
+ bitset_union(b_tmp, loc_b_pref_and_neigh, H.rows[j])
1906
+ bitset_difference(b_tmp, b_tmp, loc_b_prefix)
1907
+ bitset_discard(b_tmp, j)
1908
+ delta_i = bitset_len(b_tmp)
1909
+ if delta_i < upper_bound:
1910
+ delta.append((delta_i, j))
1911
+
1912
+ delta.sort()
1913
+
1914
+ # ==> Recursion
1915
+ for delta_i, i in delta:
1916
+
1917
+ delta_i = max(current_cost, delta_i)
1918
+
1919
+ if delta_i >= upper_bound:
1920
+ break
1921
+
1922
+ # We extend the current prefix with vertex i and explore the branch
1923
+ bitset_union(b_tmp, loc_b_pref_and_neigh, H.rows[i])
1924
+ bitset_discard(b_tmp, i)
1925
+ _my_invert_positions(prefix, positions, positions[i], loc_level)
1926
+ bitset_add(loc_b_prefix, i)
1927
+
1928
+ cost_i = vertex_separation_BAB_C(H=H,
1929
+ n=n,
1930
+ prefix=prefix,
1931
+ positions=positions,
1932
+ best_seq=best_seq,
1933
+ level=loc_level + 1,
1934
+ b_prefix=loc_b_prefix,
1935
+ b_prefix_and_neighborhood=b_tmp,
1936
+ cut_off=cut_off,
1937
+ upper_bound=upper_bound,
1938
+ current_cost=delta_i,
1939
+ bm_pool=bm_pool,
1940
+ prefix_storage=prefix_storage,
1941
+ max_prefix_length=max_prefix_length,
1942
+ max_prefix_number=max_prefix_number,
1943
+ verbose=verbose)
1944
+
1945
+ bitset_discard(loc_b_prefix, i)
1946
+
1947
+ if cost_i < upper_bound:
1948
+ upper_bound = cost_i
1949
+ if upper_bound <= cut_off:
1950
+ # We are satisfied with current solution.
1951
+ break
1952
+
1953
+ # ==> Update prefix_storage
1954
+ #
1955
+ # If the prefix P is such that c(P)<\min_{L\in{\cal L}_P(V)} c(L), no other
1956
+ # prefix P' on the same set S=V(P) of vertices can lead to a better
1957
+ # solution.
1958
+ if (loc_level <= max_prefix_length
1959
+ and current_cost < upper_bound
1960
+ and len(prefix_storage) < max_prefix_number):
1961
+ prefix_storage.add(frozen_prefix)
1962
+
1963
+ return upper_bound