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,1996 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # cython: binding=True
3
+ r"""
4
+ Tree decompositions
5
+
6
+ This module implements tree-decomposition methods.
7
+
8
+ A tree-decomposition of a graph `G = (V, E)` is a pair `(X, T)`, where `X=\{X_1,
9
+ X_2, \ldots, X_t\}` is a family of subsets of `V`, usually called *bags*, and
10
+ `T` is a tree of order `t` whose nodes are the subsets `X_i` satisfying the
11
+ following properties:
12
+
13
+ - The union of all sets `X_i` equals `V`. That is, each vertex of the graph `G`
14
+ is associated with at least one tree node.
15
+
16
+ - For every edge `(v, w)` in the graph, there is a subset `X_i` that contains
17
+ both `v` and `w`. That is, each edge of the graph `G` appears in a tree node.
18
+
19
+ - The nodes associated with vertex `v \in V` form a connected subtree of
20
+ `T`. That is, if `X_i` and `X_j` both contain a vertex `v \in V`, then all
21
+ nodes `X_k` of the tree in the (unique) path between `X_i` and `X_j` contain
22
+ `v` as well, and we have `X_i \cap X_j \subseteq X_k`.
23
+
24
+ The *width* of a tree decomposition is the size of the largest set `X_i` minus
25
+ one, i.e., `\max_{X_i \in X} |X_i| - 1`, and the *treewidth* `tw(G)` of a graph
26
+ `G` is the minimum width among all possible tree decompositions of `G`. Observe
27
+ that, the size of the largest set is diminished by one in order to make the
28
+ treewidth of a tree equal to one.
29
+
30
+ The *length* of a tree decomposition, as proposed in [DG2006]_, is the maximum
31
+ *diameter* in `G` of its bags, where the diameter of a bag `X_i` is the largest
32
+ distance in `G` between the vertices in `X_i` (i.e., `\max_{u, v \in X_i}
33
+ \dist_G(u, v)`). The *treelength* `tl(G)` of a graph `G` is the minimum length
34
+ among all possible tree decompositions of `G`.
35
+
36
+ While deciding whether a graph has treelength 1 can be done in linear time
37
+ (equivalent to deciding if the graph is chordal), deciding if it has treelength
38
+ at most `k` for any fixed constant `k \leq 2` is NP-complete [Lokshtanov2009]_.
39
+
40
+ Treewidth and treelength are different measures of tree-likeness. In particular,
41
+ trees have treewidth and treelength 1::
42
+
43
+ sage: T = graphs.RandomTree(20)
44
+ sage: T.treewidth() # needs cliquer
45
+ 1
46
+ sage: T.treelength()
47
+ 1
48
+
49
+ The treewidth of a cycle is 2 and its treelength is `\lceil n/3 \rceil`::
50
+
51
+ sage: [graphs.CycleGraph(n).treewidth() for n in range(3, 11)] # needs cliquer
52
+ [2, 2, 2, 2, 2, 2, 2, 2]
53
+ sage: [graphs.CycleGraph(n).treelength() for n in range(3, 11)] # needs cliquer
54
+ [1, 2, 2, 2, 3, 3, 3, 4]
55
+
56
+ The treewidth of a clique is `n-1` and its treelength is 1::
57
+
58
+ sage: [graphs.CompleteGraph(n).treewidth() for n in range(3, 11)] # needs cliquer
59
+ [2, 3, 4, 5, 6, 7, 8, 9]
60
+ sage: [graphs.CompleteGraph(n).treelength() for n in range(3, 11)] # needs cliquer
61
+ [1, 1, 1, 1, 1, 1, 1, 1]
62
+
63
+
64
+ .. SEEALSO::
65
+
66
+ - :wikipedia:`Tree_decomposition`
67
+ - :wikipedia:`Treewidth`
68
+
69
+
70
+ **This module contains the following methods**
71
+
72
+ .. csv-table::
73
+ :class: contentstable
74
+ :widths: 30, 70
75
+ :delim: |
76
+
77
+ :meth:`treewidth` | Compute the treewidth of `G` (and provide a decomposition).
78
+ :meth:`treelength` | Compute the treelength of `G` (and provide a decomposition).
79
+ :meth:`make_nice_tree_decomposition` | Return a *nice* tree decomposition (TD) of the TD ``tree_decomp``.
80
+ :meth:`label_nice_tree_decomposition` | Return a nice tree decomposition with nodes labelled accordingly.
81
+ :meth:`is_valid_tree_decomposition` | Check whether `T` is a valid tree-decomposition for `G`.
82
+ :meth:`reduced_tree_decomposition` | Return a reduced tree-decomposition of `T`.
83
+ :meth:`width_of_tree_decomposition` | Return the width of the tree decomposition `T` of `G`.
84
+ :meth:`length_of_tree_decomposition` | Return the length of the tree decomposition `T` of `G`.
85
+
86
+
87
+ .. TODO:
88
+
89
+ - Approximation of treelength based on :meth:`~sage.graphs.graph.Graph.lex_M`
90
+ - Approximation of treelength based on BFS Layering
91
+
92
+
93
+ Methods
94
+ -------
95
+ """
96
+ # ****************************************************************************
97
+ # Copyright (C) 2020 David Coudert <david.coudert@inria.fr>
98
+ #
99
+ # This program is free software: you can redistribute it and/or modify
100
+ # it under the terms of the GNU General Public License as published by
101
+ # the Free Software Foundation, either version 2 of the License, or
102
+ # (at your option) any later version.
103
+ # https://www.gnu.org/licenses/
104
+ # ****************************************************************************
105
+
106
+ from sage.sets.set import Set
107
+ from sage.misc.cachefunc import cached_function
108
+ from sage.sets.disjoint_set import DisjointSet
109
+ from sage.rings.infinity import Infinity
110
+ from sage.graphs.distances_all_pairs cimport c_distances_all_pairs
111
+ from cysignals.memory cimport sig_calloc, sig_free
112
+
113
+ from itertools import chain
114
+ from collections.abc import Iterable
115
+
116
+
117
+ def is_valid_tree_decomposition(G, T):
118
+ r"""
119
+ Check whether `T` is a valid tree-decomposition for `G`.
120
+
121
+ INPUT:
122
+
123
+ - ``G`` -- a sage Graph
124
+
125
+ - ``T`` -- a tree decomposition, i.e., a tree whose vertices are the bags
126
+ (subsets of vertices) of the decomposition
127
+
128
+ EXAMPLES::
129
+
130
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import is_valid_tree_decomposition
131
+ sage: K = graphs.CompleteGraph(4)
132
+ sage: T = Graph()
133
+ sage: T.add_vertex(Set(K))
134
+ sage: is_valid_tree_decomposition(K, T)
135
+ True
136
+
137
+ sage: G = graphs.RandomGNP(10, .2)
138
+ sage: T = G.treewidth(certificate=True) # needs cliquer
139
+ sage: is_valid_tree_decomposition(G, T) # needs cliquer
140
+ True
141
+
142
+ The union of the bags is the set of vertices of `G`::
143
+
144
+ sage: G = graphs.PathGraph(4)
145
+ sage: T = G.treewidth(certificate=True) # needs cliquer
146
+ sage: _ = G.add_vertex()
147
+ sage: is_valid_tree_decomposition(G, T) # needs cliquer
148
+ False
149
+
150
+ Each edge of `G` is contained in a bag::
151
+
152
+ sage: G = graphs.PathGraph(4)
153
+ sage: T = G.treewidth(certificate=True) # needs cliquer
154
+ sage: G.add_edge(0, 3)
155
+ sage: is_valid_tree_decomposition(G, T) # needs cliquer
156
+ False
157
+
158
+ The bags containing a vertex `v` form a subtree of `T`::
159
+
160
+ sage: G = graphs.PathGraph(4)
161
+ sage: X1, X2, X3 = Set([0, 1]), Set([1, 2]), Set([2, 3])
162
+ sage: T = Graph([(X1, X3), (X3, X2)])
163
+ sage: is_valid_tree_decomposition(G, T)
164
+ False
165
+
166
+ TESTS:
167
+
168
+ Check that both parameters are sage graphs::
169
+
170
+ sage: is_valid_tree_decomposition("foo", Graph())
171
+ Traceback (most recent call last):
172
+ ...
173
+ ValueError: the first parameter must be a sage Graph
174
+ sage: is_valid_tree_decomposition(Graph(), "foo")
175
+ Traceback (most recent call last):
176
+ ...
177
+ ValueError: the second parameter must be a sage Graph
178
+
179
+ Check that `T` is a tree::
180
+
181
+ sage: is_valid_tree_decomposition(Graph(), Graph(2))
182
+ Traceback (most recent call last):
183
+ ...
184
+ ValueError: the second parameter must be a tree
185
+
186
+ The vertices of `T` must be iterables::
187
+
188
+ sage: is_valid_tree_decomposition(Graph(1), Graph(1))
189
+ Traceback (most recent call last):
190
+ ...
191
+ ValueError: the vertices of T must be iterables
192
+
193
+ Small cases::
194
+
195
+ sage: is_valid_tree_decomposition(Graph(), Graph())
196
+ True
197
+ sage: is_valid_tree_decomposition(Graph(1), Graph())
198
+ False
199
+ """
200
+ from sage.graphs.graph import Graph
201
+ if not isinstance(G, Graph):
202
+ raise ValueError("the first parameter must be a sage Graph")
203
+ if not isinstance(T, Graph):
204
+ raise ValueError("the second parameter must be a sage Graph")
205
+ if not T:
206
+ return not G
207
+ elif not T.is_tree():
208
+ raise ValueError("the second parameter must be a tree")
209
+
210
+ for X in T:
211
+ if not isinstance(X, Iterable):
212
+ raise ValueError("the vertices of T must be iterables")
213
+
214
+ # 1. The union of the bags equals V
215
+ if set(G) != set(chain(*T)):
216
+ return False
217
+
218
+ # 2. Each edge of G is contained in a bag
219
+ vertex_to_bags = {u: set() for u in G}
220
+ for Xi in T:
221
+ for u in Xi:
222
+ vertex_to_bags[u].add(Xi)
223
+
224
+ for u, v in G.edge_iterator(labels=False):
225
+ if all(v not in Xi for Xi in vertex_to_bags[u]):
226
+ return False
227
+
228
+ # 3. The bags containing a vertex v form a connected subset of T
229
+ for X in vertex_to_bags.values():
230
+ D = DisjointSet(X)
231
+ for Xi in X:
232
+ for Xj in T.neighbor_iterator(Xi):
233
+ if Xj in X:
234
+ D.union(Xi, Xj)
235
+ if D.number_of_subsets() > 1:
236
+ return False
237
+
238
+ return True
239
+
240
+
241
+ def reduced_tree_decomposition(T):
242
+ r"""
243
+ Return a reduced tree-decomposition of `T`.
244
+
245
+ We merge all edges between two sets `S` and `S'` where `S` is a subset of
246
+ `S'`. To do so, we use a simple union-find data structure to record merge
247
+ operations and the good sets.
248
+
249
+ .. WARNING::
250
+
251
+ This method assumes that the vertices of the input tree `T` are hashable
252
+ and have attribute ``issuperset``, e.g., ``frozenset`` or
253
+ :class:`~sage.sets.set.Set_object_enumerated`.
254
+
255
+ INPUT:
256
+
257
+ - ``T`` -- a tree-decomposition
258
+
259
+ EXAMPLES::
260
+
261
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import reduced_tree_decomposition
262
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import is_valid_tree_decomposition
263
+ sage: G = graphs.PathGraph(3)
264
+ sage: T = Graph()
265
+ sage: T.add_path([Set([0]), Set([0, 1]), Set([1]), Set([1, 2]), Set([2])])
266
+ sage: T.order()
267
+ 5
268
+ sage: is_valid_tree_decomposition(G, T)
269
+ True
270
+ sage: T2 = reduced_tree_decomposition(T)
271
+ sage: is_valid_tree_decomposition(G, T2)
272
+ True
273
+ sage: T2.order()
274
+ 2
275
+
276
+ TESTS::
277
+
278
+ sage: # needs cliquer
279
+ sage: G = graphs.PathGraph(3)
280
+ sage: T = G.treewidth(certificate=True)
281
+ sage: is_valid_tree_decomposition(G, T)
282
+ True
283
+ sage: T == reduced_tree_decomposition(T)
284
+ True
285
+ sage: G = Graph(1)
286
+ sage: T = G.treewidth(certificate=True)
287
+ sage: T.order()
288
+ 1
289
+ sage: T == reduced_tree_decomposition(T)
290
+ True
291
+ """
292
+ if T.order() < 2:
293
+ return T
294
+
295
+ def get_ancestor(ancestor, u):
296
+ if ancestor[u] == u:
297
+ return u
298
+ ancestor[u] = get_ancestor(ancestor, ancestor[u])
299
+ return ancestor[u]
300
+
301
+ ancestor = {u: u for u in T}
302
+ for u, v in T.edge_iterator(labels=False):
303
+ u = get_ancestor(ancestor, u)
304
+ v = get_ancestor(ancestor, v)
305
+ if u == v:
306
+ continue
307
+ elif u.issuperset(v):
308
+ ancestor[v] = u
309
+ elif v.issuperset(u):
310
+ ancestor[u] = v
311
+
312
+ from sage.graphs.graph import Graph
313
+ H = Graph(multiedges=False, name="Reduced tree-decomposition of {}".format(T.name()))
314
+ for u, v in T.edge_iterator(labels=False):
315
+ u = get_ancestor(ancestor, u)
316
+ v = get_ancestor(ancestor, v)
317
+ if u != v:
318
+ H.add_edge(u, v)
319
+ return H
320
+
321
+
322
+ def width_of_tree_decomposition(G, T, check=True):
323
+ r"""
324
+ Return the width of the tree decomposition `T` of `G`.
325
+
326
+ The width of a tree-decomposition is the size of the largest bag minus
327
+ 1. The empty graph and a graph of order 1 have treewidth 0.
328
+
329
+ INPUT:
330
+
331
+ - ``G`` -- a sage Graph
332
+
333
+ - ``T`` -- a tree-decomposition for `G`
334
+
335
+ - ``check`` -- boolean (default: ``True``); whether to check that the
336
+ tree-decomposition `T` is valid for `G`
337
+
338
+ EXAMPLES::
339
+
340
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import width_of_tree_decomposition
341
+ sage: G = graphs.PathGraph(3)
342
+ sage: T = G.treewidth(certificate=True) # needs cliquer
343
+ sage: width_of_tree_decomposition(G, T, check=True) # needs cliquer
344
+ 1
345
+
346
+ TESTS::
347
+
348
+ sage: G = Graph()
349
+ sage: T = G.treewidth(certificate=True) # needs cliquer
350
+ sage: width_of_tree_decomposition(G, T, check=True) # needs cliquer
351
+ 0
352
+ sage: width_of_tree_decomposition(Graph(1), T, check=True) # needs cliquer
353
+ Traceback (most recent call last):
354
+ ...
355
+ ValueError: the tree-decomposition is not valid for this graph
356
+ """
357
+ if check and not is_valid_tree_decomposition(G, T):
358
+ raise ValueError("the tree-decomposition is not valid for this graph")
359
+
360
+ if T:
361
+ return max(0, max(len(u) for u in T) - 1)
362
+ return 0
363
+
364
+
365
+ def _from_tree_decompositions_of_atoms_to_tree_decomposition(T_atoms, cliques):
366
+ r"""
367
+ Return a tree-decomposition formed by the tree-decompositions of the atoms.
368
+
369
+ This is a helper method to avoid duplicated code.
370
+
371
+ This method builds the tree decomposition of the graph by connecting the
372
+ tree decompositions of its atoms. This is done in an order that is
373
+ consistent with the order of the atoms and cliques returned by method
374
+ :meth:`~Graph.atoms_and_clique_separators`. More precisely, the first clique
375
+ separates the first atom from the rest of the graph (call G1 this part of
376
+ the graph), the second clique separates (in G1) the second atom from the
377
+ rest of the graph G1, etc. So we merge the tree decompositions in the
378
+ reverse order of the atoms.
379
+
380
+ INPUT:
381
+
382
+ - ``T_atoms`` -- list of tree-decompositions
383
+
384
+ - ``cliques`` -- list of clique separators
385
+
386
+ EXAMPLES:
387
+
388
+ Indirect doctest::
389
+
390
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import is_valid_tree_decomposition
391
+ sage: G = graphs.Grid2dGraph(2, 3)
392
+ sage: T = G.treewidth(algorithm='sage', certificate=True) # needs cliquer
393
+ sage: is_valid_tree_decomposition(G, T) # needs cliquer
394
+ True
395
+
396
+ TESTS::
397
+
398
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import _from_tree_decompositions_of_atoms_to_tree_decomposition
399
+ sage: _from_tree_decompositions_of_atoms_to_tree_decomposition([], [Set(range(2))])
400
+ Traceback (most recent call last):
401
+ ...
402
+ ValueError: the number of cliques must be one less than the number of tree-decompositions of atoms
403
+ """
404
+ if len(T_atoms) != len(cliques) + 1:
405
+ raise ValueError("the number of cliques must be one less than the "
406
+ "number of tree-decompositions of atoms")
407
+
408
+ T = T_atoms[-1]
409
+ for i in range(len(cliques) - 1, -1, -1):
410
+ A = T_atoms[i]
411
+ C = cliques[i]
412
+
413
+ # We search for a vertex in A and T containing clique C
414
+ ua, ut = None, None
415
+ for u in A:
416
+ if u.issuperset(C):
417
+ ua = u
418
+ break
419
+ for u in T:
420
+ if u.issuperset(C):
421
+ ut = u
422
+ break
423
+ if ua and ut:
424
+ # We merge T and A
425
+ T.add_vertices(A)
426
+ T.add_edges(A.edges(sort=False))
427
+ T.add_edge(ua, ut)
428
+ else:
429
+ # This should never happen
430
+ raise RuntimeError("something goes wrong. Please report the issue "
431
+ "to sage-devel@googlegroups.com")
432
+
433
+ return T
434
+
435
+
436
+ def treewidth(g, k=None, kmin=None, certificate=False, algorithm=None, nice=False):
437
+ r"""
438
+ Compute the treewidth of `g` (and provide a decomposition).
439
+
440
+ INPUT:
441
+
442
+ - ``g`` -- a Sage Graph
443
+
444
+ - ``k`` -- integer (default: ``None``); indicates the width to be
445
+ considered. When ``k`` is an integer, the method checks that the graph has
446
+ treewidth `\leq k`. If ``k`` is ``None`` (default), the method computes
447
+ the optimal treewidth.
448
+
449
+ - ``kmin`` -- integer (default: ``None``); when specified, search for a
450
+ tree-decomposition of width at least ``kmin``. This parameter is useful
451
+ when the graph can be decomposed into atoms. This parameter is ignored
452
+ when ``k`` is not ``None`` or when ``algorithm == 'tdlib'``.
453
+
454
+ - ``certificate`` -- boolean (default: ``False``); whether to return the
455
+ tree-decomposition itself
456
+
457
+ - ``algorithm`` -- whether to use ``'sage'`` or ``'tdlib'`` (requires the
458
+ installation of the :ref:`spkg_sagemath_tdlib` package). The default behaviour is to use
459
+ 'tdlib' if it is available, and Sage's own algorithm when it is not.
460
+
461
+ - ``nice`` -- boolean (default: ``False``); whether or not to return the
462
+ nice tree decomposition, provided ``certificate`` is ``True``
463
+
464
+ OUTPUT:
465
+
466
+ ``g.treewidth()`` returns treewidth of the graph ``g``.
467
+
468
+ When ``k`` is specified, it returns ``False`` if there is no tree
469
+ decomposition of width `\leq k`, and ``True`` otherwise.
470
+
471
+ When ``certificate=True``, the tree decomposition is returned.
472
+
473
+ When ``nice=True``, the nice tree decomposition is returned.
474
+
475
+ ALGORITHM:
476
+
477
+ This function virtually explores the graph of all pairs ``(vertex_cut, cc)``,
478
+ where ``vertex_cut`` is a vertex cut of the graph of cardinality `\leq k+1`,
479
+ and ``connected_component`` is a connected component of the graph induced by
480
+ ``G-vertex_cut``.
481
+
482
+ We deduce that the pair ``(vertex_cut, cc)`` is feasible with treewidth `k`
483
+ if ``cc`` is empty, or if a vertex ``v`` from ``vertex_cut`` can be replaced
484
+ with a vertex from ``cc``, such that the pair ``(vertex_cut+v,cc-v)`` is
485
+ feasible.
486
+
487
+ .. NOTE::
488
+
489
+ The implementation would be much faster if ``cc``, the argument of the
490
+ recursive function, was a bitset. It would also be very nice to not copy
491
+ the graph in order to compute connected components, for this is really a
492
+ waste of time.
493
+
494
+ .. SEEALSO::
495
+
496
+ :meth:`~sage.graphs.graph_decompositions.vertex_separation.path_decomposition`
497
+ computes the pathwidth of a graph. See also the
498
+ :mod:`~sage.graphs.graph_decompositions.vertex_separation` module.
499
+
500
+ EXAMPLES:
501
+
502
+ The PetersenGraph has treewidth 4::
503
+
504
+ sage: petersen = graphs.PetersenGraph()
505
+ sage: petersen.treewidth(algorithm='sage') # needs cliquer
506
+ 4
507
+ sage: petersen.treewidth(algorithm='sage', certificate=True) # needs cliquer
508
+ Tree decomposition: Graph on 6 vertices
509
+
510
+ The PetersenGraph has treewidth 4 (with ``tdlib``)::
511
+
512
+ sage: petersen = graphs.PetersenGraph()
513
+ sage: petersen.treewidth(algorithm='tdlib') # optional - tdlib
514
+ 4
515
+ sage: petersen.treewidth(algorithm='tdlib', certificate=True) # optional - tdlib
516
+ Tree decomposition: Graph on 6 vertices
517
+
518
+ Nice tree decomposition of the PetersenGraph has 28 nodes::
519
+
520
+ sage: petersen = graphs.PetersenGraph()
521
+ sage: petersen.treewidth(algorithm='sage', certificate=True, nice=True) # needs cliquer
522
+ Nice tree decomposition of Tree decomposition: Graph on 28 vertices
523
+ sage: petersen.treewidth(algorithm='tdlib', certificate=True, nice=True) # optional - tdlib
524
+ Nice tree decomposition of Tree decomposition: Graph on 28 vertices
525
+
526
+ The treewidth of a 2-dimensional grid is its smallest side::
527
+
528
+ sage: graphs.Grid2dGraph(2,5).treewidth() # needs cliquer
529
+ 2
530
+ sage: graphs.Grid2dGraph(3,5).treewidth() # needs cliquer
531
+ 3
532
+
533
+ When parameter ``kmin`` is specified, the method searches for a
534
+ tree-decomposition of width at least ``kmin``::
535
+
536
+ sage: g = graphs.PetersenGraph()
537
+ sage: g.treewidth() # needs cliquer
538
+ 4
539
+ sage: g.treewidth(kmin=2, algorithm='sage') # needs cliquer
540
+ 4
541
+ sage: g.treewidth(kmin=g.order(), certificate=True, algorithm='sage') # needs cliquer
542
+ Tree decomposition: Graph on 1 vertex
543
+
544
+ TESTS::
545
+
546
+ sage: # needs cliquer
547
+ sage: g = graphs.PathGraph(3)
548
+ sage: g.treewidth()
549
+ 1
550
+ sage: g = 2*graphs.PathGraph(3)
551
+ sage: g.treewidth()
552
+ 1
553
+ sage: g.treewidth(certificate=True)
554
+ Tree decomposition: Graph on 4 vertices
555
+ sage: g.treewidth(2)
556
+ True
557
+ sage: g.treewidth(1)
558
+ True
559
+ sage: Graph(1).treewidth()
560
+ 0
561
+ sage: Graph(0).treewidth()
562
+ -1
563
+ sage: g = graphs.PetersenGraph()
564
+ sage: g.treewidth(k=2)
565
+ False
566
+ sage: g.treewidth(k=6)
567
+ True
568
+ sage: g.treewidth(k=6, kmin=2)
569
+ True
570
+ sage: g.treewidth(certificate=True).is_tree()
571
+ True
572
+ sage: g.treewidth(k=3, certificate=True)
573
+ False
574
+ sage: T = g.treewidth(k=4, certificate=True)
575
+ sage: T
576
+ Tree decomposition: Graph on 6 vertices
577
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import is_valid_tree_decomposition
578
+ sage: is_valid_tree_decomposition(g, T)
579
+ True
580
+
581
+ All edges do appear (:issue:`17893`)::
582
+
583
+ sage: # needs cliquer
584
+ sage: from itertools import combinations
585
+ sage: g = graphs.PathGraph(10)
586
+ sage: td = g.treewidth(certificate=True)
587
+ sage: for bag in td:
588
+ ....: g.delete_edges(list(combinations(bag,2)))
589
+ sage: g.size()
590
+ 0
591
+
592
+ :issue:`19358`::
593
+
594
+ sage: g = Graph()
595
+ sage: for i in range(3):
596
+ ....: for j in range(2):
597
+ ....: g.add_path([i,(i,j),(i+1)%3])
598
+ sage: g.treewidth() # needs cliquer
599
+ 2
600
+
601
+ The decomposition is a tree (:issue:`23546`)::
602
+
603
+ sage: # needs cliquer
604
+ sage: g = Graph({0:[1,2], 3:[4,5]})
605
+ sage: t = g.treewidth(certificate=True)
606
+ sage: t.is_tree()
607
+ True
608
+ sage: vertices = set()
609
+ sage: for s in t:
610
+ ....: vertices = vertices.union(s)
611
+ sage: vertices == set(g)
612
+ True
613
+
614
+ Check that the use of atoms and clique separators is correct
615
+ (:issue:`30993`)::
616
+
617
+ sage: # needs cliquer
618
+ sage: g = 2 * graphs.Grid2dGraph(2, 3)
619
+ sage: g.treewidth(algorithm='sage')
620
+ 2
621
+ sage: g.treewidth(algorithm='sage', certificate=True)
622
+ Tree decomposition: Graph on 8 vertices
623
+ sage: g.treewidth(algorithm='sage', certificate=True, kmin=4)
624
+ Tree decomposition: Graph on 4 vertices
625
+
626
+ Check that :issue:`38159` is fixed ::
627
+
628
+ sage: G = Graph('I~~}vPlr_')
629
+ sage: G.treewidth(algorithm='sage') == G.treewidth(algorithm='tdlib') # optional - tdlib, needs cliquer
630
+ True
631
+
632
+ Trivially true::
633
+
634
+ sage: graphs.PetersenGraph().treewidth(k=35) # needs cliquer
635
+ True
636
+ sage: graphs.PetersenGraph().treewidth(k=35, certificate=True) # needs cliquer
637
+ Tree decomposition: Graph on 1 vertex
638
+
639
+ Bad input::
640
+
641
+ sage: graphs.PetersenGraph().treewidth(k=-3) # needs cliquer
642
+ Traceback (most recent call last):
643
+ ...
644
+ ValueError: k(=-3) must be a nonnegative integer
645
+ """
646
+ # Check Input
647
+ if algorithm not in [None, 'tdlib', 'sage']:
648
+ raise ValueError("'algorithm' must be equal to 'tdlib', 'sage', or None")
649
+
650
+ try:
651
+ import sage.graphs.graph_decompositions.tdlib as tdlib
652
+ tdlib_found = True
653
+ except ImportError:
654
+ tdlib_found = False
655
+
656
+ if algorithm is None:
657
+ algorithm = 'tdlib' if tdlib_found else 'sage'
658
+
659
+ if (k is not None) and k < 0:
660
+ raise ValueError(f"k(={k}) must be a nonnegative integer")
661
+
662
+ # Silly cases
663
+ from sage.graphs.graph import Graph
664
+ if not g.order():
665
+ if certificate:
666
+ return Graph()
667
+ return -1 if k is None else True
668
+
669
+ if k is not None and k >= g.order() - 1:
670
+ if certificate:
671
+ return Graph({Set(g): []}, name="Tree decomposition")
672
+ return True
673
+
674
+ kmin = 0 if kmin is None else kmin
675
+ if k is None and kmin >= g.order() - 1:
676
+ if certificate:
677
+ return Graph({Set(g): []}, name="Tree decomposition")
678
+ return kmin
679
+
680
+ # TDLIB
681
+ if algorithm == 'tdlib':
682
+ if not tdlib_found:
683
+ from sage.features import FeatureNotPresentError
684
+ from sage.features.tdlib import Tdlib
685
+ raise FeatureNotPresentError(Tdlib())
686
+
687
+ tree_decomp = tdlib.treedecomposition_exact(g, -1 if k is None else k)
688
+ width = tdlib.get_width(tree_decomp)
689
+
690
+ if certificate:
691
+ if k is None or width <= k:
692
+ return make_nice_tree_decomposition(g, tree_decomp) if nice else tree_decomp
693
+ return False
694
+ return width if k is None else width <= k
695
+
696
+ # The treewidth of a graph is the maximum over its atoms. So, we decompose
697
+ # the graph by clique minimal separators, compute the treewidth of each of
698
+ # its atoms, and combine the results.
699
+ # This decomposition also deals with disconnected cases.
700
+ atoms, cliques = g.atoms_and_clique_separators()
701
+ if cliques:
702
+ # If we do not need the tree decomposition
703
+ if not certificate:
704
+ if k is None:
705
+ for a in atoms:
706
+ kmin = max(kmin, g.subgraph(a).treewidth(algorithm=algorithm, kmin=kmin))
707
+ return kmin
708
+ elif max(len(c) for c in cliques) - 1 > k:
709
+ return False
710
+ else:
711
+ return all(g.subgraph(a).treewidth(algorithm=algorithm, k=k) for a in atoms)
712
+
713
+ # Otherwise, compute the tree decomposition of each atom
714
+ T = []
715
+ for a in atoms:
716
+ ga = g.subgraph(a)
717
+ Ta = ga.treewidth(algorithm=algorithm, certificate=True, kmin=kmin)
718
+ kmin = max(kmin, width_of_tree_decomposition(ga, Ta, check=False))
719
+ T.append(Ta)
720
+
721
+ # Merge the resulting trees
722
+ tree_decomp = _from_tree_decompositions_of_atoms_to_tree_decomposition(T, cliques)
723
+
724
+ return make_nice_tree_decomposition(g, tree_decomp) if nice else tree_decomp
725
+
726
+ # Forcing k to be defined
727
+ if k is None:
728
+ for i in range(max(kmin, g.clique_number() - 1, min(g.degree())), g.order()):
729
+ ans = g.treewidth(algorithm=algorithm, k=i, certificate=certificate, nice=nice)
730
+ if ans:
731
+ return ans if certificate else i
732
+
733
+ # This is the recursion described in the method's documentation. All
734
+ # computations are cached, and depends on the pair ``cut,
735
+ # connected_component`` only.
736
+ #
737
+ # It returns either a boolean or the corresponding tree-decomposition, as a
738
+ # list of edges between vertex cuts (as it is done for the complete
739
+ # tree-decomposition at the end of the main function.
740
+ @cached_function
741
+ def rec(cut, cc):
742
+ # Easy cases
743
+ if len(cut) > k:
744
+ return False
745
+ if len(cc) + len(cut) <= k + 1:
746
+ return [(cut, cut.union(cc))] if certificate else True
747
+
748
+ # We explore all possible extensions of the cut
749
+ for v in cc:
750
+
751
+ # New cuts and connected components, with v respectively added
752
+ # and removed
753
+ cutv = cut.union([v])
754
+ ccv = cc.difference([v])
755
+
756
+ # The values returned by the recursive calls.
757
+ sons = []
758
+
759
+ # Removing v may have disconnected cc. We iterate on its
760
+ # connected components
761
+ for cci in g.subgraph(ccv).connected_components(sort=False):
762
+
763
+ # The recursive subcalls. We remove on-the-fly the vertices
764
+ # from the cut which play no role in separating the
765
+ # connected component from the rest of the graph.
766
+ reduced_cut = frozenset([x for x in cutv
767
+ if any(xx in cci for xx in g.neighbor_iterator(x))])
768
+ son = rec(reduced_cut, frozenset(cci))
769
+ if not son:
770
+ break
771
+
772
+ if certificate:
773
+ sons.extend(son)
774
+ sons.append((cut, cutv))
775
+ sons.append((cutv, reduced_cut))
776
+
777
+ # Weird Python syntax which is useful once in a lifetime : if break
778
+ # was never called in the loop above, we return "sons".
779
+ else:
780
+ return sons if certificate else True
781
+
782
+ return False
783
+
784
+ # Main call to rec function, i.e. rec({v}, V-{v})
785
+ V = list(g)
786
+ v = frozenset([V.pop()])
787
+ TD = rec(v, frozenset(V))
788
+
789
+ if TD is False:
790
+ return False
791
+
792
+ if not certificate:
793
+ return True
794
+
795
+ # Building the tree-decomposition graph. Its vertices are cuts of the
796
+ # decomposition, and there is an edge from a cut C1 to a cut C2 if C2 is an
797
+ # immediate subcall of C1
798
+ tree_decomp = Graph()
799
+ tree_decomp.add_edges(((Set(x), Set(y)) for x, y in TD), loops=False)
800
+
801
+ # The tree-decomposition may contain a lot of useless nodes.
802
+ # We merge all edges between two sets S, S' where S is a subset of S'
803
+ tree_decomp = reduced_tree_decomposition(tree_decomp)
804
+
805
+ tree_decomp.name("Tree decomposition")
806
+ if nice:
807
+ tree_decomp = make_nice_tree_decomposition(g, tree_decomp)
808
+
809
+ return tree_decomp
810
+
811
+
812
+ def make_nice_tree_decomposition(graph, tree_decomp):
813
+ r"""
814
+ Return a *nice* tree decomposition (TD) of the TD ``tree_decomp``.
815
+
816
+ See page 161 of [CFKLMPPS15]_ for a description of the nice tree decomposition.
817
+
818
+ A *nice* TD `NT` is a rooted tree with four types of nodes:
819
+
820
+ - *Leaf* nodes have no children and bag size 1;
821
+ - *Introduce* nodes have one child: If `v \in NT` is an introduce node and
822
+ `w \in NT` its child, then `Bag(v) = Bag(w) \cup \{ x \}`, where `x` is the
823
+ introduced node;
824
+ - *Forget* nodes have one child: If `v \in NT` is a forget node and
825
+ `w \in NT` its child, then `Bag(v) = Bag(w) \setminus \{ x \}`, where `x` is the
826
+ forgotten node;
827
+ - *Join* nodes have two children, both identical to the parent.
828
+
829
+ INPUT:
830
+
831
+ - ``graph`` -- a Sage graph
832
+
833
+ - ``tree_decomp`` -- a tree decomposition
834
+
835
+ OUTPUT: a nice tree decomposition
836
+
837
+ .. WARNING::
838
+
839
+ This method assumes that the vertices of the input tree ``tree_decomp``
840
+ are hashable and have attribute ``issuperset``, e.g., ``frozenset`` or
841
+ :class:`~sage.sets.set.Set_object_enumerated_with_category`.
842
+
843
+ EXAMPLES::
844
+
845
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import make_nice_tree_decomposition
846
+ sage: petersen = graphs.PetersenGraph()
847
+ sage: petersen_TD = petersen.treewidth(certificate=True) # needs cliquer
848
+ sage: make_nice_tree_decomposition(petersen, petersen_TD) # needs cliquer
849
+ Nice tree decomposition of Tree decomposition: Graph on 28 vertices
850
+
851
+ ::
852
+
853
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import make_nice_tree_decomposition
854
+ sage: cherry = graphs.CompleteBipartiteGraph(1, 2)
855
+ sage: cherry_TD = cherry.treewidth(certificate=True) # needs cliquer
856
+ sage: make_nice_tree_decomposition(cherry, cherry_TD) # needs cliquer
857
+ Nice tree decomposition of Tree decomposition: Graph on 7 vertices
858
+
859
+ ::
860
+
861
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import make_nice_tree_decomposition
862
+ sage: bip_one_four = graphs.CompleteBipartiteGraph(1, 4)
863
+ sage: bip_one_four_TD = bip_one_four.treewidth(certificate=True) # needs cliquer
864
+ sage: make_nice_tree_decomposition(bip_one_four, bip_one_four_TD) # needs cliquer
865
+ Nice tree decomposition of Tree decomposition: Graph on 15 vertices
866
+
867
+ Check that :issue:`36843` is fixed::
868
+
869
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import make_nice_tree_decomposition
870
+ sage: triangle = graphs.CompleteGraph(3)
871
+ sage: triangle_TD = triangle.treewidth(certificate=True) # needs cliquer
872
+ sage: make_nice_tree_decomposition(triangle, triangle_TD) # needs cliquer
873
+ Nice tree decomposition of Tree decomposition: Graph on 7 vertices
874
+
875
+ ::
876
+
877
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import make_nice_tree_decomposition
878
+ sage: graph = graphs.CompleteBipartiteGraph(2, 5)
879
+ sage: graph_TD = graph.treewidth(certificate=True) # needs cliquer
880
+ sage: make_nice_tree_decomposition(graph, graph_TD) # needs cliquer
881
+ Nice tree decomposition of Tree decomposition: Graph on 25 vertices
882
+
883
+ ::
884
+
885
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import make_nice_tree_decomposition
886
+ sage: empty_graph = graphs.EmptyGraph()
887
+ sage: tree_decomp = empty_graph.treewidth(certificate=True) # needs cliquer
888
+ sage: len(make_nice_tree_decomposition(empty_graph, tree_decomp)) # needs cliquer
889
+ 0
890
+
891
+ ::
892
+
893
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import make_nice_tree_decomposition
894
+ sage: singleton = graphs.CompleteGraph(1)
895
+ sage: tree_decomp = singleton.treewidth(certificate=True) # needs cliquer
896
+ sage: make_nice_tree_decomposition(singleton, tree_decomp) # needs cliquer
897
+ Nice tree decomposition of Tree decomposition: Graph on 3 vertices
898
+
899
+ ::
900
+
901
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import make_nice_tree_decomposition
902
+ sage: an_edge = graphs.CompleteGraph(2)
903
+ sage: tree_decomp = an_edge.treewidth(certificate=True) # needs cliquer
904
+ sage: make_nice_tree_decomposition(an_edge, tree_decomp) # needs cliquer
905
+ Nice tree decomposition of Tree decomposition: Graph on 5 vertices
906
+ """
907
+ if not is_valid_tree_decomposition(graph, tree_decomp):
908
+ raise ValueError("input must be a valid tree decomposition for this graph")
909
+
910
+ name = f"Nice tree decomposition of {tree_decomp.name()}"
911
+ from sage.graphs.graph import Graph
912
+ if not tree_decomp:
913
+ return Graph(name=name)
914
+
915
+ # Step 1: Ensure the tree is directed and has a root
916
+ # Choose a root and orient the edges from root-to-leaves direction
917
+ #
918
+ # Testing <= 1 for the special case when one bag containing all vertices
919
+ leaves = [u for u in tree_decomp if tree_decomp.degree(u) <= 1]
920
+
921
+ from sage.graphs.digraph import DiGraph
922
+ if len(leaves) == 1:
923
+ root = leaves[0]
924
+ directed_tree = DiGraph(tree_decomp)
925
+ else:
926
+ root = leaves.pop()
927
+
928
+ directed_tree = DiGraph(tree_decomp.breadth_first_search(start=root, edges=True),
929
+ format='list_of_edges')
930
+
931
+ # Relabel the graph in range (0, |tree_decomp| - 1)
932
+ bags_to_int = directed_tree.relabel(inplace=True, return_map=True)
933
+ # Get the new name of the root node
934
+ root = bags_to_int[root]
935
+ # Force bags to be of type Set to simplify code
936
+ bag = {ui: Set(u) for u, ui in bags_to_int.items()}
937
+
938
+ # Step 2: Add the root node and the leaf nodes, with empty bags
939
+ # To each leaf node of `directed_tree`, we add a child with empty bag.
940
+ # We also add a new root with empty bag.
941
+ root, old_root = directed_tree.add_vertex(), root
942
+ directed_tree.add_edge(root, old_root)
943
+ bag[root] = Set()
944
+ for vi, u in enumerate(leaves, start=root + 1):
945
+ directed_tree.add_edge(bags_to_int[u], vi)
946
+ bag[vi] = Set()
947
+
948
+ # Step 3: Ensure that each node of directed_tree has at most 2 children.
949
+ # If a node has more than 2 children, introduce new nodes to
950
+ # make sure each node has at most 2 children:
951
+ #
952
+ # If v has k > 2 children (w_1, w_2, ..., w_k), we disconnect (w_1, ..., w_{k-1})
953
+ # from v, and introduce k - 2 new nodes (u_1, u_2, ..., u_{k-2}).
954
+ # We then let w_i be the children of u_i for 1 <= i <= k - 2.
955
+ # We also let w_{k-1} be the second child of u_{k-2}, and
956
+ # u_i the second child of u_{i-1}.
957
+ # Finally, we let u_1 the second child of u.
958
+ # Each node u_i has the same bag as u.
959
+
960
+ # We need to call list(...) since we modify directed_tree
961
+ for ui in list(directed_tree):
962
+ if directed_tree.out_degree(ui) > 2:
963
+ children = directed_tree.neighbors_out(ui)
964
+ children.pop() # one vertex remains a child of ui
965
+
966
+ directed_tree.delete_edges((ui, vi) for vi in children)
967
+
968
+ new_nodes = [directed_tree.add_vertex() for _ in range(len(children) - 1)]
969
+
970
+ directed_tree.add_edge(ui, new_nodes[0])
971
+ directed_tree.add_path(new_nodes)
972
+ directed_tree.add_edges(zip(new_nodes, children))
973
+ directed_tree.add_edge(new_nodes[-1], children[-1])
974
+
975
+ bag.update((vi, bag[ui]) for vi in new_nodes)
976
+
977
+ # Step 4: If current vertex v has two children w1 and w2,
978
+ # then bag[v] == bag[w1] == bag[w2]
979
+ for current_node in list(directed_tree):
980
+ if directed_tree.out_degree(current_node) < 2:
981
+ continue
982
+ for neighbor in directed_tree.neighbor_out_iterator(current_node):
983
+ if bag[current_node] != bag[neighbor]:
984
+ directed_tree.delete_edge(current_node, neighbor)
985
+ new_node = directed_tree.add_vertex()
986
+ directed_tree.add_path([current_node, new_node, neighbor])
987
+ bag[new_node] = bag[current_node]
988
+
989
+ # Step 5: If the node v has only one child, then it is either an introduce
990
+ # node or a forget node.
991
+ def add_path_of_intro_nodes(u, v):
992
+ """
993
+ Replace the arc (u, v) by a path of introduce nodes.
994
+ """
995
+ if len(bag[u]) + 1 == len(bag[v]):
996
+ return
997
+
998
+ diff = list(bag[v] - bag[u])
999
+ diff.pop()
1000
+
1001
+ last_node = u
1002
+ for w in diff:
1003
+ new_node = directed_tree.add_vertex()
1004
+ bag[new_node] = bag[last_node].union(Set((w,)))
1005
+ directed_tree.add_edge(last_node, new_node)
1006
+ last_node = new_node
1007
+
1008
+ directed_tree.add_edge(last_node, v)
1009
+ directed_tree.delete_edge(u, v)
1010
+
1011
+ def add_path_of_forget_nodes(u, v):
1012
+ """
1013
+ Replace the arc (u, v) by a path of forget nodes.
1014
+ """
1015
+ if len(bag[v]) + 1 == len(bag[u]):
1016
+ return
1017
+
1018
+ diff = list(bag[u] - bag[v])
1019
+ diff.pop()
1020
+
1021
+ last_node = u
1022
+ for w in diff:
1023
+ new_node = directed_tree.add_vertex()
1024
+ bag[new_node] = bag[last_node] - {w}
1025
+ directed_tree.add_edge(last_node, new_node)
1026
+ last_node = new_node
1027
+
1028
+ directed_tree.add_edge(last_node, v)
1029
+ directed_tree.delete_edge(u, v)
1030
+
1031
+ for ui in list(directed_tree):
1032
+ if directed_tree.out_degree(ui) != 1:
1033
+ continue
1034
+
1035
+ vi = next(directed_tree.neighbor_out_iterator(ui))
1036
+ bag_ui, bag_vi = bag[ui], bag[vi]
1037
+
1038
+ # Merge the nodes if the two bags are the same
1039
+ if bag_ui == bag_vi:
1040
+ if directed_tree.in_degree(ui) == 1:
1041
+ parent = next(directed_tree.neighbor_in_iterator(ui))
1042
+ directed_tree.add_edge(parent, vi)
1043
+ else:
1044
+ root = vi
1045
+ directed_tree.delete_vertex(ui)
1046
+
1047
+ # Add paths of intro / forget nodes accordingly
1048
+
1049
+ elif bag_ui.issubset(bag_vi):
1050
+ add_path_of_intro_nodes(ui, vi)
1051
+
1052
+ elif bag_vi.issubset(bag_ui):
1053
+ add_path_of_forget_nodes(ui, vi)
1054
+
1055
+ # Handle the case when the two nodes are not related in any way above
1056
+ else:
1057
+ wi = directed_tree.add_vertex()
1058
+ bag[wi] = bag[ui] & bag[vi]
1059
+ directed_tree.add_path([ui, wi, vi])
1060
+ directed_tree.delete_edge(ui, vi)
1061
+ add_path_of_forget_nodes(ui, wi)
1062
+ add_path_of_intro_nodes(wi, vi)
1063
+
1064
+ # Return the nice tree decomposition after the processing
1065
+ nice_tree_decomp = Graph(directed_tree, name=name)
1066
+
1067
+ bfs_ordering = nice_tree_decomp.breadth_first_search(start=root)
1068
+ relabeling = {u: (i, bag[u]) for i, u in enumerate(bfs_ordering)}
1069
+ nice_tree_decomp.relabel(inplace=True, perm=relabeling)
1070
+
1071
+ return nice_tree_decomp
1072
+
1073
+
1074
+ def label_nice_tree_decomposition(nice_TD, root, directed=False):
1075
+ r"""
1076
+ Return a nice tree decomposition with nodes labelled accordingly.
1077
+
1078
+ INPUT:
1079
+
1080
+ - ``nice_TD`` -- a nice tree decomposition
1081
+
1082
+ - ``root`` -- the root of the nice tree decomposition
1083
+
1084
+ - ``directed`` -- boolean (default: ``False``); whether to return the nice
1085
+ tree decomposition as a directed graph rooted at vertex ``root`` or as an
1086
+ undirected graph
1087
+
1088
+ OUTPUT: a nice tree decomposition with nodes labelled
1089
+
1090
+ EXAMPLES::
1091
+
1092
+ sage: # needs cliquer
1093
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import make_nice_tree_decomposition, label_nice_tree_decomposition
1094
+ sage: claw = graphs.CompleteBipartiteGraph(1, 3)
1095
+ sage: claw_TD = claw.treewidth(certificate=True)
1096
+ sage: nice_TD = make_nice_tree_decomposition(claw, claw_TD)
1097
+ sage: root = sorted(nice_TD.vertices())[0]
1098
+ sage: label_TD = label_nice_tree_decomposition(nice_TD, root, directed=True)
1099
+ sage: label_TD.name()
1100
+ 'Labelled Nice tree decomposition of Tree decomposition'
1101
+ sage: for node in sorted(label_TD): # random
1102
+ ....: print(node, label_TD.get_vertex(node))
1103
+ (0, {}) forget
1104
+ (1, {0}) forget
1105
+ (2, {0, 1}) intro
1106
+ (3, {0}) forget
1107
+ (4, {0, 3}) intro
1108
+ (5, {0}) forget
1109
+ (6, {0, 2}) intro
1110
+ (7, {2}) intro
1111
+ (8, {}) leaf
1112
+ """
1113
+ from sage.graphs.digraph import DiGraph
1114
+ from sage.graphs.graph import Graph
1115
+
1116
+ directed_TD = DiGraph(nice_TD.breadth_first_search(start=root, edges=True),
1117
+ format='list_of_edges',
1118
+ name='Labelled {}'.format(nice_TD.name()))
1119
+
1120
+ # The loop starts from the root node
1121
+ # We assume the tree decomposition is valid and nice,
1122
+ # hence saving time on checking.
1123
+ for node in directed_TD:
1124
+ out_deg = directed_TD.out_degree(node)
1125
+
1126
+ if out_deg == 2:
1127
+ directed_TD.set_vertex(node, 'join')
1128
+ elif out_deg == 1:
1129
+ current_bag = node[1]
1130
+ child_bag = directed_TD.neighbors_out(node)[0][1]
1131
+
1132
+ if len(current_bag) == len(child_bag) + 1:
1133
+ directed_TD.set_vertex(node, 'intro')
1134
+ else:
1135
+ directed_TD.set_vertex(node, 'forget')
1136
+ else:
1137
+ directed_TD.set_vertex(node, 'leaf')
1138
+
1139
+ if directed:
1140
+ return directed_TD
1141
+ return Graph(directed_TD, name=nice_TD.name())
1142
+
1143
+
1144
+ #
1145
+ # Treelength
1146
+ #
1147
+
1148
+ def length_of_tree_decomposition(G, T, check=True):
1149
+ r"""
1150
+ Return the length of the tree decomposition `T` of `G`.
1151
+
1152
+ The *length* of a tree decomposition, as proposed in [DG2006]_, is the
1153
+ maximum *diameter* in `G` of its bags, where the diameter of a bag `X_i` is
1154
+ the largest distance in `G` between the vertices in `X_i` (i.e., `\max_{u, v
1155
+ \in X_i} \dist_G(u, v)`). See the documentation of the
1156
+ :mod:`~sage.graphs.graph_decompositions.tree_decomposition` module for more
1157
+ details.
1158
+
1159
+ INPUT:
1160
+
1161
+ - ``G`` -- a graph
1162
+
1163
+ - ``T`` -- a tree-decomposition for `G`
1164
+
1165
+ - ``check`` -- boolean (default: ``True``); whether to check that the
1166
+ tree-decomposition `T` is valid for `G`
1167
+
1168
+ EXAMPLES:
1169
+
1170
+ Trees and cliques have treelength 1::
1171
+
1172
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import length_of_tree_decomposition
1173
+ sage: G = graphs.CompleteGraph(5)
1174
+ sage: tl, T = G.treelength(certificate=True)
1175
+ sage: tl
1176
+ 1
1177
+ sage: length_of_tree_decomposition(G, T, check=True)
1178
+ 1
1179
+ sage: G = graphs.RandomTree(20)
1180
+ sage: tl, T = G.treelength(certificate=True)
1181
+ sage: tl
1182
+ 1
1183
+ sage: length_of_tree_decomposition(G, T, check=True)
1184
+ 1
1185
+
1186
+ The Petersen graph has treelength 2::
1187
+
1188
+ sage: G = graphs.PetersenGraph()
1189
+ sage: tl, T = G.treelength(certificate=True)
1190
+ sage: tl
1191
+ 2
1192
+ sage: length_of_tree_decomposition(G, T)
1193
+ 2
1194
+
1195
+ When a tree-decomposition has a single bag containing all vertices of a
1196
+ graph, the length of this tree-decomposition is the diameter of the graph::
1197
+
1198
+ sage: G = graphs.Grid2dGraph(2, 5)
1199
+ sage: G.treelength()
1200
+ 2
1201
+ sage: G.diameter()
1202
+ 5
1203
+ sage: T = Graph({Set(G): []})
1204
+ sage: length_of_tree_decomposition(G, T)
1205
+ 5
1206
+
1207
+ TESTS::
1208
+
1209
+ sage: G = Graph()
1210
+ sage: _, T = G.treelength(certificate=True)
1211
+ sage: length_of_tree_decomposition(G, T, check=True)
1212
+ 0
1213
+ sage: length_of_tree_decomposition(Graph(1), T, check=True)
1214
+ Traceback (most recent call last):
1215
+ ...
1216
+ ValueError: the tree-decomposition is not valid for this graph
1217
+ """
1218
+ if check and not is_valid_tree_decomposition(G, T):
1219
+ raise ValueError("the tree-decomposition is not valid for this graph")
1220
+
1221
+ cdef unsigned int n = G.order()
1222
+
1223
+ if n < 2:
1224
+ return 0
1225
+ if any(len(bag) == n for bag in T):
1226
+ return G.diameter()
1227
+
1228
+ cdef unsigned int i, j
1229
+
1230
+ # We map vertices to integers in range 0..n-1
1231
+ cdef list int_to_vertex = list(G)
1232
+ cdef dict vertex_to_int = {u: i for i, u in enumerate(int_to_vertex)}
1233
+
1234
+ # We compute the distance matrix.
1235
+ cdef unsigned short * c_distances = c_distances_all_pairs(G, vertex_list=int_to_vertex)
1236
+ cdef unsigned short ** distances = <unsigned short **>sig_calloc(n, sizeof(unsigned short *))
1237
+ for i in range(n):
1238
+ distances[i] = c_distances + i * n
1239
+
1240
+ # We now compute the maximum lengths of the bags
1241
+ from itertools import combinations
1242
+ cdef list bag_int
1243
+ cdef unsigned short dij
1244
+ cdef unsigned short length = 0
1245
+ for bag in T:
1246
+ bag_int = [vertex_to_int[u] for u in bag]
1247
+ for i, j in combinations(bag_int, 2):
1248
+ dij = distances[i][j]
1249
+ if dij > length:
1250
+ length = dij
1251
+
1252
+ sig_free(c_distances)
1253
+ sig_free(distances)
1254
+
1255
+ return length
1256
+
1257
+
1258
+ def treelength_lowerbound(G):
1259
+ r"""
1260
+ Return a lower bound on the treelength of `G`.
1261
+
1262
+ See [DG2006]_ for more details.
1263
+
1264
+ INPUT:
1265
+
1266
+ - ``G`` -- a sage Graph
1267
+
1268
+ EXAMPLES::
1269
+
1270
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import treelength_lowerbound
1271
+ sage: G = graphs.PetersenGraph()
1272
+ sage: treelength_lowerbound(G)
1273
+ 1
1274
+ sage: G.treelength()
1275
+ 2
1276
+ sage: G = graphs.CycleGraph(5)
1277
+ sage: treelength_lowerbound(G)
1278
+ 2
1279
+ sage: G.treelength()
1280
+ 2
1281
+
1282
+ TESTS::
1283
+
1284
+ sage: treelength_lowerbound(Graph())
1285
+ 0
1286
+ """
1287
+ if G.is_cycle():
1288
+ from sage.arith.misc import integer_ceil as ceil
1289
+ return int(ceil(G.order() / 3))
1290
+
1291
+ lowerbound = 0
1292
+ girth = G.girth()
1293
+ if girth is not Infinity:
1294
+ lowerbound = max(lowerbound, girth / 3)
1295
+
1296
+ return int(lowerbound)
1297
+
1298
+
1299
+ cdef class TreelengthConnected:
1300
+ r"""
1301
+ Compute the treelength of a connected graph (and provide a decomposition).
1302
+
1303
+ This class implements an algorithm for computing the treelength of a
1304
+ connected graph that virtually explores the graph of all pairs
1305
+ ``(vertex_cut, connected_component)``, where ``vertex_cut`` is a vertex cut
1306
+ of the graph of length `\leq k`, and ``connected_component`` is a connected
1307
+ component of the graph induced by ``G - vertex_cut``.
1308
+
1309
+ We deduce that the pair ``(vertex_cut, connected_component)`` is feasible
1310
+ with treelength `k` if ``connected_component`` is empty, or if a vertex
1311
+ ``v`` from ``vertex_cut`` can be replaced with a vertex from
1312
+ ``connected_component``, such that the pair ``(vertex_cut + v,
1313
+ connected_component - v)`` is feasible.
1314
+
1315
+ INPUT:
1316
+
1317
+ - ``G`` -- a sage Graph
1318
+
1319
+ - ``k`` -- integer (default: ``None``); indicates the length to be
1320
+ considered. When `k` is an integer, the method checks that the graph has
1321
+ treelength `\leq k`. If `k` is ``None`` (default), the method computes the
1322
+ optimal treelength.
1323
+
1324
+ - ``certificate`` -- boolean (default: ``False``); whether to also compute
1325
+ the tree-decomposition itself
1326
+
1327
+ OUTPUT:
1328
+
1329
+ ``TreelengthConnected(G)`` returns the treelength of `G`. When `k` is
1330
+ specified, it returns ``False`` when no tree-decomposition of length
1331
+ `\leq k` exists or ``True`` otherwise. When ``certificate=True``, the
1332
+ tree-decomposition is also returned.
1333
+
1334
+ EXAMPLES:
1335
+
1336
+ A clique has treelength 1::
1337
+
1338
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import TreelengthConnected
1339
+ sage: TreelengthConnected(graphs.CompleteGraph(3)).get_length()
1340
+ 1
1341
+ sage: TC = TreelengthConnected(graphs.CompleteGraph(4), certificate=True)
1342
+ sage: TC.get_length()
1343
+ 1
1344
+ sage: TC.get_tree_decomposition()
1345
+ Tree decomposition of Complete graph: Graph on 1 vertex
1346
+
1347
+ A cycle has treelength `\lceil n/3 \rceil`::
1348
+
1349
+ sage: TreelengthConnected(graphs.CycleGraph(6)).get_length()
1350
+ 2
1351
+ sage: TreelengthConnected(graphs.CycleGraph(7)).get_length()
1352
+ 3
1353
+ sage: TreelengthConnected(graphs.CycleGraph(7), k=3).is_less_than_k()
1354
+ True
1355
+ sage: TreelengthConnected(graphs.CycleGraph(7), k=2).is_less_than_k()
1356
+ False
1357
+
1358
+ TESTS:
1359
+
1360
+ The input graph must be connected::
1361
+
1362
+ sage: TreelengthConnected(Graph(2))
1363
+ Traceback (most recent call last):
1364
+ ...
1365
+ ValueError: the graph is not connected
1366
+
1367
+ The parameter `k` must be nonnegative::
1368
+
1369
+ sage: TreelengthConnected(Graph(1), k=-1)
1370
+ Traceback (most recent call last):
1371
+ ...
1372
+ ValueError: k (= -1) must be a nonnegative integer
1373
+
1374
+ Parameter ``certificate`` must be ``True`` to get a tree decomposition::
1375
+
1376
+ sage: TreelengthConnected(Graph(1), certificate=False).get_tree_decomposition()
1377
+ Traceback (most recent call last):
1378
+ ...
1379
+ ValueError: parameter 'certificate' has not been set to True
1380
+
1381
+ When parameter `k` is specified and ``certificate`` is ``True``, the
1382
+ computed tree decomposition is any valid tree decomposition with length at
1383
+ most `k`. However, this tree decomposition exists only if the treelength of
1384
+ `G` is at most `k` (i.e., `tl(G) \leq k`)::
1385
+
1386
+ sage: G = graphs.Grid2dGraph(2, 3)
1387
+ sage: TC = TreelengthConnected(G, k=2, certificate=True)
1388
+ sage: TC.is_less_than_k()
1389
+ True
1390
+ sage: TC.get_tree_decomposition()
1391
+ Tree decomposition of 2D Grid Graph for [2, 3]: Graph on 3 vertices
1392
+ sage: TC = TreelengthConnected(G, k=1, certificate=True)
1393
+ sage: TC.is_less_than_k()
1394
+ False
1395
+ sage: TC.get_tree_decomposition()
1396
+ Traceback (most recent call last):
1397
+ ...
1398
+ ValueError: no tree decomposition with length <= 1 was found
1399
+ """
1400
+
1401
+ def __init__(self, G, k=None, certificate=False):
1402
+ r"""
1403
+ Initialize this object and compute the treelength of `G`.
1404
+
1405
+ INPUT:
1406
+
1407
+ - ``G`` -- a sage Graph
1408
+
1409
+ - ``k`` -- integer (default: ``None``); indicates the length to be
1410
+ considered. When `k` is an integer, the method checks that the graph
1411
+ has treelength `\leq k`. If `k` is ``None`` (default), the method
1412
+ computes the optimal treelength.
1413
+
1414
+ - ``certificate`` -- boolean (default: ``False``); whether to compute
1415
+ the tree-decomposition itself
1416
+
1417
+ TESTS::
1418
+
1419
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import TreelengthConnected
1420
+ sage: G = graphs.CycleGraph(4)
1421
+ sage: TreelengthConnected(G).get_length()
1422
+ 2
1423
+ """
1424
+ if k is not None and k < 0:
1425
+ raise ValueError("k (= {}) must be a nonnegative integer".format(k))
1426
+ G._scream_if_not_simple()
1427
+ if not G.is_connected():
1428
+ raise ValueError("the graph is not connected")
1429
+
1430
+ self.certificate = certificate
1431
+ self.k_is_defined = k is not None
1432
+ self.k = k if self.k_is_defined else 0
1433
+
1434
+ if certificate:
1435
+ from sage.graphs.graph import Graph
1436
+ self.name = "Tree decomposition of {}".format(G.name())
1437
+
1438
+ self.n = G.order()
1439
+ self.distances = NULL # used in the destructor
1440
+
1441
+ # Trivial cases
1442
+ if self.n <= 1 or (self.k_is_defined and self.n <= k):
1443
+ if certificate:
1444
+ if self.n:
1445
+ self.tree = Graph({Set(G): []}, format='dict_of_lists', name=self.name)
1446
+ else:
1447
+ self.tree = Graph(name=self.name)
1448
+ self.length = 0 if self.n <= 1 else G.diameter(algorithm='DHV')
1449
+ self.leq_k = True # We know that k is nonnegative
1450
+ return
1451
+
1452
+ if self.k_is_defined and not k:
1453
+ # We have at least 2 vertices and 1 edges, so tl >= 1
1454
+ self.leq_k = False
1455
+ return
1456
+
1457
+ if G.is_clique():
1458
+ if certificate:
1459
+ self.tree = Graph({Set(G): []}, format='dict_of_lists', name=self.name)
1460
+ self.length = 1
1461
+ self.leq_k = True
1462
+ return
1463
+
1464
+ cdef unsigned int i, j
1465
+
1466
+ # If the vertices are not labeled 0..n-1, we relabel the graph. This
1467
+ # way, the labeling of the vertices matches the rows and columns of the
1468
+ # distance matrix
1469
+ if set(G) == set(range(self.n)):
1470
+ graph = G
1471
+ self.perm_inv = dict()
1472
+ else:
1473
+ graph, perm = G.relabel(inplace=False, return_map=True)
1474
+ self.perm_inv = {i: u for u, i in perm.items()}
1475
+
1476
+ # We compute the distance matrix.
1477
+ self.c_distances = c_distances_all_pairs(graph, vertex_list=list(range(self.n)))
1478
+ self.distances = <unsigned short **>sig_calloc(self.n, sizeof(unsigned short *))
1479
+ for i in range(self.n):
1480
+ self.distances[i] = self.c_distances + i * self.n
1481
+
1482
+ # and the diameter of the graph
1483
+ self.diameter = 0
1484
+ for i in range(self.n):
1485
+ for j in range(i, self.n):
1486
+ self.diameter = max(self.diameter, self.distances[i][j])
1487
+
1488
+ if self.k_is_defined and k >= self.diameter:
1489
+ # All vertices fit in one bag
1490
+ if certificate:
1491
+ self.tree = Graph({Set(G): []}, format='dict_of_lists', name=self.name)
1492
+ self.length = self.diameter
1493
+ self.leq_k = True
1494
+ return
1495
+
1496
+ # Forcing k to be defined
1497
+ if not self.k_is_defined:
1498
+ for i in range(treelength_lowerbound(graph), self.diameter + 1):
1499
+ ans = self._treelength(graph, i)
1500
+ if ans:
1501
+ self.length = i
1502
+ return
1503
+
1504
+ # If k is defined
1505
+ ans = self._treelength(graph, k)
1506
+ if ans:
1507
+ self.length = k
1508
+ self.leq_k = True
1509
+ else:
1510
+ self.leq_k = False
1511
+
1512
+ def __dealloc__(self):
1513
+ r"""
1514
+ Destroy the object.
1515
+
1516
+ TESTS::
1517
+
1518
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import TreelengthConnected
1519
+ sage: G = graphs.CycleGraph(4)
1520
+ sage: TreelengthConnected(G).get_length()
1521
+ 2
1522
+ """
1523
+ if self.distances:
1524
+ sig_free(self.c_distances)
1525
+ sig_free(self.distances)
1526
+
1527
+ cdef bint _treelength(self, g, k) noexcept:
1528
+ r"""
1529
+ Check whether the treelength of `g` is at most `k`.
1530
+
1531
+ INPUT:
1532
+
1533
+ - ``g`` -- a sage Graph
1534
+
1535
+ - ``k`` -- integer; indicates the length to be considered
1536
+
1537
+ TESTS::
1538
+
1539
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import TreelengthConnected
1540
+ sage: G = graphs.CycleGraph(4)
1541
+ sage: TreelengthConnected(G, k=2).is_less_than_k()
1542
+ True
1543
+ """
1544
+
1545
+ # This is the recursion described in the method's documentation. All
1546
+ # computations are cached, and depends on the pair ``cut,
1547
+ # connected_component`` only.
1548
+ #
1549
+ # It returns either a boolean or the corresponding tree-decomposition,
1550
+ # as a list of edges between vertex cuts (used to build the complete
1551
+ # tree-decomposition at the end of the _treelength method).
1552
+ @cached_function
1553
+ def rec(cut, cc):
1554
+ cdef int v
1555
+ cdef frozenset reduced_cut
1556
+
1557
+ if len(cc) == 1:
1558
+ [v] = cc
1559
+ # We identify the neighbors of v in cut
1560
+ reduced_cut = cut.intersection(g.neighbor_iterator(v))
1561
+ # We can form a new bag with its closed neighborhood, and this
1562
+ # bag has diameter at most 2. Furthermore, if k == 1, we know
1563
+ # that the bag cut has diameter <= 1, and so the new bag has
1564
+ # diameter 1
1565
+ if self.certificate:
1566
+ if cut == reduced_cut:
1567
+ return [(cut, cut.union(cc))]
1568
+ # We need to forget some vertices
1569
+ return [(cut, reduced_cut), (reduced_cut, reduced_cut.union(cc))]
1570
+
1571
+ return True
1572
+
1573
+ # We explore all possible extensions of the cut
1574
+ cdef frozenset cutv
1575
+ cdef frozenset ccv
1576
+ cdef frozenset cci
1577
+ cdef frozenset reduced_cuti
1578
+ cdef list sons
1579
+ cdef int x
1580
+
1581
+ for v in cc:
1582
+
1583
+ # We know that the cut has diameter <= k. So we check is adding
1584
+ # v to the cut does not make its diameter > k
1585
+ if any(self.distances[v][x] > k for x in cut):
1586
+ continue
1587
+ # We add v to the cut and remove it from cc
1588
+ cutv = cut.union([v])
1589
+ ccv = cc.difference([v])
1590
+
1591
+ # The values returned by the recursive calls.
1592
+ sons = []
1593
+
1594
+ # Removing v may have disconnected cc. We iterate on its
1595
+ # connected components
1596
+ for _cci in g.subgraph(ccv).connected_components(sort=False):
1597
+ cci = frozenset(_cci)
1598
+
1599
+ # The recursive subcalls. We remove on-the-fly the vertices
1600
+ # from the cut which play no role in separating the
1601
+ # connected component from the rest of the graph. That is,
1602
+ # we identify the vertices of cutv with a neighbor in cci
1603
+ reduced_cuti = frozenset([x for x in cutv
1604
+ if any(xx in cci for xx in g.neighbor_iterator(x))])
1605
+ if not reduced_cuti:
1606
+ # This should not happen
1607
+ break
1608
+
1609
+ # and we do a recursive call
1610
+ son = rec(reduced_cuti, cci)
1611
+ if not son:
1612
+ break
1613
+
1614
+ # We get a valid decomposition of cci
1615
+ if self.certificate:
1616
+ # We connect cut, cutv, reduced_cci and son
1617
+ sons.append((cut, cutv))
1618
+ if v in reduced_cuti:
1619
+ sons.append((cutv, reduced_cuti))
1620
+ else:
1621
+ reduced_cutv = reduced_cuti.union([v])
1622
+ sons.append((cutv, reduced_cutv))
1623
+ sons.append((reduced_cutv, reduced_cuti))
1624
+ sons.extend(son)
1625
+
1626
+ # Weird Python syntax which is useful once in a lifetime : if break
1627
+ # was never called in the loop above, we return "sons".
1628
+ else:
1629
+ return sons if self.certificate else True
1630
+
1631
+ return False
1632
+
1633
+ # Main call to rec function, i.e. rec({v}, V-{v})
1634
+ cdef list V = list(g)
1635
+ cdef frozenset v = frozenset([V.pop()])
1636
+ TD = rec(v, frozenset(V))
1637
+
1638
+ if TD is False:
1639
+ return False
1640
+
1641
+ if not self.certificate:
1642
+ return True
1643
+
1644
+ # Building the tree-decomposition graph. Its vertices are cuts of the
1645
+ # decomposition, and there is an edge from a cut C1 to a cut C2 if C2 is an
1646
+ # immediate subcall of C1. If needed, the vertices are relabeled.
1647
+ if self.perm_inv:
1648
+ def good_label(x):
1649
+ return Set([self.perm_inv[i] for i in x])
1650
+ else:
1651
+ def good_label(x):
1652
+ return Set(x)
1653
+
1654
+ from sage.graphs.graph import Graph
1655
+ T = Graph([(good_label(x), good_label(y)) for x, y in TD if x != y],
1656
+ format='list_of_edges')
1657
+ self.tree = reduced_tree_decomposition(T)
1658
+ self.tree.name(self.name)
1659
+ return True
1660
+
1661
+ def get_tree_decomposition(self):
1662
+ """
1663
+ Return the tree-decomposition.
1664
+
1665
+ EXAMPLES::
1666
+
1667
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import TreelengthConnected
1668
+ sage: G = graphs.CycleGraph(4)
1669
+ sage: TreelengthConnected(G, certificate=True).get_tree_decomposition()
1670
+ Tree decomposition of Cycle graph: Graph on 2 vertices
1671
+ sage: G.diameter()
1672
+ 2
1673
+ sage: TreelengthConnected(G, k=2, certificate=True).get_tree_decomposition()
1674
+ Tree decomposition of Cycle graph: Graph on 1 vertex
1675
+ sage: TreelengthConnected(G, k=1, certificate=True).get_tree_decomposition()
1676
+ Traceback (most recent call last):
1677
+ ...
1678
+ ValueError: no tree decomposition with length <= 1 was found
1679
+
1680
+ TESTS::
1681
+
1682
+ sage: G = graphs.CycleGraph(4)
1683
+ sage: TreelengthConnected(G, certificate=False).get_tree_decomposition()
1684
+ Traceback (most recent call last):
1685
+ ...
1686
+ ValueError: parameter 'certificate' has not been set to True
1687
+ """
1688
+ if self.certificate:
1689
+ if self.k_is_defined and not self.leq_k:
1690
+ raise ValueError("no tree decomposition with length <= {} was found".format(self.k))
1691
+ return self.tree
1692
+ else:
1693
+ raise ValueError("parameter 'certificate' has not been set to True")
1694
+
1695
+ def get_length(self):
1696
+ """
1697
+ Return the length of the tree decomposition.
1698
+
1699
+ EXAMPLES::
1700
+
1701
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import TreelengthConnected
1702
+ sage: G = graphs.CycleGraph(4)
1703
+ sage: TreelengthConnected(G).get_length()
1704
+ 2
1705
+ sage: TreelengthConnected(G, k=2).get_length()
1706
+ 2
1707
+ sage: TreelengthConnected(G, k=1).get_length()
1708
+ Traceback (most recent call last):
1709
+ ...
1710
+ ValueError: no tree decomposition with length <= 1 was found
1711
+
1712
+ TESTS::
1713
+
1714
+ sage: TreelengthConnected(Graph()).get_length()
1715
+ 0
1716
+ """
1717
+ if self.k_is_defined and not self.leq_k:
1718
+ raise ValueError("no tree decomposition with length <= {} was found".format(self.k))
1719
+ return self.length
1720
+
1721
+ def is_less_than_k(self):
1722
+ """
1723
+ Return whether a tree decomposition with length at most `k` was found.
1724
+
1725
+ EXAMPLES::
1726
+
1727
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import TreelengthConnected
1728
+ sage: G = graphs.CycleGraph(4)
1729
+ sage: TreelengthConnected(G, k=1).is_less_than_k()
1730
+ False
1731
+ sage: TreelengthConnected(G, k=2).is_less_than_k()
1732
+ True
1733
+ sage: TreelengthConnected(G).is_less_than_k()
1734
+ Traceback (most recent call last):
1735
+ ...
1736
+ ValueError: parameter 'k' has not been specified
1737
+
1738
+ TESTS::
1739
+
1740
+ sage: TreelengthConnected(Graph(), k=1).is_less_than_k()
1741
+ True
1742
+ """
1743
+ if self.k_is_defined:
1744
+ return self.leq_k
1745
+ raise ValueError("parameter 'k' has not been specified")
1746
+
1747
+
1748
+ def treelength(G, k=None, certificate=False):
1749
+ r"""
1750
+ Compute the treelength of `G` (and provide a decomposition).
1751
+
1752
+ The *length* of a tree decomposition, as proposed in [DG2006]_, is the
1753
+ maximum *diameter* in `G` of its bags, where the diameter of a bag `X_i` is
1754
+ the largest distance in `G` between the vertices in `X_i` (i.e., `\max_{u, v
1755
+ \in X_i} \dist_G(u, v)`). The *treelength* `tl(G)` of a graph `G` is the
1756
+ minimum length among all possible tree decompositions of `G`.
1757
+ See the documentation of the
1758
+ :mod:`~sage.graphs.graph_decompositions.tree_decomposition` module for more
1759
+ details.
1760
+
1761
+ INPUT:
1762
+
1763
+ - ``G`` -- a sage Graph
1764
+
1765
+ - ``k`` -- integer (default: ``None``); indicates the length to be
1766
+ considered. When `k` is an integer, the method checks that the graph has
1767
+ treelength `\leq k`. If `k` is ``None`` (default), the method computes the
1768
+ optimal treelength.
1769
+
1770
+ - ``certificate`` -- boolean (default: ``False``); whether to also return
1771
+ the tree-decomposition itself
1772
+
1773
+ OUTPUT:
1774
+
1775
+ ``G.treelength()`` returns the treelength of `G`. When `k` is specified, it
1776
+ returns ``False`` when no tree-decomposition of length `\leq k` exists or
1777
+ ``True`` otherwise. When ``certificate=True``, the tree-decomposition is
1778
+ also returned.
1779
+
1780
+ ALGORITHM:
1781
+
1782
+ This method virtually explores the graph of all pairs ``(vertex_cut,
1783
+ connected_component)``, where ``vertex_cut`` is a vertex cut of the graph of
1784
+ length `\leq k`, and ``connected_component`` is a connected component of the
1785
+ graph induced by ``G - vertex_cut``.
1786
+
1787
+ We deduce that the pair ``(vertex_cut, connected_component)`` is feasible
1788
+ with treelength `k` if ``connected_component`` is empty, or if a vertex
1789
+ ``v`` from ``vertex_cut`` can be replaced with a vertex from
1790
+ ``connected_component``, such that the pair ``(vertex_cut + v,
1791
+ connected_component - v)`` is feasible.
1792
+
1793
+ In practice, this method decomposes the graph by its clique minimal
1794
+ separators into atoms, computes the treelength of each of atom and returns
1795
+ the maximum value over all the atoms. Indeed, we have that `tl(G) = \max_{X
1796
+ \in A} tl(G[X])` where `A` is the set of atoms of the decomposition by
1797
+ clique separators of `G`. When ``certificate == True``, the
1798
+ tree-decompositions of the atoms are connected to each others by adding
1799
+ edges with respect to the clique separators.
1800
+
1801
+ .. SEEALSO::
1802
+
1803
+ - :meth:`treewidth` computes the treewidth of a graph.
1804
+ - :meth:`~sage.graphs.graph_decompositions.vertex_separation.path_decomposition`
1805
+ computes the pathwidth of a graph.
1806
+ - module :mod:`~sage.graphs.graph_decompositions.vertex_separation`.
1807
+ - :meth:`~sage.graphs.graph_decompositions.clique_separators.atoms_and_clique_separators`
1808
+
1809
+ EXAMPLES:
1810
+
1811
+ The PetersenGraph has treelength 2::
1812
+
1813
+ sage: G = graphs.PetersenGraph()
1814
+ sage: G.treelength()
1815
+ 2
1816
+
1817
+ Disconnected graphs have infinite treelength::
1818
+
1819
+ sage: G = Graph(2)
1820
+ sage: G.treelength()
1821
+ +Infinity
1822
+ sage: G.treelength(k=+Infinity)
1823
+ True
1824
+ sage: G.treelength(k=2)
1825
+ False
1826
+ sage: G.treelength(certificate=True)
1827
+ Traceback (most recent call last):
1828
+ ...
1829
+ ValueError: the tree decomposition of a disconnected graph is not defined
1830
+
1831
+ Chordal graphs have treelength 1::
1832
+
1833
+ sage: G = graphs.RandomChordalGraph(30)
1834
+ sage: while not G.is_connected():
1835
+ ....: G = graphs.RandomChordalGraph(30)
1836
+ sage: G.treelength()
1837
+ 1
1838
+
1839
+ Cycles have treelength `\lceil n/3 \rceil`::
1840
+
1841
+ sage: [graphs.CycleGraph(n).treelength() for n in range(3, 11)]
1842
+ [1, 2, 2, 2, 3, 3, 3, 4]
1843
+
1844
+ TESTS:
1845
+
1846
+ Check that the decomposition by clique separators is valid::
1847
+
1848
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import TreelengthConnected
1849
+ sage: from sage.graphs.graph_decompositions.tree_decomposition import is_valid_tree_decomposition
1850
+ sage: G = graphs.StarGraph(3)
1851
+ sage: G.subdivide_edges(G.edges(sort=False), 2)
1852
+ sage: G = G.cartesian_product(graphs.CycleGraph(3))
1853
+ sage: tl, T = G.treelength(certificate=True)
1854
+ sage: tl == TreelengthConnected(G).get_length()
1855
+ True
1856
+ sage: is_valid_tree_decomposition(G, T)
1857
+ True
1858
+
1859
+ Corner cases::
1860
+
1861
+ sage: Graph().treelength()
1862
+ 0
1863
+ sage: Graph().treelength(certificate=True)
1864
+ (0, Tree decomposition: Graph on 0 vertices)
1865
+ sage: Graph(1).treelength()
1866
+ 0
1867
+ sage: Graph(1).treelength(k=0)
1868
+ True
1869
+ sage: Graph(1).treelength(certificate=True)
1870
+ (0, Tree decomposition: Graph on 1 vertex)
1871
+ sage: Graph(1).treelength(k=0, certificate=True)
1872
+ (True, Tree decomposition: Graph on 1 vertex)
1873
+ sage: G = graphs.PathGraph(2)
1874
+ sage: G.treelength()
1875
+ 1
1876
+ sage: G.treelength(k=0)
1877
+ False
1878
+ sage: G.treelength(certificate=True)
1879
+ (1, Tree decomposition of Path graph: Graph on 1 vertex)
1880
+ sage: G.treelength(certificate=True, k=0)
1881
+ (False, None)
1882
+ sage: G.treelength(certificate=True, k=1)
1883
+ (True, Tree decomposition of Path graph: Graph on 1 vertex)
1884
+ sage: G.treelength(certificate=True, k=0)
1885
+ (False, None)
1886
+ sage: G.treelength(k=-1)
1887
+ Traceback (most recent call last):
1888
+ ...
1889
+ ValueError: k(=-1) must be a nonnegative integer
1890
+ """
1891
+ if G.is_directed():
1892
+ raise ValueError("this method is defined for undirected graphs only")
1893
+ if k is not None and k < 0:
1894
+ raise ValueError("k(={}) must be a nonnegative integer".format(k))
1895
+
1896
+ cdef str name = "Tree decomposition"
1897
+ if G.name():
1898
+ name += " of {}".format(G.name())
1899
+
1900
+ # Corner cases
1901
+ from sage.graphs.graph import Graph
1902
+ if G.order() <= 1:
1903
+ answer = 0 if k is None else True
1904
+ if certificate:
1905
+ if G:
1906
+ answer = answer, Graph({Set(G): []}, format='dict_of_lists', name=name)
1907
+ else:
1908
+ answer = answer, Graph(name=name)
1909
+ return answer
1910
+ if not G.is_connected():
1911
+ if certificate:
1912
+ raise ValueError("the tree decomposition of a disconnected graph is not defined")
1913
+ elif k is None:
1914
+ return +Infinity
1915
+ else:
1916
+ return k is Infinity
1917
+ if k == 0:
1918
+ return (False, None) if certificate else False
1919
+ if not certificate and G.is_chordal():
1920
+ return 1 if k is None else True
1921
+
1922
+ # We decompose the graph by clique minimal separators into atoms and solve
1923
+ # the problem on each of them
1924
+ atoms, cliques = G.atoms_and_clique_separators()
1925
+
1926
+ if not cliques:
1927
+ # We have a single atom
1928
+ TC = TreelengthConnected(G, k=k, certificate=certificate)
1929
+ if certificate:
1930
+ if k is None:
1931
+ return TC.get_length(), TC.get_tree_decomposition()
1932
+ elif TC.is_less_than_k():
1933
+ return True, TC.get_tree_decomposition()
1934
+ else:
1935
+ return False, None
1936
+ if k is None:
1937
+ return TC.get_length()
1938
+ return TC.is_less_than_k()
1939
+
1940
+ # As some atoms might be isomorphic, we use a dictionary keyed by immutable
1941
+ # copies of canonical graphs to store intermediate results.
1942
+ cdef dict data = dict()
1943
+ cdef list result = []
1944
+ cdef int tl = 1 # The graph is connected and of order at least 2
1945
+ cdef dict certif_inv
1946
+ cdef dict perm
1947
+
1948
+ for atom in atoms:
1949
+
1950
+ ga = G.subgraph(atom)
1951
+ if ga.is_clique():
1952
+ if certificate:
1953
+ result.append(Graph({Set(atom): []}, format='dict_of_lists'))
1954
+ continue
1955
+
1956
+ gc, certif = ga.canonical_label(certificate=True)
1957
+ gci = gc.copy(immutable=True)
1958
+
1959
+ if gci in data:
1960
+ # We already solved the problem for an isomorphic atom.
1961
+ if certificate:
1962
+ # We deduce the solution for this atom
1963
+ certif_inv = {i: u for u, i in certif.items()}
1964
+ perm = {u: Set([certif_inv[i] for i in u]) for u in data[gci]}
1965
+ result.append(data[gci].relabel(perm=perm, inplace=False, immutable=False))
1966
+ continue
1967
+
1968
+ # We solve the problem for this atom and store the result
1969
+ TC = TreelengthConnected(gci, k=k, certificate=certificate)
1970
+ if certificate:
1971
+ T = TC.get_tree_decomposition()
1972
+ data[gci] = T
1973
+ certif_inv = {i: u for u, i in certif.items()}
1974
+ perm = {u: Set([certif_inv[i] for i in u]) for u in T}
1975
+ result.append(T.relabel(perm=perm, inplace=False, immutable=False))
1976
+ if k is None:
1977
+ tl = max(tl, TC.get_length())
1978
+ elif not TC.is_less_than_k():
1979
+ return False if not certificate else (False, None)
1980
+
1981
+ if not certificate:
1982
+ if k is None:
1983
+ return tl
1984
+ return True
1985
+
1986
+ # We now build the tree decomposition of the graph by connecting the tree
1987
+ # decompositions of its atoms.
1988
+ T = _from_tree_decompositions_of_atoms_to_tree_decomposition(result, cliques)
1989
+
1990
+ # The tree-decomposition may contain a lot of useless nodes.
1991
+ # We merge all edges between two sets S,S' where S is a subset of S'
1992
+ T = reduced_tree_decomposition(T)
1993
+ T.name(name)
1994
+ if k is None:
1995
+ return tl, T
1996
+ return True, T