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,1704 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # cython: binding=True
3
+ r"""
4
+ Hyperbolicity
5
+
6
+ **Definition** :
7
+
8
+ The hyperbolicity `\delta` of a graph `G` has been defined by Gromov
9
+ [Gro1987]_ as follows (we give here the so-called 4-points condition):
10
+
11
+ Let `a, b, c, d` be vertices of the graph, let `S_1`, `S_2` and `S_3` be
12
+ defined by
13
+
14
+ .. MATH::
15
+
16
+ S_1 = dist(a, b) + dist(d, c)\\
17
+ S_2 = dist(a, c) + dist(b, d)\\
18
+ S_3 = dist(a, d) + dist(b, c)\\
19
+
20
+ and let `M_1` and `M_2` be the two largest values among `S_1`, `S_2`, and
21
+ `S_3`. We define `hyp(a, b, c, d) = M_1 - M_2`, and the hyperbolicity
22
+ `\delta(G)` of the graph is the maximum of `hyp` over all possible
23
+ 4-tuples `(a, b, c, d)` divided by 2. That is, the graph is said
24
+ `\delta`-hyperbolic when
25
+
26
+ .. MATH::
27
+
28
+ \delta(G) = \frac{1}{2}\max_{a,b,c,d\in V(G)}hyp(a, b, c, d)
29
+
30
+ (note that `hyp(a, b, c, d)=0` whenever two elements among `a,b,c,d` are
31
+ equal)
32
+
33
+ **Some known results** :
34
+
35
+ - Trees and cliques are `0`-hyperbolic
36
+
37
+ - `n\times n` grids are `n-1`-hyperbolic
38
+
39
+ - Cycles are approximately `n/4`-hyperbolic
40
+
41
+ - Chordal graphs are `\leq 1`-hyperbolic
42
+
43
+ Besides, the hyperbolicity of a graph is the maximum over all its
44
+ biconnected components.
45
+
46
+ **Algorithms and complexity** :
47
+
48
+ The time complexity of the naive implementation (i.e. testing all 4-tuples)
49
+ is `O( n^4 )`, and an algorithm with time complexity `O(n^{3.69})` has been
50
+ proposed in [FIV2012]_. This remains very long for large-scale graphs, and
51
+ much harder to implement.
52
+
53
+ Several improvements over the naive algorithm have been proposed and are
54
+ implemented in the current module.
55
+
56
+ - Another upper bound on `hyp(a, b, c, d)` has been proved in [CCL2015]_. It
57
+ is used to design an algorithm with worse case time complexity in
58
+ `O(n^4)` but that behaves much better in practice.
59
+
60
+ Assume that `S_1 = dist(a, b) + dist(c, d)` is the largest sum among
61
+ `S_1,S_2,S_3`. We have
62
+
63
+ .. MATH::
64
+
65
+ S_2 + S_3 =& dist(a, c) + dist(b, d) + dist(a, d) + dist(b, c)\\
66
+ =& [ dist(a, c) + dist(b, c) ] + [ dist(a, d) + dist(b, d)]\\
67
+ \geq &dist(a,b) + dist(a,b)\\
68
+ \geq &2dist(a,b)\\
69
+
70
+ Now, since `S_1` is the largest sum, we have
71
+
72
+ .. MATH::
73
+
74
+ hyp(a, b, c, d) =& S_1 - \max\{S_2, S_3\}\\
75
+ \leq& S_1 - \frac{S_2+ S_3}{2}\\
76
+ \leq& S_1 - dist(a, b)\\
77
+ =& dist(c, d)\\
78
+
79
+ We obtain similarly that `hyp(a, b, c, d) \leq dist(a, b)`. Consequently,
80
+ in the implementation of the 'CCL' algorithm, we ensure that `S_1` is
81
+ larger than `S_2` and `S_3` using an ordering of the pairs by decreasing
82
+ lengths. Then, we use the best value `h` found so far to stop exploration
83
+ as soon as `dist(a, b) \leq h`.
84
+
85
+ The worst case time complexity of this algorithm is `O(n^4)`, but it
86
+ performs very well in practice since it cuts the search space. This
87
+ algorithm can be turned into an approximation algorithm since at any step
88
+ of its execution we maintain an upper and a lower bound. We can thus stop
89
+ execution as soon as a multiplicative approximation factor or an additive
90
+ one is proven.
91
+
92
+ - The notion of ''far-apart pairs'' has been introduced in [Sot2011]_ to
93
+ further reduce the number of 4-tuples to consider. We say that the pair
94
+ `(a,b)` is far-apart if for every `w` in `V\setminus\{a,b\}` we have
95
+
96
+ .. MATH::
97
+
98
+ dist(w,a)+dist(a,b) > dist(w,b) \text{ and }dist(w,b)+dist(a,b) > dist(w,a)
99
+
100
+ Determining the set of far-apart pairs can be done in time `O(nm)` using
101
+ BFS. Now, it is proved in [Sot2011]_ that there exists two far-apart pairs
102
+ `(a,b)` and `(c,d)` satisfying `\delta(G) = hyp(a, b, c, d)/2`. For
103
+ instance, the `n\times m`-grid has only two far-apart pairs, and so
104
+ computing its hyperbolicity is immediate once the far-apart pairs are
105
+ found. The 'CCL+FA' or 'CCL+' algorithm improves the 'CCL' algorithm
106
+ since it uses far-apart pairs.
107
+
108
+ - This algorithm was further improved in [BCCM2015]_: instead of iterating
109
+ twice over all pairs of vertices, in the "inner" loop, we cut several
110
+ pairs by exploiting properties of the underlying graph.
111
+
112
+ .. TODO::
113
+
114
+ - Add exact methods for the hyperbolicity of chordal graphs
115
+
116
+ - Add method for partitioning the graph with clique separators
117
+
118
+ **This module contains the following functions**
119
+
120
+ At Python level :
121
+
122
+ .. csv-table::
123
+ :class: contentstable
124
+ :widths: 30, 70
125
+ :delim: |
126
+
127
+ :meth:`~hyperbolicity` | Return the hyperbolicity of the graph or an approximation of this value.
128
+ :meth:`~hyperbolicity_distribution` | Return the hyperbolicity distribution of the graph or a sampling of it.
129
+
130
+ AUTHORS:
131
+
132
+ - David Coudert (2012): initial version, exact and approximate algorithm,
133
+ distribution, sampling
134
+ - David Coudert (2014): improved exact algorithm using far-apart pairs
135
+ - Michele Borassi (2015): cleaned the code and implemented the new algorithm
136
+ - Karan Desai (2016): fixed minor typo in documentation
137
+
138
+
139
+ Methods
140
+ -------
141
+ """
142
+
143
+ # ****************************************************************************
144
+ # Copyright (C) 2012 David Coudert <david.coudert@inria.fr>
145
+ #
146
+ # This program is free software: you can redistribute it and/or modify
147
+ # it under the terms of the GNU General Public License as published by
148
+ # the Free Software Foundation, either version 2 of the License, or
149
+ # (at your option) any later version.
150
+ # https://www.gnu.org/licenses/
151
+ # ****************************************************************************
152
+
153
+ from libc.string cimport memset
154
+ from cysignals.memory cimport check_allocarray, sig_free
155
+ from cysignals.signals cimport sig_on, sig_off
156
+ from memory_allocator cimport MemoryAllocator
157
+
158
+ from sage.graphs.distances_all_pairs cimport c_distances_all_pairs
159
+ from sage.arith.misc import binomial
160
+ from sage.rings.integer_ring import ZZ
161
+ from sage.graphs.base.static_sparse_graph cimport short_digraph
162
+ from sage.graphs.base.static_sparse_graph cimport init_short_digraph
163
+ from sage.graphs.base.static_sparse_graph cimport free_short_digraph
164
+ from libc.stdint cimport uint16_t, uint32_t, uint64_t
165
+ from sage.data_structures.bitset_base cimport *
166
+
167
+
168
+ # Defining a pair of vertices as a C struct
169
+ ctypedef struct pair:
170
+ uint32_t s
171
+ uint32_t t
172
+
173
+
174
+ ######################################################################
175
+ # Speedup functions
176
+ ######################################################################
177
+
178
+ def _my_subgraph(G, vertices, relabel=False, return_map=False):
179
+ r"""
180
+ Return the subgraph containing the given vertices.
181
+
182
+ This method considers only the connectivity. Therefore, edge labels are
183
+ ignored as well as any other decoration of the graph (vertex position,
184
+ etc.).
185
+
186
+ If ``relabel`` is ``True``, the vertices of the new graph are relabeled with
187
+ integers in the range '0\cdots \mid vertices \mid -1'. The relabeling map is
188
+ returned if ``return_map`` is also ``True``.
189
+
190
+ TESTS:
191
+
192
+ Giving anything else than a Graph::
193
+
194
+ sage: from sage.graphs.hyperbolicity import _my_subgraph as mysub
195
+ sage: mysub([], [])
196
+ Traceback (most recent call last):
197
+ ...
198
+ ValueError: the input parameter must be a Graph
199
+
200
+ Subgraph of a PetersenGraph::
201
+
202
+ sage: from sage.graphs.hyperbolicity import _my_subgraph as mysub
203
+ sage: H = mysub(graphs.PetersenGraph(), [0,2,4,6])
204
+ sage: H.edges(sort=True, labels=None)
205
+ [(0, 4)]
206
+ sage: H.vertices(sort=True)
207
+ [0, 2, 4, 6]
208
+ """
209
+ from sage.graphs.graph import Graph
210
+ if not isinstance(G, Graph):
211
+ raise ValueError("the input parameter must be a Graph")
212
+ H = Graph()
213
+ if not vertices:
214
+ return (H, {}) if (relabel and return_map) else H
215
+
216
+ if relabel:
217
+ map = dict(zip(iter(vertices), range(len(vertices))))
218
+ else:
219
+ map = dict(zip(iter(vertices), iter(vertices)))
220
+
221
+ B = {}
222
+ for v in G.vertex_iterator():
223
+ B[v] = False
224
+ for v in vertices:
225
+ B[v] = True
226
+ H.add_vertex(map[v])
227
+
228
+ for u in vertices:
229
+ for v in G.neighbor_iterator(u):
230
+ if B[v]:
231
+ H.add_edge(map[u], map[v])
232
+
233
+ return (H, map) if (relabel and return_map) else H
234
+
235
+
236
+ ######################################################################
237
+ # Building blocks
238
+ ######################################################################
239
+
240
+ cdef inline int __hyp__(unsigned short** distances, int a, int b, int c, int d) noexcept:
241
+ """
242
+ Return the hyperbolicity of the given 4-tuple.
243
+ """
244
+ cdef int S1, S2, S3, h
245
+ S1 = distances[a][b] + distances[c][d]
246
+ S2 = distances[a][c] + distances[b][d]
247
+ S3 = distances[a][d] + distances[b][c]
248
+ if S1 >= S2:
249
+ if S2 > S3:
250
+ h = S1 - S2
251
+ else:
252
+ h = abs(S1 - S3)
253
+ else:
254
+ if S1 > S3:
255
+ h = S2 - S1
256
+ else:
257
+ h = abs(S2 - S3)
258
+ return h
259
+
260
+
261
+ ######################################################################
262
+ # Basic algorithm for the hyperbolicity
263
+ ######################################################################
264
+
265
+ cdef tuple hyperbolicity_basic_algorithm(int N,
266
+ unsigned short** distances,
267
+ verbose):
268
+ """
269
+ Return **twice** the hyperbolicity of a graph, and a certificate.
270
+
271
+ This method implements the basic algorithm for computing the hyperbolicity
272
+ of a graph which tests all 4-tuples of vertices not satisfying a cutting
273
+ rule proposed in [Sot2011]_.
274
+
275
+ INPUT:
276
+
277
+ - ``N`` -- number of vertices of the graph
278
+
279
+ - ``distances`` -- path distance matrix (see the distance_all_pairs
280
+ module)
281
+
282
+ - ``verbose`` -- boolean (default: ``False``); set to ``True`` to display
283
+ some information during execution
284
+
285
+ OUTPUT:
286
+
287
+ This function returns a tuple ( h, certificate ), where:
288
+
289
+ - ``h`` -- the maximum computed value over all 4-tuples, and so is twice
290
+ the hyperbolicity of the graph. If no such 4-tuple is found, -1 is
291
+ returned.
292
+
293
+ - ``certificate`` -- 4-tuple of vertices maximizing the value `h`. If no
294
+ such 4-tuple is found, the empty list [] is returned.
295
+ """
296
+ cdef int a, b, c, d, hh, h_LB
297
+ cdef list certificate
298
+
299
+ h_LB = -1
300
+
301
+ for a in range(N - 3):
302
+ for b in range(a + 1, N - 2):
303
+
304
+ # We use the cutting rule proposed in [Sot2011]_
305
+ if 2 * distances[a][b] <= h_LB:
306
+ continue
307
+
308
+ for c in range(b + 1, N - 1):
309
+
310
+ # We use the cutting rule proposed in [Sot2011]_
311
+ if 2 * distances[a][c] <= h_LB or 2 * distances[b][c] <= h_LB:
312
+ continue
313
+
314
+ for d in range(c + 1, N):
315
+
316
+ # We compute the hyperbolicity of the 4-tuple
317
+ hh = __hyp__(distances, a, b, c, d)
318
+
319
+ # We compare the value with previously known bound
320
+ if hh > h_LB:
321
+ h_LB = hh
322
+ certificate = [a, b, c, d]
323
+
324
+ if verbose:
325
+ print('New lower bound:', ZZ(hh)/2)
326
+
327
+ # Last, we return the computed value and the certificate
328
+ if h_LB != -1:
329
+ return (h_LB, certificate)
330
+ return (-1, [])
331
+
332
+
333
+ ######################################################################
334
+ # Greedy dominating set
335
+ ######################################################################
336
+
337
+ def _greedy_dominating_set(H, verbose=False):
338
+ r"""
339
+ Return a greedy approximation of a dominating set.
340
+
341
+ EXAMPLES::
342
+
343
+ sage: from sage.graphs.hyperbolicity import _greedy_dominating_set
344
+ sage: G = graphs.PetersenGraph()
345
+ sage: _greedy_dominating_set(G)
346
+ [0, 2, 6]
347
+ """
348
+ cdef list V = sorted([(d, u) for u, d in H.degree_iterator(labels=True)],
349
+ reverse=True, key=lambda x: x[0])
350
+ cdef list DOM = []
351
+ cdef set seen = set()
352
+ for _, u in V:
353
+ if u not in seen:
354
+ seen.add(u)
355
+ DOM.append(u)
356
+ seen.update(H.neighbor_iterator(u))
357
+
358
+ if verbose:
359
+ print("Greedy dominating set: {}".format(DOM))
360
+
361
+ return DOM
362
+
363
+
364
+ ######################################################################
365
+ # Distances and far-apart pairs
366
+ ######################################################################
367
+
368
+ cdef inline distances_and_far_apart_pairs(gg,
369
+ unsigned short* distances,
370
+ unsigned short* far_apart_pairs,
371
+ list int_to_vertex):
372
+ """
373
+ Compute both distances between all pairs and far-apart pairs.
374
+
375
+ See the module's documentation for the definition of far-apart pairs.
376
+
377
+ This method assumes that:
378
+
379
+ - The input graph gg is connected. If not, the result will be incorrect.
380
+
381
+ - The arrays distances and far_apart_pairs have already been allocated with
382
+ size `n^2`.
383
+ """
384
+ cdef uint32_t n = gg.order()
385
+ cdef uint32_t i
386
+
387
+ if not distances or not far_apart_pairs:
388
+ raise ValueError("distances or far_apart_pairs is a NULL pointer")
389
+ elif n > <unsigned short> -1:
390
+ # Computing the distances/far_apart_pairs can only be done if we have
391
+ # less than MAX_UNSIGNED_SHORT vertices.
392
+ raise ValueError("The graph backend contains more than {} nodes and "
393
+ "we cannot compute the matrix of distances/far-apart "
394
+ "pairs on something"
395
+ "like that!".format(<unsigned short> -1))
396
+
397
+ # The list of waiting vertices
398
+ cdef MemoryAllocator mem = MemoryAllocator()
399
+ cdef uint32_t* waiting_list = <uint32_t*>mem.allocarray(n, sizeof(uint32_t))
400
+ cdef unsigned short** c_far_apart = <unsigned short**>mem.allocarray(n, sizeof(unsigned short*))
401
+
402
+ # The vertices which have already been visited
403
+ cdef bitset_t seen
404
+ bitset_init(seen, n)
405
+
406
+ # the beginning and the end of the list stored in waiting_list
407
+ cdef uint32_t waiting_beginning, waiting_end
408
+
409
+ cdef uint32_t source
410
+ cdef uint32_t v, u
411
+
412
+ # All pairs are initially far-apart
413
+ memset(far_apart_pairs, 1, n * n * sizeof(unsigned short))
414
+ for i in range(n):
415
+ c_far_apart[i] = far_apart_pairs + i * n
416
+ c_far_apart[i][i] = 0
417
+
418
+ # Copying the whole graph to obtain the list of neighbors quicker than by
419
+ # calling out_neighbors. This data structure is well documented in the
420
+ # module sage.graphs.base.static_sparse_graph
421
+ cdef short_digraph sd
422
+ init_short_digraph(sd, gg, edge_labelled=False, vertex_list=int_to_vertex)
423
+ cdef uint32_t** p_vertices = sd.neighbors
424
+ cdef uint32_t* p_tmp
425
+ cdef uint32_t* end
426
+
427
+ cdef unsigned short* c_distances = distances
428
+
429
+ memset(distances, -1, n * n * sizeof(unsigned short))
430
+
431
+ # We run n different BFS taking each vertex as a source
432
+ for source in range(n):
433
+
434
+ # The source is seen
435
+ bitset_clear(seen)
436
+ bitset_add(seen, source)
437
+ c_distances[source] = 0
438
+
439
+ # and added to the queue
440
+ waiting_list[0] = source
441
+ waiting_beginning = 0
442
+ waiting_end = 0
443
+
444
+ # For as long as there are vertices left to explore
445
+ while waiting_beginning <= waiting_end:
446
+
447
+ # We pick the first one
448
+ v = waiting_list[waiting_beginning]
449
+
450
+ p_tmp = p_vertices[v]
451
+ end = p_vertices[v+1]
452
+
453
+ # Iterating over all the outneighbors u of v
454
+ while p_tmp < end:
455
+ u = p_tmp[0]
456
+
457
+ # If we notice one of these neighbors is not seen yet, we set
458
+ # its parameters and add it to the queue to be explored later.
459
+ if not bitset_in(seen, u):
460
+ c_distances[u] = c_distances[v] + 1
461
+ bitset_add(seen, u)
462
+ waiting_end += 1
463
+ waiting_list[waiting_end] = u
464
+
465
+ if c_distances[u] == c_distances[v] + 1:
466
+ # v is on the path from source to u
467
+ c_far_apart[source][v] = 0
468
+ c_far_apart[v][source] = 0
469
+
470
+ p_tmp += 1
471
+
472
+ waiting_beginning += 1
473
+
474
+ c_distances += n
475
+
476
+ bitset_free(seen)
477
+ free_short_digraph(sd)
478
+
479
+
480
+ cdef inline pair** sort_pairs(uint32_t N,
481
+ uint16_t D,
482
+ unsigned short** values,
483
+ unsigned short** to_include,
484
+ uint32_t* nb_p,
485
+ uint32_t* nb_pairs_of_length) noexcept:
486
+ """
487
+ Return an array of unordered pairs {i,j} in increasing order of values.
488
+
489
+ Uses counting sort to list pairs {i,j} in increasing order of values(i,j).
490
+ If to_include[i][j] = 0, the pair is ignored. We assume N and D to be
491
+ correct with respect to the arrays values and to_include, that values and
492
+ to_include are symmetric (that is, values[i][j] = values[j][i] and
493
+ to_include[i][j] = to_include[j][i], and that nb_p, nb_pairs_of_length are
494
+ already allocated.
495
+
496
+ INPUT:
497
+
498
+ - ``N`` -- the range of i and j (that is, the square root of the number
499
+ of pairs to be sorted);
500
+
501
+ - ``D`` -- the maximum value of an element;
502
+
503
+ - ``values`` -- an array containing in position (i,j) the value of the
504
+ pair (i,j);
505
+
506
+ - ``to_include`` -- an array such that to_include[i][j] contains "1" if
507
+ pair (i,j) should be included, "0" otherwise. If NULL, all elements are
508
+ included;
509
+
510
+ OUTPUT:
511
+
512
+ - ``nb_p`` -- the number of pairs to be included;
513
+
514
+ - ``nb_pairs_of_length`` -- an array containing in position k the number
515
+ of pairs (i,j) that are included and such that values[i][j] = k
516
+
517
+ - ``pairs_of_length`` -- this function returns this array, containing in
518
+ position k a pointer to the first included pair (i,j) such that
519
+ values[i][j] = k.
520
+ """
521
+ # pairs_of_length[d] is the list of pairs of vertices at distance d
522
+ cdef pair** pairs_of_length = <pair**>check_allocarray(D + 1, sizeof(pair*))
523
+ cdef unsigned short* p_to_include
524
+ cdef uint32_t i, j, k
525
+ nb_p[0] = 0
526
+
527
+ # fills nb_pairs_of_length and nb_p
528
+ memset(nb_pairs_of_length, 0, (D + 1) * sizeof(uint32_t))
529
+
530
+ if not to_include:
531
+ nb_p[0] = (N * (N - 1)) / 2
532
+ for i in range(N):
533
+ for j in range(i + 1, N):
534
+ nb_pairs_of_length[values[i][j]] += 1
535
+ else:
536
+ for i in range(N):
537
+ p_to_include = to_include[i]
538
+ for j in range(i + 1, N):
539
+ if p_to_include[j]:
540
+ nb_p[0] += 1
541
+ nb_pairs_of_length[values[i][j]] += 1
542
+
543
+ pairs_of_length[0] = <pair*>check_allocarray(nb_p[0], sizeof(pair))
544
+
545
+ # temporary variable used to fill pairs_of_length
546
+ cdef uint32_t* cpt_pairs = <uint32_t*>check_calloc(D + 1, sizeof(uint32_t))
547
+
548
+ # ==> Defines pairs_of_length[d] for all d
549
+ for i in range(1, D + 1):
550
+ pairs_of_length[i] = pairs_of_length[i - 1] + nb_pairs_of_length[i - 1]
551
+
552
+ # ==> Fills pairs_of_length[d] for all d
553
+ if not to_include:
554
+ for i in range(N):
555
+ for j in range(i + 1, N):
556
+ k = values[i][j]
557
+ if k:
558
+ pairs_of_length[k][cpt_pairs[k]].s = i
559
+ pairs_of_length[k][cpt_pairs[k]].t = j
560
+ cpt_pairs[k] += 1
561
+ else:
562
+ for i in range(N):
563
+ p_to_include = to_include[i]
564
+ for j in range(i + 1, N):
565
+ if p_to_include[j]:
566
+ k = values[i][j]
567
+ pairs_of_length[k][cpt_pairs[k]].s = i
568
+ pairs_of_length[k][cpt_pairs[k]].t = j
569
+ cpt_pairs[k] += 1
570
+
571
+ sig_free(cpt_pairs)
572
+ return pairs_of_length
573
+
574
+
575
+ ######################################################################
576
+ # Compute the hyperbolicity using the algorithm of [BCCM2015]_
577
+ ######################################################################
578
+
579
+ cdef tuple hyperbolicity_BCCM(int N,
580
+ unsigned short** distances,
581
+ unsigned short** far_apart_pairs,
582
+ int D,
583
+ int h_LB,
584
+ float approximation_factor,
585
+ float additive_gap,
586
+ verbose=False):
587
+ """
588
+ Return the hyperbolicity of a graph.
589
+
590
+ This method implements the exact and the approximate algorithms proposed in
591
+ [BCCM2015]_. See the module's documentation for more details.
592
+
593
+ This method assumes that the graph under consideration is connected.
594
+
595
+ INPUT:
596
+
597
+ - ``N`` -- number of vertices of the graph
598
+
599
+ - ``distances`` -- path distance matrix
600
+
601
+ - ``far_apart_pairs`` -- 0/1 matrix of far-apart pairs. Pair ``(i,j)`` is
602
+ far-apart if ``far_apart_pairs[i][j]\neq 0``
603
+
604
+ - ``D`` -- diameter of the graph
605
+
606
+ - ``h_LB`` -- lower bound on the hyperbolicity
607
+
608
+ - ``approximation_factor`` -- when the approximation factor is set to some
609
+ value larger than 1.0, the function stop computations as soon as the
610
+ ratio between the upper bound and the best found solution is less than
611
+ the approximation factor. When the approximation factor is 1.0, the
612
+ problem is solved optimally.
613
+
614
+ - ``additive_gap`` -- when set to a positive number, the function stop
615
+ computations as soon as the difference between the upper bound and the
616
+ best found solution is less than additive gap. When the gap is 0.0, the
617
+ problem is solved optimally.
618
+
619
+ - ``verbose`` -- boolean (default: ``False``); set to ``True`` to display
620
+ some information during execution
621
+
622
+ OUTPUT:
623
+
624
+ This function returns a tuple ( h, certificate, h_UB ), where:
625
+
626
+ - ``h`` -- integer; when 4-tuples with hyperbolicity larger or equal
627
+ to `h_LB are found, h is the maximum computed value and so twice the
628
+ hyperbolicity of the graph. If no such 4-tuple is found, it returns -1.
629
+
630
+ - ``certificate`` -- is a list of vertices; when 4-tuples with
631
+ hyperbolicity larger that h_LB are found, certificate is the list of the
632
+ 4 vertices for which the maximum value (and so the hyperbolicity of the
633
+ graph) has been computed. If no such 4-tuple is found, it returns the
634
+ empty list [].
635
+
636
+ - ``h_UB`` -- integer equal to the proven upper bound for `h`; when
637
+ ``h == h_UB``, the returned solution is optimal
638
+ """
639
+ cdef MemoryAllocator mem = MemoryAllocator()
640
+ cdef int h = 0, hh # can get negative value
641
+ cdef int a, b, c, d, h_UB, n_val, n_acc, i, j
642
+ cdef int hplusone
643
+ cdef int condacc
644
+ cdef int x, S1, S2, S3
645
+ cdef list certificate = []
646
+ cdef uint32_t nb_p # The total number of pairs.
647
+ cdef unsigned short *dist_a
648
+ cdef unsigned short *dist_b
649
+ cdef bint GOTO_RETURN = 0
650
+
651
+ # Variable used to store "mates".
652
+ cdef int **mate = <int**> mem.malloc(N * sizeof(int*))
653
+ for i in range(N):
654
+ mate[i] = <int*> mem.malloc(N * sizeof(int))
655
+ cdef int *cont_mate = <int*> mem.calloc(N, sizeof(int))
656
+
657
+ # The farness of all vertices (the farness of v is the sum of the distances
658
+ # between v and all other vertices).
659
+ cdef uint64_t *farness = <uint64_t*> mem.calloc(N, sizeof(uint64_t))
660
+ cdef short *ecc = <short*> mem.calloc(N, sizeof(short))
661
+ cdef int central = 0
662
+ cdef int **mates_decr_order_value = <int**> mem.malloc(N * sizeof(int*))
663
+ cdef int *value = <int*> mem.malloc(N * sizeof(int))
664
+ cdef int *nvalues = <int*> mem.malloc((D + 1) * sizeof(int))
665
+ cdef short *acc_bool = <short*> mem.calloc(N, sizeof(short))
666
+ cdef int *acc = <int*> mem.malloc(N * sizeof(int))
667
+ cdef int *val = <int*> mem.malloc(N * sizeof(int))
668
+ cdef int *nvalues_cum = <int*> mem.malloc((D + 1) * sizeof(int))
669
+ cdef uint64_t nq = 0
670
+
671
+ # We compute the farness and the eccentricity of all vertices.
672
+ # We set central as the vertex with minimum farness
673
+ for a in range(N):
674
+ dist_a = distances[a]
675
+ for b in range(N):
676
+ farness[a] += dist_a[b]
677
+ ecc[a] = max(ecc[a], dist_a[b])
678
+ if dist_a[b] >= N:
679
+ raise ValueError("the input graph must be connected")
680
+ if farness[a] < farness[central]:
681
+ central = a
682
+ cdef unsigned short *dist_central = distances[central]
683
+
684
+ # We put in variable mates_decr_order_value[a] all vertices b, in
685
+ # decreasing order of ecc[b]-distances[a][b]
686
+ for a in range(N):
687
+ mates_decr_order_value[a] = <int*> mem.malloc(N * sizeof(int))
688
+ dist_a = distances[a]
689
+ memset(nvalues, 0, (D + 1) * sizeof(int))
690
+
691
+ for b in range(N):
692
+ value[b] = ecc[b] - dist_a[b]
693
+ nvalues[value[b]] += 1
694
+ nvalues_cum[D] = 0
695
+
696
+ for b in range(D - 1, -1, -1):
697
+ nvalues_cum[b] = nvalues_cum[b + 1] + nvalues[b + 1]
698
+
699
+ for b in range(N):
700
+ mates_decr_order_value[a][nvalues_cum[value[b]]] = b
701
+ nvalues_cum[value[b]] += 1
702
+
703
+ # We sort pairs, in increasing order of distance
704
+ cdef uint32_t * nb_pairs_of_length = <uint32_t *> mem.calloc(D + 1, sizeof(uint32_t))
705
+
706
+ cdef pair ** pairs_of_length = sort_pairs(N, D, distances, far_apart_pairs,
707
+ &nb_p, nb_pairs_of_length)
708
+
709
+ if verbose:
710
+ print("Current 2 connected component has %d vertices and diameter %d" % (N, D))
711
+ if not far_apart_pairs:
712
+ print("Number of pairs: %d" % (nb_p))
713
+ print("Repartition of pairs:",
714
+ [(i, nb_pairs_of_length[i]) for i in range(1, D + 1) if nb_pairs_of_length[i] > 0])
715
+ else:
716
+ print("Number of far-apart pairs: %d\t(%d pairs in total)" % (nb_p, binomial(N, 2)))
717
+ print("Repartition of far-apart pairs:",
718
+ [(i, nb_pairs_of_length[i]) for i in range(1, D + 1) if nb_pairs_of_length[i] > 0])
719
+
720
+ cdef pair * sorted_pairs = pairs_of_length[0]
721
+
722
+ approximation_factor = min(approximation_factor, D)
723
+ additive_gap = min(additive_gap, D)
724
+
725
+ # We start iterating from pairs with maximum distance.
726
+ for x in range(nb_p - 1, -1, -1):
727
+ a = sorted_pairs[x].s
728
+ b = sorted_pairs[x].t
729
+
730
+ # Without loss of generality, a has smaller farness than b.
731
+ if farness[a] < farness[b]:
732
+ a, b = b, a
733
+
734
+ dist_a = distances[a]
735
+ dist_b = distances[b]
736
+ h_UB = distances[a][b]
737
+
738
+ # If we cannot improve further, we stop
739
+ if h_UB <= h:
740
+ h_UB = h
741
+ GOTO_RETURN = 1
742
+ break
743
+
744
+ # Termination if required approximation is found
745
+ if (h_UB <= h * approximation_factor) or (h_UB - h <= additive_gap):
746
+ GOTO_RETURN = 1
747
+ break
748
+
749
+ # We update variable mate, adding pair (a,b)
750
+ mate[a][cont_mate[a]] = b
751
+ cont_mate[a] += 1
752
+ mate[b][cont_mate[b]] = a
753
+ cont_mate[b] += 1
754
+
755
+ # We compute acceptable and valuable vertices
756
+ n_acc = 0
757
+ n_val = 0
758
+
759
+ hplusone = h + 1
760
+ condacc = 3 * hplusone - 2 * h_UB
761
+
762
+ for i in range(N):
763
+ c = mates_decr_order_value[a][i]
764
+ if cont_mate[c] > 0:
765
+ if 2 * (ecc[c] - dist_a[c]) >= condacc:
766
+ if 2 * (ecc[c] - dist_b[c]) >= condacc:
767
+ if 2 * dist_a[c] >= hplusone and 2 * dist_b[c] >= hplusone:
768
+ if 2 * ecc[c] >= 2 * hplusone - h_UB + dist_a[c] + dist_b[c]:
769
+ # Vertex c is acceptable
770
+ acc_bool[c] = 1
771
+ acc[n_acc] = c
772
+ n_acc += 1
773
+ if 2 * dist_central[c] + h_UB - h > dist_a[c] + dist_b[c]:
774
+ # Vertex c is valuable
775
+ val[n_val] = c
776
+ n_val += 1
777
+ else:
778
+ break
779
+
780
+ # For each pair (c,d) where c is valuable and d is acceptable, we
781
+ # compute the hyperbolicity of (a,b,c,d), and we update h if necessary
782
+ for i in range(n_val):
783
+ c = val[i]
784
+ for j in range(cont_mate[c]):
785
+ d = mate[c][j]
786
+ if (acc_bool[d]):
787
+ nq += 1
788
+ S1 = h_UB + distances[c][d]
789
+ S2 = dist_a[c] + dist_b[d]
790
+ S3 = dist_a[d] + dist_b[c]
791
+ if S2 > S3:
792
+ hh = S1 - S2
793
+ else:
794
+ hh = S1 - S3
795
+
796
+ if h < hh or not certificate:
797
+ # We update current bound on the hyperbolicity and the
798
+ # search space.
799
+ #
800
+ # Note that if hh==0, we first make sure that a,b,c,d are
801
+ # all distinct and are a valid certificate.
802
+ if hh > 0 or not (a == c or a == d or b == c or b == d):
803
+ h = hh
804
+ certificate = [a, b, c, d]
805
+
806
+ if verbose:
807
+ print("New lower bound:", ZZ(hh)/2)
808
+
809
+ # We reset acc_bool
810
+ for v in range(n_acc):
811
+ acc_bool[acc[v]] = 0
812
+
813
+ # Needed because sometimes h_UB is not updated, if the analysis is no cut.
814
+ if not GOTO_RETURN:
815
+ h_UB = h
816
+
817
+ # We now free the memory
818
+ sig_free(pairs_of_length[0])
819
+ sig_free(pairs_of_length)
820
+
821
+ if verbose:
822
+ print("Visited 4-tuples:", nq)
823
+
824
+ # Last, we return the computed value and the certificate
825
+ if not certificate:
826
+ return (-1, [], h_UB)
827
+
828
+ # When using far-apart pairs, the loops may end before improving the
829
+ # upper-bound
830
+ return (h, certificate, h_UB)
831
+
832
+
833
+ ######################################################################
834
+ # Compute the hyperbolicity using the algorithm of [CCL2015]_
835
+ ######################################################################
836
+
837
+ cdef tuple hyperbolicity_CCL(int N,
838
+ unsigned short** distances,
839
+ unsigned short** far_apart_pairs,
840
+ int D,
841
+ int h_LB,
842
+ float approximation_factor,
843
+ float additive_gap,
844
+ verbose=False):
845
+ """
846
+ Return the hyperbolicity of a graph.
847
+
848
+ This method implements the exact and the approximate algorithms proposed in
849
+ [CCL2015]_. See the module's documentation for more details.
850
+
851
+ This method assumes that the graph under consideration is connected.
852
+
853
+ INPUT:
854
+
855
+ - ``N`` -- number of vertices of the graph
856
+
857
+ - ``distances`` -- path distance matrix
858
+
859
+ - ``far_apart_pairs`` -- 0/1 matrix of far-apart pairs. Pair ``(i,j)`` is
860
+ far-apart if ``far_apart_pairs[i][j]\neq 0``
861
+
862
+ - ``D`` -- diameter of the graph
863
+
864
+ - ``h_LB`` -- lower bound on the hyperbolicity
865
+
866
+ - ``approximation_factor`` -- when the approximation factor is set to some
867
+ value larger than 1.0, the function stop computations as soon as the
868
+ ratio between the upper bound and the best found solution is less than
869
+ the approximation factor. When the approximation factor is 1.0, the
870
+ problem is solved optimally.
871
+
872
+ - ``additive_gap`` -- when set to a positive number, the function stop
873
+ computations as soon as the difference between the upper bound and the
874
+ best found solution is less than additive gap. When the gap is 0.0, the
875
+ problem is solved optimally.
876
+
877
+ - ``verbose`` -- boolean (default: ``False``); set to ``True`` to display
878
+ some information during execution
879
+
880
+ OUTPUT:
881
+
882
+ This function returns a tuple ( h, certificate, h_UB ), where:
883
+
884
+ - ``h`` -- integer; when 4-tuples with hyperbolicity larger or equal
885
+ to `h_LB are found, h is the maximum computed value and so twice the
886
+ hyperbolicity of the graph. If no such 4-tuple is found, it returns -1.
887
+
888
+ - ``certificate`` -- is a list of vertices; when 4-tuples with
889
+ hyperbolicity larger that h_LB are found, certificate is the list of the
890
+ 4 vertices for which the maximum value (and so the hyperbolicity of the
891
+ graph) has been computed. If no such 4-tuple is found, it returns the
892
+ empty list [].
893
+
894
+ - ``h_UB`` -- integer equal to the proven upper bound for `h`; when
895
+ ``h == h_UB``, the returned solution is optimal
896
+ """
897
+ cdef int hh # can get negative value
898
+ cdef int a, b, c, d, h, h_UB
899
+ cdef int l1, l2, S1, S2, S3
900
+ cdef uint32_t x, y
901
+ cdef list certificate = []
902
+ cdef uint32_t nb_p # The total number of pairs
903
+
904
+ # Test if the distance matrix corresponds to a connected graph, i.e., if
905
+ # distances from node 0 are all less or equal to N-1.
906
+ for a in range(N):
907
+ if distances[0][a] >= N:
908
+ raise ValueError("the input graph must be connected")
909
+
910
+ # nb_pairs_of_length[d] is the number of pairs of vertices at distance d
911
+ cdef uint32_t* nb_pairs_of_length = <uint32_t*>check_allocarray(D + 1, sizeof(uint32_t))
912
+
913
+ if not nb_pairs_of_length:
914
+ raise MemoryError
915
+
916
+ cdef pair** pairs_of_length = sort_pairs(N, D, distances, far_apart_pairs,
917
+ &nb_p, nb_pairs_of_length)
918
+
919
+ if verbose:
920
+ print("Current 2 connected component has %d vertices and diameter %d" % (N, D))
921
+ if not far_apart_pairs:
922
+ print("Number of pairs: %d" % (nb_p))
923
+ print("Repartition of pairs:",
924
+ [(i, nb_pairs_of_length[i]) for i in range(1, D + 1) if nb_pairs_of_length[i] > 0])
925
+ else:
926
+ print("Number of far-apart pairs: %d\t(%d pairs in total)" % (nb_p, binomial(N, 2)))
927
+ print("Repartition of far-apart pairs:",
928
+ [(i, nb_pairs_of_length[i]) for i in range(1, D + 1) if nb_pairs_of_length[i] > 0])
929
+
930
+ approximation_factor = min(approximation_factor, D)
931
+ additive_gap = min(additive_gap, D)
932
+
933
+ # We create the list of triples (sum,length1,length2) sorted in decreasing
934
+ # lexicographic order: decreasing by sum, decreasing by length2, decreasing
935
+ # length1. This is to ensure a valid ordering for S1, to avoid some tests,
936
+ # and to ease computation of bounds.
937
+ cdef list triples = []
938
+ for l2 in range(D, 0, -1):
939
+ if nb_pairs_of_length[l2]:
940
+ for l1 in range(D, l2 - 1, -1):
941
+ if nb_pairs_of_length[l1]:
942
+ triples.append((l1 + l2, l1, l2))
943
+
944
+ # We use some short-cut variables for efficiency
945
+ cdef pair* pairs_of_length_l1
946
+ cdef pair* pairs_of_length_l2
947
+ cdef uint32_t nb_pairs_of_length_l1, nb_pairs_of_length_l2
948
+ cdef unsigned short * dist_a
949
+ cdef unsigned short * dist_b
950
+ h = h_LB
951
+ h_UB = D
952
+ cdef int GOTO_RETURN = 0
953
+
954
+ # S1 = l1+l2
955
+ # l1 = dist(a,b)
956
+ # l2 = dist(c,d)
957
+ # l1 >= l2
958
+ for S1, l1, l2 in triples:
959
+
960
+ if h_UB > l2:
961
+ h_UB = l2
962
+
963
+ if verbose:
964
+ print("New upper bound:", ZZ(h_UB) / 2)
965
+
966
+ # Termination if required approximation is found
967
+ if certificate and ((h_UB <= h * approximation_factor) or (h_UB-h <= additive_gap)):
968
+ GOTO_RETURN = 1
969
+ break
970
+
971
+ # If we cannot improve further, we stop
972
+ #
973
+ # See the module's documentation for a proof that this cut is
974
+ # valid. Remember that the triples are sorted in a specific order.
975
+ if h_UB <= h:
976
+ h_UB = h
977
+ break
978
+
979
+ pairs_of_length_l1 = pairs_of_length[l1]
980
+ pairs_of_length_l2 = pairs_of_length[l2]
981
+ nb_pairs_of_length_l1 = nb_pairs_of_length[l1]
982
+ nb_pairs_of_length_l2 = nb_pairs_of_length[l2]
983
+
984
+ for x in range(nb_pairs_of_length_l1):
985
+ a = pairs_of_length_l1[x].s
986
+ b = pairs_of_length_l1[x].t
987
+ dist_a = distances[a]
988
+ dist_b = distances[b]
989
+
990
+ # We do not want to test pairs of pairs twice if l1 == l2
991
+ for y in range((x + 1) if l1 == l2 else 0, nb_pairs_of_length_l2):
992
+ c = pairs_of_length_l2[y].s
993
+ d = pairs_of_length_l2[y].t
994
+
995
+ # We compute the hyperbolicity of the 4-tuple. We have S1 = l1 +
996
+ # l2, and the order in which pairs are visited allow us to claim
997
+ # that S1 = max( S1, S2, S3 ). Indeed, if S1 is not the maximum
998
+ # value, the order ensures that the maximum value has previously
999
+ # been checked.
1000
+ S2 = dist_a[c] + dist_b[d]
1001
+ S3 = dist_a[d] + dist_b[c]
1002
+ if S2 > S3:
1003
+ hh = S1 - S2
1004
+ else:
1005
+ hh = S1 - S3
1006
+
1007
+ if h < hh or not certificate:
1008
+ # We update current bound on the hyperbolicity and the
1009
+ # search space.
1010
+ #
1011
+ # Note that if hh==0, we first make sure that a,b,c,d are
1012
+ # all distinct and are a valid certificate.
1013
+ if hh > 0 or not (a == c or a == d or b == c or b == d):
1014
+ h = hh
1015
+ certificate = [a, b, c, d]
1016
+
1017
+ if verbose:
1018
+ print("New lower bound:", ZZ(hh) / 2)
1019
+
1020
+ # If we cannot improve further, we stop
1021
+ if l2 <= h:
1022
+ GOTO_RETURN = 1
1023
+ h_UB = h
1024
+ break
1025
+
1026
+ # Termination if required approximation is found
1027
+ if (h_UB <= h * approximation_factor) or (h_UB - h <= additive_gap):
1028
+ GOTO_RETURN = 1
1029
+ break
1030
+
1031
+ if GOTO_RETURN:
1032
+ break
1033
+
1034
+ if GOTO_RETURN:
1035
+ break
1036
+
1037
+ # We now free the memory
1038
+ sig_free(nb_pairs_of_length)
1039
+ sig_free(pairs_of_length[0])
1040
+ sig_free(pairs_of_length)
1041
+
1042
+ # Last, we return the computed value and the certificate
1043
+ if not certificate:
1044
+ return (-1, [], h_UB)
1045
+
1046
+ # When using far-apart pairs, the loops may end before improving the
1047
+ # upper-bound
1048
+ return (h, certificate, h_UB if GOTO_RETURN else h)
1049
+
1050
+
1051
+ def hyperbolicity(G,
1052
+ algorithm='BCCM',
1053
+ approximation_factor=None,
1054
+ additive_gap=None,
1055
+ verbose=False):
1056
+ r"""
1057
+ Return the hyperbolicity of the graph or an approximation of this value.
1058
+
1059
+ The hyperbolicity of a graph has been defined by Gromov [Gro1987]_ as
1060
+ follows: Let `a, b, c, d` be vertices of the graph, let `S_1 = dist(a, b) +
1061
+ dist(b, c)`, `S_2 = dist(a, c) + dist(b, d)`, and `S_3 = dist(a, d) +
1062
+ dist(b, c)`, and let `M_1` and `M_2` be the two largest values among `S_1`,
1063
+ `S_2`, and `S_3`. We have `hyp(a, b, c, d) = |M_1 - M_2|`, and the
1064
+ hyperbolicity of the graph is the maximum over all possible 4-tuples `(a,b,
1065
+ c,d)` divided by 2. The worst case time complexity is in `O( n^4 )`.
1066
+
1067
+ See the documentation of :mod:`sage.graphs.hyperbolicity` for more
1068
+ information.
1069
+
1070
+ INPUT:
1071
+
1072
+ - ``G`` -- a connected Graph
1073
+
1074
+ - ``algorithm`` -- (default: ``'BCCM'``) specifies the algorithm to use
1075
+ among:
1076
+
1077
+ - ``'basic'`` is an exhaustive algorithm considering all possible
1078
+ 4-tuples and so have time complexity in `O(n^4)`.
1079
+
1080
+ - ``'CCL'`` is an exact algorithm proposed in [CCL2015]_. It considers
1081
+ the 4-tuples in an ordering allowing to cut the search space as soon
1082
+ as a new lower bound is found (see the module's documentation). This
1083
+ algorithm can be turned into a approximation algorithm.
1084
+
1085
+ - ``'CCL+FA'`` or ``'CCL+'`` uses the notion of far-apart pairs as
1086
+ proposed in [Sot2011]_ to significantly reduce the overall
1087
+ computation time of the ``'CCL'`` algorithm.
1088
+
1089
+ - ``'BCCM'`` is an exact algorithm proposed in [BCCM2015]_. It
1090
+ improves ``'CCL+FA'`` by cutting several 4-tuples (for more
1091
+ information, see the module's documentation).
1092
+
1093
+ - ``'dom'`` is an approximation with additive constant four. It
1094
+ computes the hyperbolicity of the vertices of a dominating set of
1095
+ the graph. This is sometimes slower than ``'CCL'`` and sometimes
1096
+ faster. Try it to know if it is interesting for you.
1097
+ The ``additive_gap`` and ``approximation_factor`` parameters cannot
1098
+ be used in combination with this method and so are ignored.
1099
+
1100
+ - ``approximation_factor`` -- (default: ``None``) when the approximation factor
1101
+ is set to some value (larger than 1.0), the function stop computations as
1102
+ soon as the ratio between the upper bound and the best found solution is
1103
+ less than the approximation factor. When the approximation factor is 1.0,
1104
+ the problem is solved optimally. This parameter is used only when the
1105
+ chosen algorithm is ``'CCL'``, ``'CCL+FA'``, or ``'BCCM'``.
1106
+
1107
+ - ``additive_gap`` -- (default: ``None``) when set to a positive number, the
1108
+ function stop computations as soon as the difference between the upper
1109
+ bound and the best found solution is less than additive gap. When the gap
1110
+ is 0.0, the problem is solved optimally. This parameter is used only when
1111
+ the chosen algorithm is ``'CCL'`` or ``'CCL+FA'``, or ``'BCCM'``.
1112
+
1113
+ - ``verbose`` -- boolean (default: ``False``); set to ``True`` to display
1114
+ some information during execution: new upper and lower bounds, etc.
1115
+
1116
+ OUTPUT:
1117
+
1118
+ This function returns the tuple ( delta, certificate, delta_UB ), where:
1119
+
1120
+ - ``delta`` -- the hyperbolicity of the graph (half-integer value)
1121
+
1122
+ - ``certificate`` -- is the list of the 4 vertices for which the maximum
1123
+ value has been computed, and so the hyperbolicity of the graph
1124
+
1125
+ - ``delta_UB`` -- is an upper bound for ``delta``. When ``delta ==
1126
+ delta_UB``, the returned solution is optimal. Otherwise, the approximation
1127
+ factor if ``delta_UB/delta``.
1128
+
1129
+ EXAMPLES:
1130
+
1131
+ Hyperbolicity of a `3\times 3` grid::
1132
+
1133
+ sage: from sage.graphs.hyperbolicity import hyperbolicity
1134
+ sage: G = graphs.Grid2dGraph(3, 3)
1135
+ sage: L,C,U = hyperbolicity(G, algorithm='BCCM'); L,sorted(C),U
1136
+ (2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2)
1137
+ sage: L,C,U = hyperbolicity(G, algorithm='CCL'); L,sorted(C),U
1138
+ (2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2)
1139
+ sage: L,C,U = hyperbolicity(G, algorithm='basic'); L,sorted(C),U
1140
+ (2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2)
1141
+
1142
+ Hyperbolicity of a PetersenGraph::
1143
+
1144
+ sage: from sage.graphs.hyperbolicity import hyperbolicity
1145
+ sage: G = graphs.PetersenGraph()
1146
+ sage: L,C,U = hyperbolicity(G, algorithm='BCCM'); L,sorted(C),U
1147
+ (1/2, [6, 7, 8, 9], 1/2)
1148
+ sage: L,C,U = hyperbolicity(G, algorithm='CCL'); L,sorted(C),U
1149
+ (1/2, [0, 1, 2, 3], 1/2)
1150
+ sage: L,C,U = hyperbolicity(G, algorithm='CCL+'); L,sorted(C),U
1151
+ (1/2, [0, 1, 2, 3], 1/2)
1152
+ sage: L,C,U = hyperbolicity(G, algorithm='CCL+FA'); L,sorted(C),U
1153
+ (1/2, [0, 1, 2, 3], 1/2)
1154
+ sage: L,C,U = hyperbolicity(G, algorithm='basic'); L,sorted(C),U
1155
+ (1/2, [0, 1, 2, 3], 1/2)
1156
+ sage: L,C,U = hyperbolicity(G, algorithm='dom'); L,U
1157
+ (0, 1)
1158
+ sage: sorted(C) # random
1159
+ [0, 1, 2, 6]
1160
+
1161
+ Asking for an approximation in a grid graph::
1162
+
1163
+ sage: # needs sage.rings.real_mpfr
1164
+ sage: from sage.graphs.hyperbolicity import hyperbolicity
1165
+ sage: G = graphs.Grid2dGraph(2, 10)
1166
+ sage: L,C,U = hyperbolicity(G, algorithm='CCL', approximation_factor=1.5); L,U
1167
+ (1, 3/2)
1168
+ sage: L,C,U = hyperbolicity(G, algorithm='CCL+', approximation_factor=1.5); L,U
1169
+ (1, 1)
1170
+ sage: L,C,U = hyperbolicity(G, algorithm='CCL', approximation_factor=4); L,U
1171
+ (1, 4)
1172
+ sage: L,C,U = hyperbolicity(G, algorithm='CCL', additive_gap=2); L,U
1173
+ (1, 3)
1174
+ sage: L,C,U = hyperbolicity(G, algorithm='dom'); L,U
1175
+ (1, 5)
1176
+
1177
+ Asking for an approximation in a cycle graph::
1178
+
1179
+ sage: # needs sage.rings.real_mpfr
1180
+ sage: from sage.graphs.hyperbolicity import hyperbolicity
1181
+ sage: G = graphs.CycleGraph(10)
1182
+ sage: L,C,U = hyperbolicity(G, algorithm='CCL', approximation_factor=1.5); L,U
1183
+ (2, 5/2)
1184
+ sage: L,C,U = hyperbolicity(G, algorithm='CCL+FA', approximation_factor=1.5); L,U
1185
+ (2, 5/2)
1186
+ sage: L,C,U = hyperbolicity(G, algorithm='CCL+FA', additive_gap=1); L,U
1187
+ (2, 5/2)
1188
+
1189
+ Comparison of results::
1190
+
1191
+ sage: from sage.graphs.hyperbolicity import hyperbolicity
1192
+ sage: for i in range(10): # long time # needs networkx
1193
+ ....: G = graphs.RandomBarabasiAlbert(100,2)
1194
+ ....: d1,_,_ = hyperbolicity(G, algorithm='basic')
1195
+ ....: d2,_,_ = hyperbolicity(G, algorithm='CCL')
1196
+ ....: d3,_,_ = hyperbolicity(G, algorithm='CCL+')
1197
+ ....: d4,_,_ = hyperbolicity(G, algorithm='CCL+FA')
1198
+ ....: d5,_,_ = hyperbolicity(G, algorithm='BCCM')
1199
+ ....: l3,_,u3 = hyperbolicity(G, approximation_factor=2)
1200
+ ....: if (not d1==d2==d3==d4==d5) or l3>d1 or u3<d1:
1201
+ ....: print("That's not good!")
1202
+
1203
+ sage: from sage.graphs.hyperbolicity import hyperbolicity
1204
+ sage: import random
1205
+ sage: random.seed()
1206
+ sage: for i in range(10): # long time # needs networkx
1207
+ ....: n = random.randint(2, 20)
1208
+ ....: m = random.randint(0, n*(n-1) / 2)
1209
+ ....: G = graphs.RandomGNM(n, m)
1210
+ ....: for cc in G.connected_components_subgraphs():
1211
+ ....: d1,_,_ = hyperbolicity(cc, algorithm='basic')
1212
+ ....: d2,_,_ = hyperbolicity(cc, algorithm='CCL')
1213
+ ....: d3,_,_ = hyperbolicity(cc, algorithm='CCL+')
1214
+ ....: d4,_,_ = hyperbolicity(cc, algorithm='CCL+FA')
1215
+ ....: d5,_,_ = hyperbolicity(cc, algorithm='BCCM')
1216
+ ....: l3,_,u3 = hyperbolicity(cc, approximation_factor=2)
1217
+ ....: if (not d1==d2==d3==d4==d5) or l3>d1 or u3<d1:
1218
+ ....: print("Error in graph ", cc.edges(sort=True))
1219
+
1220
+ The hyperbolicity of a graph is the maximum value over all its biconnected
1221
+ components::
1222
+
1223
+ sage: from sage.graphs.hyperbolicity import hyperbolicity
1224
+ sage: G = graphs.PetersenGraph() * 2
1225
+ sage: G.add_edge(0, 11)
1226
+ sage: L,C,U = hyperbolicity(G); L,sorted(C),U
1227
+ (1/2, [6, 7, 8, 9], 1/2)
1228
+
1229
+ TESTS:
1230
+
1231
+ Giving anything else than a Graph::
1232
+
1233
+ sage: from sage.graphs.hyperbolicity import hyperbolicity
1234
+ sage: hyperbolicity([])
1235
+ Traceback (most recent call last):
1236
+ ...
1237
+ ValueError: the input parameter must be a Graph
1238
+
1239
+ Giving a non connected graph::
1240
+
1241
+ sage: from sage.graphs.hyperbolicity import hyperbolicity
1242
+ sage: G = Graph([(0,1),(2,3)])
1243
+ sage: hyperbolicity(G)
1244
+ Traceback (most recent call last):
1245
+ ...
1246
+ ValueError: the input Graph must be connected
1247
+
1248
+ Giving wrong approximation factor::
1249
+
1250
+ sage: from sage.graphs.hyperbolicity import hyperbolicity
1251
+ sage: G = graphs.PetersenGraph()
1252
+ sage: hyperbolicity(G, algorithm='CCL', approximation_factor=0.1)
1253
+ Traceback (most recent call last):
1254
+ ...
1255
+ ValueError: the approximation factor must be >= 1.0
1256
+
1257
+ Giving negative additive gap::
1258
+
1259
+ sage: from sage.graphs.hyperbolicity import hyperbolicity
1260
+ sage: G = Graph()
1261
+ sage: hyperbolicity(G, algorithm='CCL', additive_gap=-1)
1262
+ Traceback (most recent call last):
1263
+ ...
1264
+ ValueError: the additive gap must be a real positive number
1265
+
1266
+ Asking for an unknown algorithm::
1267
+
1268
+ sage: from sage.graphs.hyperbolicity import hyperbolicity
1269
+ sage: G = Graph()
1270
+ sage: hyperbolicity(G, algorithm='tip top')
1271
+ Traceback (most recent call last):
1272
+ ...
1273
+ ValueError: algorithm 'tip top' not yet implemented, please contribute
1274
+ """
1275
+
1276
+ # Abbreviations for algorithms are expanded.
1277
+ if algorithm == "CCL+":
1278
+ algorithm = "CCL+FA"
1279
+
1280
+ from sage.graphs.graph import Graph
1281
+ if not isinstance(G, Graph):
1282
+ raise ValueError("the input parameter must be a Graph")
1283
+ if algorithm not in ['basic', 'CCL', 'CCL+FA', 'BCCM', 'dom']:
1284
+ raise ValueError("algorithm '%s' not yet implemented, please contribute" % (algorithm))
1285
+ if approximation_factor is None:
1286
+ approximation_factor = 1.0
1287
+ elif approximation_factor == 1.0:
1288
+ pass
1289
+ elif algorithm in ['CCL', 'CCL+FA', 'BCCM']:
1290
+ from sage.rings.real_mpfr import RR
1291
+
1292
+ if approximation_factor not in RR or approximation_factor < 1.0:
1293
+ raise ValueError("the approximation factor must be >= 1.0")
1294
+ else:
1295
+ raise ValueError("the approximation_factor is ignored when using"
1296
+ "the '%s' algorithm" % (algorithm))
1297
+ if additive_gap is None:
1298
+ additive_gap = 0.0
1299
+ elif additive_gap == 0.0:
1300
+ pass
1301
+ elif algorithm in ['CCL', 'CCL+FA', 'BCCM']:
1302
+ from sage.rings.real_mpfr import RR
1303
+
1304
+ if additive_gap not in RR or additive_gap < 0.0:
1305
+ raise ValueError("the additive gap must be a real positive number")
1306
+ else:
1307
+ raise ValueError("the additive_gap is ignored when using the '%s' algorithm." % (algorithm))
1308
+
1309
+ # The hyperbolicity is defined on connected graphs
1310
+ if not G.is_connected():
1311
+ raise ValueError("the input Graph must be connected")
1312
+
1313
+ # The hyperbolicity of some classes of graphs is known. If it is easy and
1314
+ # fast to test that a graph belongs to one of these classes, we do it.
1315
+ if G.num_verts() <= 3:
1316
+ # The hyperbolicity of a graph with 3 vertices is 0.
1317
+ # The certificate is the set of vertices.
1318
+ return 0, list(G), 0
1319
+
1320
+ elif G.num_verts() == G.num_edges() + 1:
1321
+ # G is a tree
1322
+ # Any set of 4 vertices is a valid certificate
1323
+ return 0, list(G)[:4], 0
1324
+
1325
+ elif G.is_clique():
1326
+ # Any set of 4 vertices is a valid certificate
1327
+ return 0, list(G)[:4], 0
1328
+
1329
+ cdef int i, j, D
1330
+ cdef list certificate = []
1331
+ cdef list certif
1332
+
1333
+ cdef int N = G.num_verts()
1334
+ hyp = 0
1335
+ hyp_UB = 0
1336
+
1337
+ #
1338
+ # The hyperbolicity of a graph is the maximum over its 2-connected
1339
+ # components.
1340
+ #
1341
+ B, _ = G.blocks_and_cut_vertices()
1342
+ if len(B) > 1:
1343
+
1344
+ if verbose:
1345
+ # we compute the distribution of size of the blocks
1346
+ L = [len(V) for V in B]
1347
+ print("Graph with %d blocks" % (len(B)))
1348
+ print("Blocks size distribution:", {x: L.count(x) for x in L})
1349
+
1350
+ for V in B:
1351
+
1352
+ # The hyperbolicity of a graph with 3 vertices is 0, and a graph
1353
+ # cannot have hyperbolicity larger than N/2. So we consider only
1354
+ # larger 2-connected subgraphs.
1355
+ if len(V) > max(3, 2 * hyp):
1356
+
1357
+ hh, certif, hh_UB = hyperbolicity(_my_subgraph(G, V), algorithm=algorithm,
1358
+ approximation_factor=approximation_factor,
1359
+ additive_gap=additive_gap, verbose=verbose)
1360
+
1361
+ # We test if the new computed value improves upon previous value.
1362
+ if hh > hyp or (hh == hyp and not certificate):
1363
+ hyp = hh
1364
+ certificate = certif
1365
+
1366
+ # We update independently the upper bound for cases in which we
1367
+ # are asking for an approximation.
1368
+ hyp_UB = max(hyp_UB, hh_UB)
1369
+
1370
+ # Last, we return the computed value and the certificate
1371
+ return hyp, certificate, hyp_UB
1372
+
1373
+ #
1374
+ # Now the graph is 2-connected, has at least 4 vertices and is not a clique.
1375
+ #
1376
+
1377
+ cdef unsigned short* _distances_
1378
+ cdef unsigned short** distances
1379
+ cdef unsigned short* _far_apart_pairs_
1380
+ cdef unsigned short** far_apart_pairs
1381
+ cdef list int_to_vertex = list(G)
1382
+
1383
+ # We compute the distances and store the results in a 2D array
1384
+ distances = <unsigned short **>check_allocarray(N, sizeof(unsigned short *))
1385
+ if not distances:
1386
+ raise MemoryError("Unable to allocate array 'distances'.")
1387
+
1388
+ if algorithm == 'CCL+FA' or algorithm == 'BCCM':
1389
+ _distances_ = <unsigned short *>check_allocarray(N * N, sizeof(unsigned short))
1390
+ _far_apart_pairs_ = <unsigned short *>check_allocarray(N * N, sizeof(unsigned short))
1391
+ far_apart_pairs = <unsigned short **>check_allocarray(N, sizeof(unsigned short *))
1392
+
1393
+ distances_and_far_apart_pairs(G, _distances_, _far_apart_pairs_, int_to_vertex)
1394
+
1395
+ for i in range(N):
1396
+ far_apart_pairs[i] = _far_apart_pairs_ + i*N
1397
+
1398
+ else:
1399
+ _distances_ = c_distances_all_pairs(G, vertex_list=int_to_vertex)
1400
+ _far_apart_pairs_ = NULL
1401
+ far_apart_pairs = NULL
1402
+
1403
+ D = 0
1404
+ for i in range(N):
1405
+ distances[i] = _distances_ + i * N
1406
+ for j in range(i + 1, N):
1407
+ if distances[i][j] > D:
1408
+ D = distances[i][j]
1409
+
1410
+ # We call the cython function for computing the hyperbolicity with the
1411
+ # required parameters.
1412
+ if algorithm in ['CCL', 'CCL+FA']:
1413
+ sig_on()
1414
+ hyp, certif, hyp_UB = hyperbolicity_CCL(N, distances, far_apart_pairs, D, hyp,
1415
+ approximation_factor, 2 * additive_gap, verbose)
1416
+ sig_off()
1417
+
1418
+ elif algorithm == 'BCCM':
1419
+ sig_on()
1420
+ hyp, certif, hyp_UB = hyperbolicity_BCCM(N, distances, far_apart_pairs,
1421
+ D, hyp, approximation_factor,
1422
+ 2 * additive_gap, verbose)
1423
+ sig_off()
1424
+
1425
+ elif algorithm == 'dom':
1426
+ # Computes a dominating set DOM of G, and computes the hyperbolicity
1427
+ # considering only vertices in DOM
1428
+ DOM = set(_greedy_dominating_set(G, verbose=verbose))
1429
+ # We need at least 4 vertices
1430
+ while len(DOM) < 4:
1431
+ DOM.add(G.random_vertex())
1432
+ # We map the dominating set to [0..N-1]
1433
+ v_to_int = {v: i for i, v in enumerate(G.vertex_iterator())}
1434
+ DOM_int = set(v_to_int[v] for v in DOM)
1435
+ # We set null distances to vertices outside DOM. This way these
1436
+ # vertices will not be considered anymore.
1437
+ for i in range(N):
1438
+ if i not in DOM_int:
1439
+ for j in range(N):
1440
+ distances[i][j] = 0
1441
+ distances[j][i] = 0
1442
+ sig_on()
1443
+ hyp, certif, hyp_UB = hyperbolicity_CCL(N, distances, NULL, D, hyp, 1.0, 0.0, verbose)
1444
+ sig_off()
1445
+ hyp_UB = min(hyp + 8, D)
1446
+
1447
+ elif algorithm == 'basic':
1448
+ sig_on()
1449
+ hyp, certif = hyperbolicity_basic_algorithm(N, distances, verbose=verbose)
1450
+ sig_off()
1451
+ hyp_UB = hyp
1452
+
1453
+ # We now release the memory
1454
+ sig_free(distances)
1455
+ sig_free(_distances_)
1456
+ sig_free(_far_apart_pairs_)
1457
+ sig_free(far_apart_pairs)
1458
+
1459
+ # Map the certificate 'certif' with the corresponding vertices in the graph
1460
+ certificate = [int_to_vertex[i] for i in certif]
1461
+
1462
+ # Last, we return the computed value and the certificate
1463
+ return ZZ(hyp)/2, certificate, ZZ(hyp_UB)/2
1464
+
1465
+
1466
+ ######################################################################
1467
+ # Distribution of the hyperbolicity of 4-tuples
1468
+ ######################################################################
1469
+
1470
+ cdef dict __hyperbolicity_distribution__(int N, unsigned short** distances):
1471
+ """
1472
+ Return the distribution of the hyperbolicity of the 4-tuples of the graph.
1473
+
1474
+ The hyperbolicity of a graph has been defined by Gromov [Gro1987]_ as
1475
+ follows: Let `a, b, c, d` be vertices of the graph, let `S_1 = dist(a, b) +
1476
+ dist(b, c)`, `S_2 = dist(a, c) + dist(b, d)`, and `S_3 = dist(a, d) +
1477
+ dist(b, c)`, and let `M_1` and `M_2` be the two largest values among `S_1`,
1478
+ `S_2`, and `S_3`. We have `hyp(a, b, c, d) = |M_1 - M_2|`, and the
1479
+ hyperbolicity of the graph is the maximum over all possible 4-tuples `(a, b,
1480
+ c, d)` divided by 2.
1481
+
1482
+ The computation of the hyperbolicity of each 4-tuple, and so the
1483
+ hyperbolicity distribution, takes time in `O( n^4 )`.
1484
+
1485
+ We use ``unsigned long int`` on 64 bits, so ``uint64_t``, to count the
1486
+ number of 4-tuples of given hyperbolicity. So we cannot exceed `2^64-1`.
1487
+ This value should be sufficient for most users.
1488
+
1489
+ INPUT:
1490
+
1491
+ - ``N`` -- number of vertices of the graph (and side of the matrix)
1492
+
1493
+ - ``distances`` -- matrix of distances in the graph
1494
+
1495
+ OUTPUT:
1496
+
1497
+ - ``hdict`` -- dictionary such that hdict[i] is the number of 4-tuples of
1498
+ hyperbolicity i among the considered 4-tuples
1499
+ """
1500
+ # We initialize the table of hyperbolicity. We use an array of unsigned long
1501
+ # int instead of a dictionary since it is much faster.
1502
+ cdef int i
1503
+
1504
+ cdef uint64_t* hdistr = <uint64_t*>check_calloc(N + 1, sizeof(uint64_t))
1505
+ if not hdistr:
1506
+ raise MemoryError
1507
+
1508
+ # We now compute the hyperbolicity of each 4-tuple
1509
+ cdef int a, b, c, d
1510
+ for a in range(N - 3):
1511
+ for b in range(a + 1, N - 2):
1512
+ for c in range(b + 1, N - 1):
1513
+ for d in range(c + 1, N):
1514
+ hdistr[__hyp__(distances, a, b, c, d)] += 1
1515
+
1516
+ # We prepare the dictionary of hyperbolicity distribution to return
1517
+ Nchoose4 = binomial(N, 4)
1518
+ cdef dict hdict = {ZZ(i)/2: (ZZ(hdistr[i]) / Nchoose4) for i in range(N + 1) if hdistr[i] > 0}
1519
+
1520
+ sig_free(hdistr)
1521
+
1522
+ return hdict
1523
+
1524
+
1525
+ # We use this trick since it is way faster than using the sage randint function.
1526
+ cdef extern from "stdlib.h":
1527
+ long c_libc_random "random"()
1528
+ void c_libc_srandom "srandom"(unsigned int seed)
1529
+
1530
+
1531
+ cdef dict __hyperbolicity_sampling__(int N, unsigned short** distances, uint64_t sampling_size):
1532
+ """
1533
+ Return a sampling of the hyperbolicity distribution of the graph.
1534
+
1535
+ The hyperbolicity of a graph has been defined by Gromov [Gro1987]_ as
1536
+ follows: Let `a, b, c, d` be vertices of the graph, let `S_1 = dist(a, b) +
1537
+ dist(b, c)`, `S_2 = dist(a, c) + dist(b, d)`, and `S_3 = dist(a, d) +
1538
+ dist(b, c)`, and let `M_1` and `M_2` be the two largest values among `S_1`,
1539
+ `S_2`, and `S_3`. We have `hyp(a, b, c, d) = |M_1 - M_2|`, and the
1540
+ hyperbolicity of the graph is the maximum over all possible 4-tuples `(a, b,
1541
+ c, d)` divided by 2.
1542
+
1543
+ We use ``unsigned long int`` on 64 bits, so ``uint64_t``, to count the
1544
+ number of 4-tuples of given hyperbolicity. So we cannot exceed `2^64-1`.
1545
+ This value should be sufficient for most users.
1546
+
1547
+ INPUT:
1548
+
1549
+ - ``N`` -- number of vertices of the graph (and side of the matrix)
1550
+
1551
+ - ``distances`` -- matrix of distances in the graph
1552
+
1553
+ - ``sampling_size`` -- number of 4-tuples considered. Default value is 1000
1554
+
1555
+ OUTPUT:
1556
+
1557
+ - ``hdict`` -- dictionary such that hdict[i] is the number of 4-tuples of
1558
+ hyperbolicity i among the considered 4-tuples
1559
+ """
1560
+ cdef int i, a, b, c, d
1561
+ cdef uint64_t j
1562
+
1563
+ if N < 4:
1564
+ raise ValueError("N must be at least 4")
1565
+
1566
+ # We initialize the table of hyperbolicity. We use an array of unsigned long
1567
+ # int instead of a dictionary since it is much faster.
1568
+ cdef uint64_t* hdistr = <uint64_t*>check_calloc(N + 1, sizeof(uint64_t))
1569
+ if not hdistr:
1570
+ raise MemoryError
1571
+
1572
+ # We now compute the hyperbolicity of each quadruple
1573
+ for j in range(sampling_size):
1574
+ a = c_libc_random() % N
1575
+ b = c_libc_random() % N
1576
+ c = c_libc_random() % N
1577
+ d = c_libc_random() % N
1578
+ while a == b:
1579
+ b = c_libc_random() % N
1580
+ while a == c or b == c:
1581
+ c = c_libc_random() % N
1582
+ while a == d or b == d or c == d:
1583
+ d = c_libc_random() % N
1584
+
1585
+ hdistr[__hyp__(distances, a, b, c, d)] += 1
1586
+
1587
+ # We prepare the dictionary of hyperbolicity distribution from sampling
1588
+ cdef dict hdict = {ZZ(i)/2: ZZ(hdistr[i])/ZZ(sampling_size) for i in range(N + 1) if hdistr[i] > 0}
1589
+
1590
+ sig_free(hdistr)
1591
+
1592
+ return hdict
1593
+
1594
+
1595
+ def hyperbolicity_distribution(G, algorithm='sampling', sampling_size=10**6):
1596
+ r"""
1597
+ Return the hyperbolicity distribution of the graph or a sampling of it.
1598
+
1599
+ The hyperbolicity of a graph has been defined by Gromov [Gro1987]_ as
1600
+ follows: Let `a, b, c, d` be vertices of the graph, let `S_1 = dist(a, b) +
1601
+ dist(b, c)`, `S_2 = dist(a, c) + dist(b, d)`, and `S_3 = dist(a, d) +
1602
+ dist(b, c)`, and let `M_1` and `M_2` be the two largest values among `S_1`,
1603
+ `S_2`, and `S_3`. We have `hyp(a, b, c, d) = |M_1 - M_2|`, and the
1604
+ hyperbolicity of the graph is the maximum over all possible 4-tuples `(a, b,
1605
+ c, d)` divided by 2.
1606
+
1607
+ The computation of the hyperbolicity of each 4-tuple, and so the
1608
+ hyperbolicity distribution, takes time in `O( n^4 )`.
1609
+
1610
+ INPUT:
1611
+
1612
+ - ``G`` -- a Graph
1613
+
1614
+ - ``algorithm`` -- (default: ``'sampling'``) when algorithm is 'sampling', it
1615
+ returns the distribution of the hyperbolicity over a sample of
1616
+ ``sampling_size`` 4-tuples. When algorithm is 'exact', it computes the
1617
+ distribution of the hyperbolicity over all 4-tuples. Be aware that the
1618
+ computation time can be HUGE.
1619
+
1620
+ - ``sampling_size`` -- (default: `10^6`) number of 4-tuples considered in
1621
+ the sampling. Used only when ``algorithm == 'sampling'``
1622
+
1623
+ OUTPUT:
1624
+
1625
+ - ``hdict`` -- dictionary such that hdict[i] is the number of 4-tuples of
1626
+ hyperbolicity i
1627
+
1628
+ EXAMPLES:
1629
+
1630
+ Exact hyperbolicity distribution of the Petersen Graph::
1631
+
1632
+ sage: from sage.graphs.hyperbolicity import hyperbolicity_distribution
1633
+ sage: G = graphs.PetersenGraph()
1634
+ sage: hyperbolicity_distribution(G,algorithm='exact')
1635
+ {0: 3/7, 1/2: 4/7}
1636
+
1637
+ Exact hyperbolicity distribution of a `3\times 3` grid::
1638
+
1639
+ sage: from sage.graphs.hyperbolicity import hyperbolicity_distribution
1640
+ sage: G = graphs.GridGraph([3,3])
1641
+ sage: hyperbolicity_distribution(G,algorithm='exact')
1642
+ {0: 11/18, 1: 8/21, 2: 1/126}
1643
+
1644
+ TESTS:
1645
+
1646
+ Giving anything else than a Graph::
1647
+
1648
+ sage: from sage.graphs.hyperbolicity import hyperbolicity_distribution
1649
+ sage: hyperbolicity_distribution([])
1650
+ Traceback (most recent call last):
1651
+ ...
1652
+ ValueError: the input parameter must be a Graph
1653
+
1654
+ Giving a non connected graph::
1655
+
1656
+ sage: from sage.graphs.hyperbolicity import hyperbolicity_distribution
1657
+ sage: G = Graph([(0,1),(2,3)])
1658
+ sage: hyperbolicity_distribution(G)
1659
+ Traceback (most recent call last):
1660
+ ...
1661
+ ValueError: the input Graph must be connected
1662
+ """
1663
+ from sage.graphs.graph import Graph
1664
+ if not isinstance(G, Graph):
1665
+ raise ValueError("the input parameter must be a Graph")
1666
+ # The hyperbolicity is defined on connected graphs
1667
+ if not G.is_connected():
1668
+ raise ValueError("the input Graph must be connected")
1669
+
1670
+ # The hyperbolicity distribution of some classes of graphs is known. If it
1671
+ # is easy and fast to test that a graph belongs to one of these classes, we
1672
+ # do it.
1673
+ if (G.num_verts() == G.num_edges() + 1) or G.is_clique():
1674
+ return {0: sampling_size if algorithm=='sampling' else binomial(G.num_verts(), 4)}
1675
+
1676
+ cdef int N = G.num_verts()
1677
+ cdef int i
1678
+ cdef unsigned short** distances
1679
+ cdef unsigned short* _distances_
1680
+ cdef dict hdict
1681
+
1682
+ # We compute the all pairs shortest path and store the result in a 2D array
1683
+ # for faster access.
1684
+ _distances_ = c_distances_all_pairs(G, vertex_list=list(G))
1685
+ distances = <unsigned short**>check_allocarray(N, sizeof(unsigned short*))
1686
+ if not distances:
1687
+ sig_free(_distances_)
1688
+ raise MemoryError
1689
+
1690
+ for i in range(N):
1691
+ distances[i] = _distances_ + i * N
1692
+
1693
+ if algorithm == 'exact':
1694
+ hdict = __hyperbolicity_distribution__(N, distances)
1695
+ elif algorithm == 'sampling':
1696
+ hdict = __hyperbolicity_sampling__(N, distances, sampling_size)
1697
+ else:
1698
+ raise ValueError("algorithm '%s' not yet implemented, please contribute" % (algorithm))
1699
+
1700
+ # We release memory
1701
+ sig_free(distances)
1702
+ sig_free(_distances_)
1703
+
1704
+ return hdict