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,1375 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # cython: binding=True
3
+ # distutils: language = c++
4
+ r"""
5
+ Static sparse graphs
6
+
7
+ What is the point ?
8
+ -------------------
9
+
10
+ This class implements a Cython (di)graph structure made for efficiency. The
11
+ graphs are *static*, i.e. no add/remove vertex/edges methods are available, nor
12
+ can they easily or efficiently be implemented within this data structure.
13
+
14
+ The data structure, however, is made to save the maximum amount of computations
15
+ for graph algorithms whose main operation is to *list the out-neighbours of a
16
+ vertex* (which is precisely what BFS, DFS, distance computations and the
17
+ flow-related stuff waste their life on).
18
+
19
+ The code contained in this module is written C-style. The purpose is efficiency
20
+ and simplicity.
21
+
22
+ For an overview of graph data structures in sage, see
23
+ :mod:`~sage.graphs.base.overview`.
24
+
25
+ Author:
26
+
27
+ - Nathann Cohen (2011)
28
+
29
+ Data structure
30
+ --------------
31
+
32
+ .. image:: ../../../media/structure.png
33
+
34
+ The data structure is actually pretty simple and compact. ``short_digraph`` has
35
+ five fields
36
+
37
+ - ``n`` -- integer; the number of vertices in the graph
38
+
39
+ - ``m`` -- integer; the number of edges in the graph
40
+
41
+ - ``edges`` -- ``uint32_t *``; array whose length is the number of edges of the
42
+ graph
43
+
44
+ - ``neighbors`` -- ``uint32_t **``; this array has size `n+1`, and describes how
45
+ the data of ``edges`` should be read : the neighbors of vertex `i` are the
46
+ elements of ``edges`` addressed by ``neighbors[i]...neighbors[i+1]-1``. The
47
+ element ``neighbors[n]``, which corresponds to no vertex (they are numbered
48
+ from `0` to `n-1`) is present so that it remains easy to enumerate the
49
+ neighbors of vertex `n-1` : the last of them is the element addressed by
50
+ ``neighbors[n]-1``.
51
+ The arrays ``neighbors[i]`` are guaranteed to be sorted so the time
52
+ complexity for deciding if ``g`` has edge `(u, v)` is `O(\log{m})` using
53
+ binary search.
54
+
55
+ - ``edge_labels`` -- list; this cython list associates a label to each edge
56
+ of the graph. If a given edge is represented by ``edges[i]``, this its
57
+ associated label can be found at ``edge_labels[i]``. This object is usually
58
+ NULL, unless the call to ``init_short_digraph`` explicitly requires the labels
59
+ to be stored in the data structure.
60
+
61
+ In the example given above, vertex 0 has 2,3,5,7,8 and 9 as out-neighbors, but
62
+ not 4, which is an out-neighbour of vertex 1. Vertex `n-1` has 2, 5, 8 and 9 as
63
+ out-neighbors. ``neighbors[n]`` points toward the cell immediately *after* the
64
+ end of ``edges``, hence *outside of the allocated memory*. It is used to
65
+ indicate the end of the outneighbors of vertex `n-1`
66
+
67
+ **Iterating over the edges**
68
+
69
+ This is *the one thing* to have in mind when working with this data structure::
70
+
71
+ cdef list_edges(short_digraph g):
72
+ cdef int i, j
73
+ for i in range(g.n):
74
+ for j in range(g.neighbors[i+1]-g.neighbors[i]):
75
+ print("There is an edge from {} to {}".format(i, g.neighbors[i][j]))
76
+
77
+ **Advantages**
78
+
79
+ Two great points :
80
+
81
+ - The neighbors of a vertex are C types, and are contiguous in memory.
82
+ - Storing such graphs is incredibly cheaper than storing Python structures.
83
+
84
+ Well, I think it would be hard to have anything more efficient than that to
85
+ enumerate out-neighbors in sparse graphs ! :-)
86
+
87
+ Technical details
88
+ -----------------
89
+
90
+ * When creating a ``short_digraph`` from a ``Graph`` or ``DiGraph`` named ``G``,
91
+ the `i^{\text{th}}` vertex corresponds *by default* to ``list(G)[i]``.
92
+ Using optional parameter ``vertex_list``, you can specify the order of the
93
+ vertices. Then `i^{\text{th}}` vertex will corresponds to ``vertex_list[i]``.
94
+
95
+ * Some methods return ``bitset_t`` objects when lists could be expected. There
96
+ is a very useful ``bitset_list`` function for this kind of problems :-)
97
+
98
+ * When the edges are labelled, most of the space taken by this graph is taken by
99
+ edge labels. If no edge is labelled then this space is not allocated, but if
100
+ *any* edge has a label then a (possibly empty) label is stored for each edge,
101
+ which can double the memory needs.
102
+
103
+ * The data structure stores the number of edges, even though it appears that
104
+ this number can be reconstructed with ``g.neighbors[n]-g.neighbors[0]``. The
105
+ trick is that not all elements of the ``g.edges`` array are necessarily used :
106
+ when an undirected graph contains loops, only one entry of the array of size
107
+ `2m` is used to store it, instead of the expected two. Storing the number of
108
+ edges is the only way to avoid an uselessly costly computation to obtain the
109
+ number of edges of an undirected, looped, AND labelled graph (think of several
110
+ loops on the same vertex with different labels).
111
+
112
+ * The codes of this module are well documented, and many answers can be found
113
+ directly in the code.
114
+
115
+ Cython functions
116
+ ----------------
117
+
118
+ .. csv-table::
119
+ :class: contentstable
120
+ :widths: 30, 70
121
+ :delim: |
122
+
123
+ ``init_short_digraph(short_digraph g, G, edge_labelled, vertex_list)`` | Initialize ``short_digraph g`` from a Sage (Di)Graph.
124
+ ``int n_edges(short_digraph g)`` | Return the number of edges in ``g``
125
+ ``int out_degree(short_digraph g, int i)`` | Return the out-degree of vertex `i` in ``g``
126
+ ``has_edge(short_digraph g, int u, int v)`` | Test the existence of an edge.
127
+ ``edge_label(short_digraph g, int * edge)`` | Return the label associated with a given edge
128
+ ``init_empty_copy(short_digraph dst, short_digraph src)`` | Allocate ``dst`` so that it can contain as many vertices and edges as ``src``.
129
+ ``init_reverse(short_digraph dst, short_digraph src)`` | Initialize ``dst`` to a copy of ``src`` with all edges in the opposite direction.
130
+ ``free_short_digraph(short_digraph g)`` | Free the resources used by ``g``
131
+
132
+ **Connectivity**
133
+
134
+ ``can_be_reached_from(short_digraph g, int src, bitset_t reached)``
135
+
136
+ Assuming ``bitset_t reached`` has size at least ``g.n``, this method updates
137
+ ``reached`` so that it represents the set of vertices that can be reached
138
+ from ``src`` in ``g``.
139
+
140
+ ``strongly_connected_component_containing_vertex(short_digraph g, short_digraph g_reversed, int v, bitset_t scc)``
141
+
142
+ Assuming ``bitset_t reached`` has size at least ``g.n``, this method updates
143
+ ``scc`` so that it represents the vertices of the strongly connected
144
+ component containing ``v`` in ``g``. The variable ``g_reversed`` is assumed
145
+ to represent the reverse of ``g``.
146
+
147
+ ``tarjan_strongly_connected_components_C(short_digraph g, int *scc)``
148
+
149
+ Assuming ``scc`` is already allocated and has size at least ``g.n``, this
150
+ method computes the strongly connected components of ``g``, and outputs in
151
+ ``scc[v]`` the number of the strongly connected component containing ``v``.
152
+ It returns the number of strongly connected components.
153
+
154
+ ``strongly_connected_components_digraph_C(short_digraph g, int nscc, int *scc, short_digraph output):``
155
+
156
+ Assuming ``nscc`` and ``scc`` are the outputs of
157
+ ``tarjan_strongly_connected_components_C`` on ``g``, this routine
158
+ sets ``output`` to the
159
+ strongly connected component digraph of ``g``, that is, the vertices of
160
+ ``output`` are the strongly connected components of ``g`` (numbers are
161
+ provided by ``scc``), and ``output`` contains an arc ``(C1,C2)`` if ``g``
162
+ has an arc from a vertex in ``C1`` to a vertex in ``C2``.
163
+
164
+ What is this module used for ?
165
+ ------------------------------
166
+
167
+ It is for instance used in the :mod:`sage.graphs.distances_all_pairs` module,
168
+ and in the :meth:`~sage.graphs.digraph.DiGraph.strongly_connected_components`
169
+ method.
170
+
171
+ Python functions
172
+ ----------------
173
+
174
+ These functions are available so that Python modules from Sage can call the
175
+ Cython routines this module implements (as they cannot directly call methods
176
+ with C arguments).
177
+ """
178
+ # ****************************************************************************
179
+ # Copyright (C) 2010 Nathann Cohen <nathann.cohen@gmail.com>
180
+ #
181
+ # This program is free software: you can redistribute it and/or modify
182
+ # it under the terms of the GNU General Public License as published by
183
+ # the Free Software Foundation, either version 2 of the License, or
184
+ # (at your option) any later version.
185
+ # https://www.gnu.org/licenses/
186
+ # ****************************************************************************
187
+
188
+ cimport cpython
189
+ from libc.limits cimport INT_MAX
190
+ from libc.math cimport sqrt
191
+ from libcpp.vector cimport vector
192
+ from cysignals.memory cimport check_allocarray, check_calloc, sig_free
193
+ from cysignals.signals cimport sig_on, sig_off
194
+ from cython.operator cimport postincrement
195
+ from memory_allocator cimport MemoryAllocator
196
+
197
+ from sage.data_structures.bitset_base cimport *
198
+ from sage.graphs.base.static_sparse_backend cimport StaticSparseCGraph
199
+ from sage.graphs.base.static_sparse_backend cimport StaticSparseBackend
200
+
201
+
202
+ cdef extern from "fenv.h":
203
+ int FE_TONEAREST
204
+ int FE_UPWARD
205
+ int FE_DOWNWARD
206
+ int FE_TOWARDZERO
207
+ int fegetround ()
208
+ int fesetround (int)
209
+
210
+
211
+ cdef int init_short_digraph(short_digraph g, G, edge_labelled=False,
212
+ vertex_list=None) except -1:
213
+ r"""
214
+ Initialize ``short_digraph g`` from a Sage (Di)Graph.
215
+
216
+ INPUT:
217
+
218
+ - ``g`` -- a short_digraph
219
+
220
+ - ``G`` -- a ``Graph`` or a ``DiGraph``. If ``G`` is a ``Graph`` object,
221
+ then any edge between two vertices `u` and `v` is replaced by two arcs in
222
+ both directions.
223
+
224
+ - ``edge_labelled`` -- boolean (default: ``False``); whether to store the
225
+ label of edges or not
226
+
227
+ - ``vertex_list`` -- list (default: ``None``); list of all vertices of ``G``
228
+ in some order. When given, it is used to map the vertices of the graph to
229
+ consecutive integers. Otherwise, the result of ``list(G)`` is used
230
+ instead. Beware that if ``vertex_list`` is not ``None``, it is not checked
231
+ and this function assumes that it contains a permutation of the vertices
232
+ of the graph ``G``.
233
+
234
+ COMPLEXITY:
235
+
236
+ The time complexity for initializing ``g`` is `O(n + m)` for ``SparseGraph``
237
+ and `O(n^2)` for ``DenseGraph``.
238
+
239
+ TESTS:
240
+
241
+ Indirect doctests for sorted output::
242
+
243
+ sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend
244
+ sage: G = graphs.CompleteGraph(5).relabel(list('abcde'), inplace=False)
245
+ sage: B = StaticSparseBackend(G, sort=False)
246
+ sage: list(B.iterator_nbrs('a'))
247
+ ['b', 'c', 'd', 'e']
248
+ sage: G = graphs.CompleteGraph(5).relabel(list('badec'), inplace=False)
249
+ sage: B = StaticSparseBackend(G, sort=False)
250
+ sage: list(B.iterator_nbrs('a'))
251
+ ['b', 'd', 'e', 'c']
252
+
253
+ Same with labels::
254
+
255
+ sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend
256
+ sage: G = graphs.CompleteGraph(5).relabel(list('abcde'), inplace=False)
257
+ sage: for i, (u, v, _) in enumerate(G.edges()):
258
+ ....: G.set_edge_label(u, v, f'{u}{v}')
259
+ sage: B = StaticSparseBackend(G, sort=False)
260
+ sage: list(B.iterator_edges('a', True))
261
+ [('a', 'b', 'ab'), ('a', 'c', 'ac'), ('a', 'd', 'ad'), ('a', 'e', 'ae')]
262
+ sage: G = graphs.CompleteGraph(5).relabel(list('badec'), inplace=False)
263
+ sage: for i, (u, v, _) in enumerate(G.edges()):
264
+ ....: G.set_edge_label(u, v, f'{u}{v}')
265
+ sage: B = StaticSparseBackend(G, sort=False)
266
+ sage: list(B.iterator_edges('a', True))
267
+ [('a', 'b', 'ab'), ('a', 'd', 'ad'), ('a', 'e', 'ae'), ('a', 'c', 'ac')]
268
+ """
269
+ from sage.graphs.graph import Graph
270
+ from sage.graphs.digraph import DiGraph
271
+
272
+ if not isinstance(G, (Graph, DiGraph)):
273
+ raise ValueError("The source graph must be either a DiGraph or a Graph"
274
+ "object !")
275
+
276
+ if G.order() >= INT_MAX:
277
+ raise ValueError(f"short_digraph can handle at most {INT_MAX} vertices")
278
+
279
+ g.edge_labels = NULL
280
+ g.n = G.order()
281
+ g.m = G.size()
282
+
283
+ cdef int isdigraph = G.is_directed()
284
+ cdef uint32_t i, j
285
+ cdef list vertices = vertex_list if vertex_list is not None else list(G)
286
+ cdef dict v_to_id = {v: i for i, v in enumerate(vertices)}
287
+ cdef list edge_labels
288
+ # Loops are not stored twice for undirected graphs
289
+ cdef int n_edges = g.m if isdigraph else 2*g.m - G.number_of_loops()
290
+
291
+ g.edges = <uint32_t *>check_allocarray(n_edges, sizeof(uint32_t))
292
+ g.neighbors = <uint32_t **>check_allocarray(1 + g.n, sizeof(uint32_t *))
293
+
294
+ # Initializing the value of neighbors
295
+ g.neighbors[0] = g.edges
296
+
297
+ if not G.has_loops():
298
+ # Normal case
299
+ for i, v in enumerate(vertices):
300
+ g.neighbors[i+1] = g.neighbors[i] + <int>(G.out_degree(v) if isdigraph else G.degree(v))
301
+ else:
302
+ # In the presence of loops. For a funny reason, if a vertex v has a loop
303
+ # attached to it and no other incident edge, Sage declares that it has
304
+ # degree 2. This way, the sum of the degrees of the vertices is twice
305
+ # the number of edges, but then the degree of a vertex is not the number
306
+ # of its neighbors anymore. One should never try to think. It never ends
307
+ # well.
308
+ for i, v in enumerate(vertices):
309
+ g.neighbors[i+1] = g.neighbors[i] + <int> len(G.edges_incident(v))
310
+
311
+ if edge_labelled:
312
+ edge_labels = [None] * n_edges
313
+
314
+ # Note that neighbors[i] will be naturally sorted by increasing id,
315
+ # because the arrays will be built by appending vertices in the same
316
+ # order as they appear in ``vertices``
317
+ for i, v in enumerate(vertices):
318
+ if isdigraph:
319
+ edge_iterator = G.incoming_edge_iterator(v,
320
+ labels=edge_labelled)
321
+ else:
322
+ edge_iterator = G.edge_iterator(v, labels=edge_labelled,
323
+ sort_vertices=False)
324
+ for e in edge_iterator:
325
+ u = e[0] if v == e[1] else e[1]
326
+ j = v_to_id[u]
327
+ # Handle the edge u -> v of G (= the edge j -> i of g)
328
+ g.neighbors[j][0] = i
329
+ # Note: cannot use the dereference Cython operator here, do not
330
+ # known why but the following line does not compile
331
+ # dereference(g.neighbors[j]) = i
332
+ if edge_labelled:
333
+ edge_labels[g.neighbors[j] - g.edges] = e[2]
334
+ postincrement(g.neighbors[j]) # increment pointer to next item
335
+
336
+ # Reinitializing the value of neighbors
337
+ for i in range(g.n-1, 0, -1):
338
+ g.neighbors[i] = g.neighbors[i-1]
339
+ g.neighbors[0] = g.edges
340
+
341
+ if edge_labelled:
342
+ g.edge_labels = <PyObject *> <void *> edge_labels
343
+ cpython.Py_XINCREF(g.edge_labels)
344
+
345
+
346
+ cdef inline int n_edges(short_digraph g) noexcept:
347
+ """
348
+ Return the number of edges in ``g``.
349
+
350
+ The number of edges is nothing but a difference of pointers
351
+ """
352
+ return <int> (g.neighbors[g.n] - g.edges)
353
+
354
+
355
+ cdef inline int out_degree(short_digraph g, int i) noexcept:
356
+ """
357
+ Return the out-degree of vertex `i` in ``g``.
358
+
359
+ The out-degree is nothing but a difference of pointers
360
+ """
361
+ return <int> (g.neighbors[i + 1] - g.neighbors[i])
362
+
363
+
364
+ cdef int init_empty_copy(short_digraph dst, short_digraph src) except -1:
365
+ """
366
+ Allocate ``dst`` so that it can contain as many vertices and edges as
367
+ ``src``.
368
+ """
369
+ dst.n = src.n
370
+ dst.m = src.m
371
+ dst.edge_labels = NULL
372
+ cdef list edge_labels
373
+
374
+ dst.edges = <uint32_t *>check_allocarray(n_edges(src), sizeof(uint32_t))
375
+ dst.neighbors = <uint32_t **>check_allocarray(src.n + 1, sizeof(uint32_t *))
376
+
377
+ if src.edge_labels != NULL:
378
+ edge_labels = [None] * n_edges(src)
379
+ dst.edge_labels = <PyObject *> <void *> edge_labels
380
+ cpython.Py_XINCREF(dst.edge_labels)
381
+
382
+
383
+ cdef int init_reverse(short_digraph dst, short_digraph src) except -1:
384
+ """
385
+ Initialize ``dst`` to a copy of ``src`` with all edges in the opposite
386
+ direction.
387
+
388
+ TESTS:
389
+
390
+ Indirect doctests for sorted output::
391
+
392
+ sage: from sage.graphs.base.static_sparse_backend import StaticSparseBackend
393
+ sage: D = digraphs.Complete(5).relabel(list('abcde'), inplace=False)
394
+ sage: for i, (u, v, _) in enumerate(D.edges()):
395
+ ....: D.set_edge_label(u, v, f'{u}{v}')
396
+ sage: B = StaticSparseBackend(D, sort=False)
397
+ sage: list(B.iterator_in_edges('a', True))
398
+ [('b', 'a', 'ba'), ('c', 'a', 'ca'), ('d', 'a', 'da'), ('e', 'a', 'ea')]
399
+ sage: D = digraphs.Complete(5).relabel(list('badec'), inplace=False)
400
+ sage: for i, (u, v, _) in enumerate(D.edges()):
401
+ ....: D.set_edge_label(u, v, f'{u}{v}')
402
+ sage: B = StaticSparseBackend(D, sort=False)
403
+ sage: list(B.iterator_in_edges('a', True))
404
+ [('b', 'a', 'ba'), ('d', 'a', 'da'), ('e', 'a', 'ea'), ('c', 'a', 'ca')]
405
+ """
406
+ cdef int i, j, v
407
+ # Allocates memory for dst
408
+ init_empty_copy(dst, src)
409
+
410
+ # Avoiding a later segfault
411
+ if not dst.n:
412
+ return 0
413
+
414
+ # 1/3
415
+ #
416
+ # In a first pass, we count the in-degrees of each vertex and store it in a
417
+ # vector. With this information, we can initialize dst.neighbors to its
418
+ # correct value. The content of dst.edges is not touched at this level.
419
+ cdef int * in_degree = <int *>check_calloc(src.n, sizeof(int))
420
+
421
+ for i in range(n_edges(src)):
422
+ in_degree[src.edges[i]] += 1
423
+
424
+ # Updating dst.neighbors
425
+ dst.neighbors[0] = dst.edges
426
+ for i in range(1, src.n + 1):
427
+ dst.neighbors[i] = dst.neighbors[i - 1] + in_degree[i - 1]
428
+ sig_free(in_degree)
429
+
430
+ # 2/3
431
+ #
432
+ # Second pass : we list the edges again, and add them in dst.edges. Doing
433
+ # so, we will change the value of dst.neighbors, but that is not so bad as
434
+ # we can fix it afterwards.
435
+ # Note that neighbors[i] will be naturally sorted by increasing id,
436
+ # because the arrays will be built by appending vertices in the same
437
+ # order as they appear in ``vertices``
438
+ for i in range(0, src.n):
439
+ for j in range(out_degree(src, i)):
440
+ v = src.neighbors[i][j]
441
+ dst.neighbors[v][0] = i
442
+
443
+ if dst.edge_labels:
444
+ (<list> dst.edge_labels)[dst.neighbors[v] - dst.edges] = edge_label(src, src.neighbors[i] + j)
445
+
446
+ dst.neighbors[v] += 1
447
+
448
+ # 3/3
449
+ #
450
+ # Third step : set the correct values of dst.neighbors again. It is easy, as
451
+ # the correct value of dst.neighbors[i] is actually dst.neighbors[i-1]
452
+ for i in range(src.n - 1, 0, -1):
453
+ dst.neighbors[i] = dst.neighbors[i - 1]
454
+ dst.neighbors[0] = dst.edges
455
+
456
+ return 0
457
+
458
+
459
+ cdef int compare_uint32_p(const_void *a, const_void *b) noexcept:
460
+ """
461
+ Comparison function needed for ``bsearch``.
462
+ """
463
+ return (<uint32_t *> a)[0] - (<uint32_t *> b)[0]
464
+
465
+
466
+ cdef inline uint32_t * has_edge(short_digraph g, int u, int v) noexcept:
467
+ r"""
468
+ Test the existence of an edge.
469
+
470
+ Return a pointer to ``v`` in the list of neighbors of ``u`` if found and
471
+ ``NULL`` otherwise.
472
+
473
+ .. NOTE::
474
+
475
+ Use the fact that the array ``g.neighbors[u]`` is guaranteed to be sorted.
476
+ """
477
+ # The neighbors of u are sorted by increasing label. We can use binary
478
+ # search to decide if g has edge (u, v)
479
+ return <uint32_t *> bsearch(&v, g.neighbors[u],
480
+ g.neighbors[u+1] - g.neighbors[u],
481
+ sizeof(uint32_t), compare_uint32_p)
482
+
483
+
484
+ cdef inline object edge_label(short_digraph g, uint32_t * edge):
485
+ r"""
486
+ Return the label associated with a given edge
487
+ """
488
+ if not g.edge_labels:
489
+ return None
490
+ return (<list> g.edge_labels)[edge - g.edges]
491
+
492
+
493
+ cdef uint32_t simple_BFS(short_digraph g,
494
+ uint32_t source,
495
+ uint32_t *distances,
496
+ uint32_t *predecessors,
497
+ uint32_t *waiting_list,
498
+ bitset_t seen) noexcept:
499
+ """
500
+ Perform a breadth first search (BFS) using the same method as in
501
+ sage.graphs.distances_all_pairs.all_pairs_shortest_path_BFS
502
+
503
+ Furthermore, the method returns the eccentricity of the source which is
504
+ either the last computed distance when all vertices are seen, or a very
505
+ large number (UINT32_MAX) when the graph is not connected.
506
+
507
+ INPUT:
508
+
509
+ - ``g`` -- a short_digraph
510
+
511
+ - ``source`` -- starting node of the BFS
512
+
513
+ - ``distances`` -- array of size ``n`` to store BFS distances from
514
+ ``source``. This method assumes that this array has already been
515
+ allocated. However, there is no need to initialize it.
516
+
517
+ - ``predecessors`` -- array of size ``n`` to store the first predecessor of
518
+ each vertex during the BFS search from ``source``. The predecessor of the
519
+ ``source`` is itself. This method assumes that this array has already
520
+ been allocated. However, it is possible to pass a ``NULL`` pointer in
521
+ which case the predecessors are not recorded.
522
+
523
+ - ``waiting_list`` -- array of size ``n`` to store the order in which the
524
+ vertices are visited during the BFS search from ``source``. This method
525
+ assumes that this array has already been allocated. However, there is no
526
+ need to initialize it.
527
+
528
+ - ``seen`` -- bitset of size ``n`` that must be initialized before calling
529
+ this method (i.e., bitset_init(seen, n)). However, there is no need to
530
+ clear it.
531
+ """
532
+ cdef uint32_t v, u
533
+ cdef uint32_t waiting_beginning = 0
534
+ cdef uint32_t waiting_end = 0
535
+ cdef uint32_t * p_tmp
536
+ cdef uint32_t * end
537
+ cdef uint32_t n = g.n
538
+ cdef uint32_t ** p_vertices = g.neighbors
539
+
540
+ # the source is seen
541
+ bitset_clear(seen)
542
+ bitset_add(seen, source)
543
+ distances[source] = 0
544
+ if predecessors!=NULL:
545
+ predecessors[source] = source
546
+
547
+ # and added to the queue
548
+ waiting_list[0] = source
549
+ waiting_beginning = 0
550
+ waiting_end = 0
551
+
552
+ # For as long as there are vertices left to explore
553
+ while waiting_beginning <= waiting_end:
554
+
555
+ # We pick the first one
556
+ v = waiting_list[waiting_beginning]
557
+ p_tmp = p_vertices[v]
558
+ end = p_vertices[v + 1]
559
+
560
+ # and we iterate over all the outneighbors u of v
561
+ while p_tmp < end:
562
+ u = p_tmp[0]
563
+
564
+ # If we notice one of these neighbors is not seen yet, we set its
565
+ # parameters and add it to the queue to be explored later.
566
+ if not bitset_in(seen, u):
567
+ distances[u] = distances[v] + 1
568
+ bitset_add(seen, u)
569
+ waiting_end += 1
570
+ waiting_list[waiting_end] = u
571
+ if predecessors:
572
+ predecessors[u] = v
573
+
574
+ p_tmp += 1
575
+
576
+ waiting_beginning += 1
577
+
578
+ # We return the eccentricity of the source
579
+ return distances[waiting_list[waiting_end]] if waiting_end == n - 1 else UINT32_MAX
580
+
581
+
582
+ cdef int can_be_reached_from(short_digraph g, int src, bitset_t reached) except -1:
583
+ """
584
+ Feed ``reached`` with the vertices reachable from ``src``.
585
+ """
586
+ if not g.n:
587
+ return 0
588
+
589
+ # Initializing the set of vertices reached by setting only bit src
590
+ bitset_set_first_n(reached, 0)
591
+ bitset_add(reached, src)
592
+
593
+ # We will be doing a Depth-First Search. We allocate the stack we need for
594
+ # that, and put "src" on top of it.
595
+ cdef int * stack = <int *>check_allocarray(g.n, sizeof(int))
596
+
597
+ stack[0] = src
598
+ cdef int stack_size = 1
599
+
600
+ # What we need to iterate over the edges
601
+ cdef int i
602
+ cdef uint32_t * v
603
+ cdef uint32_t * end
604
+
605
+ # Plain old DFS ...
606
+ #
607
+ # If there is something left on the stack, we remove it consider each of its
608
+ # neighbors. If we find any which has not been reached yet, we set its
609
+ # corresponding bit in the reached bitset, and add it on top of the stack.
610
+
611
+ while stack_size:
612
+ stack_size -= 1
613
+ i = stack[stack_size]
614
+
615
+ v = g.neighbors[i]
616
+ end = g.neighbors[i + 1]
617
+
618
+ while v < end:
619
+ if not bitset_in(reached, v[0]):
620
+ bitset_add(reached, v[0])
621
+ stack[stack_size] = v[0]
622
+ stack_size += 1
623
+
624
+ v += 1
625
+
626
+ sig_free(stack)
627
+
628
+
629
+ cdef int tarjan_strongly_connected_components_C(short_digraph g, int *scc) noexcept:
630
+ r"""
631
+ The Tarjan algorithm to compute strongly connected components (SCCs).
632
+
633
+ This routine returns the number of SCCs `k` and, stores in ``scc[v]`` an
634
+ integer between `0` and `k-1`, corresponding to the SCC containing v. SCCs
635
+ are numbered in reverse topological order, that is, if `(v,w)` is an edge
636
+ in the graph, ``scc[v] <= scc[w]``.
637
+
638
+ The basic idea of the algorithm is this: a depth-first search (DFS) begins
639
+ from an arbitrary start node (and subsequent DFSes are
640
+ conducted on any nodes that have not yet been found). As usual with DFSes,
641
+ the search visits every node of the graph exactly once, declining to revisit
642
+ any node that has already been explored. Thus, the collection of search
643
+ trees is a spanning forest of the graph. The strongly connected components
644
+ are the subtrees of this spanning forest having no edge directed outside the
645
+ subtree.
646
+
647
+ To recover these components, during the DFS, we keep the index of a node,
648
+ that is, the position in the DFS tree, and the lowlink: as soon as the
649
+ subtree rooted at `v` has been fully explored, the lowlink of `v` is the
650
+ smallest index reachable from `v` passing from descendants of `v`. If the
651
+ subtree rooted at `v` has been fully explored, and the index of `v` equals
652
+ the lowlink of `v`, that whole subtree is a new SCC.
653
+ """
654
+ cdef MemoryAllocator mem = MemoryAllocator()
655
+ cdef int u, v, w, n = g.n, current_index = 0, currentscc = 0
656
+ cdef int *index = <int *> mem.malloc(n * sizeof(int))
657
+ cdef int *pred = <int *> mem.malloc(n * sizeof(int))
658
+ cdef int *lowlink = <int *> mem.malloc(n * sizeof(int))
659
+ cdef int *dfs_stack = <int *> mem.malloc((n_edges(g) + 1) * sizeof(int))
660
+ cdef int dfs_stack_end
661
+ cdef int *scc_stack = <int *> mem.malloc(n * sizeof(int)) # Used to keep track of which nodes are in the "current" SCC
662
+ cdef short *in_scc_stack = <short *> mem.calloc(n, sizeof(short))
663
+ cdef uint32_t *p_tmp
664
+ cdef short *visited = <short *> mem.calloc(n, sizeof(short))
665
+ # The variable visited[v] is 0 if the vertex has never been visited, 1 if
666
+ # it is an ancestor of the current vertex, 2 otherwise.
667
+
668
+ for u in range(n):
669
+ if visited[u]:
670
+ continue
671
+
672
+ # Perform a DFS from u
673
+ dfs_stack_end = 1
674
+ scc_stack_end = 0
675
+ dfs_stack[0] = u
676
+ pred[u] = u
677
+
678
+ while dfs_stack_end:
679
+ v = dfs_stack[dfs_stack_end - 1]
680
+ if not visited[v]:
681
+ # It means that this is the first time we visit v.
682
+ # We set the index and the lowlink to be equal: during the
683
+ # algorithm, the lowlink may decrease.
684
+ visited[v] = 1
685
+ index[v] = current_index
686
+ lowlink[v] = current_index
687
+ current_index = current_index + 1
688
+ # We add v to the stack of vertices in the current SCC
689
+ scc_stack[scc_stack_end] = v
690
+ scc_stack_end = scc_stack_end + 1
691
+ in_scc_stack[v] = 1
692
+
693
+ # We iterate over all neighbors of v
694
+ p_tmp = g.neighbors[v]
695
+ while p_tmp < g.neighbors[v + 1]:
696
+ w = p_tmp[0]
697
+ p_tmp += 1
698
+ if not visited[w]:
699
+ # Vertex w is added to the DFS stack
700
+ pred[w] = v
701
+ dfs_stack[dfs_stack_end] = w
702
+ dfs_stack_end += 1
703
+ elif in_scc_stack[w]:
704
+ # We update the lowlink of v (later, we will "pass"
705
+ # this updated value to all ancestors of v.
706
+ lowlink[v] = min(lowlink[v], lowlink[w])
707
+ else:
708
+ # The vertex v has already been visited.
709
+ dfs_stack_end -= 1
710
+
711
+ if visited[v] == 1:
712
+ # It means that we have just processed all the DFS
713
+ # subtree rooted at v. Hence, the lowlink of v is the
714
+ # final value, and we "pass" this value to the
715
+ # predecessor of v.
716
+ lowlink[pred[v]] = min(lowlink[pred[v]], lowlink[v])
717
+
718
+ if lowlink[v] == index[v]:
719
+ # The DFS subtree rooted at v is a new SCC. We
720
+ # recover the SCC from scc_stack.
721
+ w = -1
722
+ while w != v:
723
+ scc_stack_end -= 1
724
+ w = scc_stack[scc_stack_end]
725
+ in_scc_stack[w] = 0
726
+ scc[w] = currentscc
727
+ currentscc += 1
728
+ visited[v] = 2
729
+
730
+ return currentscc
731
+
732
+
733
+ def tarjan_strongly_connected_components(G):
734
+ r"""
735
+ Return the lists of vertices in each strongly connected components (SCCs).
736
+
737
+ This method implements the Tarjan algorithm to compute the strongly
738
+ connected components of the digraph. It returns a list of lists of vertices,
739
+ each list of vertices representing a strongly connected component.
740
+
741
+ The basic idea of the algorithm is this: a depth-first search (DFS) begins
742
+ from an arbitrary start node (and subsequent DFSes are
743
+ conducted on any nodes that have not yet been found). As usual with DFSes,
744
+ the search visits every node of the graph exactly once, declining to revisit
745
+ any node that has already been explored. Thus, the collection of search
746
+ trees is a spanning forest of the graph. The strongly connected components
747
+ correspond to the subtrees of this spanning forest that have no edge
748
+ directed outside the subtree.
749
+
750
+ To recover these components, during the DFS, we keep the index of a node,
751
+ that is, the position in the DFS tree, and the lowlink: as soon as the
752
+ subtree rooted at `v` has been fully explored, the lowlink of `v` is the
753
+ smallest index reachable from `v` passing from descendants of `v`. If the
754
+ subtree rooted at `v` has been fully explored, and the index of `v` equals
755
+ the lowlink of `v`, that whole subtree is a new SCC.
756
+
757
+ For more information, see the
758
+ :wikipedia:`Tarjan%27s_strongly_connected_components_algorithm`.
759
+
760
+ EXAMPLES::
761
+
762
+ sage: from sage.graphs.base.static_sparse_graph import tarjan_strongly_connected_components
763
+ sage: tarjan_strongly_connected_components(digraphs.Path(3))
764
+ [[2], [1], [0]]
765
+ sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
766
+ sage: D.connected_components(sort=True)
767
+ [[0, 1, 2, 3], [4, 5, 6]]
768
+ sage: D = DiGraph( { 0 : [1, 3], 1 : [2], 2 : [3], 4 : [5, 6], 5 : [6] } )
769
+ sage: D.strongly_connected_components()
770
+ [[3], [2], [1], [0], [6], [5], [4]]
771
+ sage: D.add_edge([2,0])
772
+ sage: D.strongly_connected_components()
773
+ [[3], [0, 1, 2], [6], [5], [4]]
774
+ sage: D = DiGraph([('a','b'), ('b','c'), ('c', 'd'), ('d', 'b'), ('c', 'e')])
775
+ sage: [sorted(scc) for scc in D.strongly_connected_components()]
776
+ [['e'], ['b', 'c', 'd'], ['a']]
777
+
778
+ TESTS:
779
+
780
+ Checking that the result is correct::
781
+
782
+ sage: from sage.graphs.base.static_sparse_graph import tarjan_strongly_connected_components
783
+ sage: import random
784
+ sage: for i in range(10): # long time
785
+ ....: n = random.randint(2,20)
786
+ ....: m = random.randint(1, n*(n-1))
787
+ ....: g = digraphs.RandomDirectedGNM(n,m)
788
+ ....: sccs = tarjan_strongly_connected_components(g)
789
+ ....: for scc in sccs:
790
+ ....: scc_check = g.strongly_connected_component_containing_vertex(scc[0])
791
+ ....: assert(sorted(scc) == sorted(scc_check))
792
+
793
+ Checking against NetworkX::
794
+
795
+ sage: import networkx # needs networkx
796
+ sage: for i in range(10): # long time # needs networkx
797
+ ....: g = digraphs.RandomDirectedGNP(100,.05)
798
+ ....: h = g.networkx_graph()
799
+ ....: scc1 = g.strongly_connected_components()
800
+ ....: scc2 = networkx.strongly_connected_components(h)
801
+ ....: s1 = Set(map(Set,scc1))
802
+ ....: s2 = Set(map(Set,scc2))
803
+ ....: if s1 != s2:
804
+ ....: print("Ooch !")
805
+
806
+ Immutable digraphs::
807
+
808
+ sage: from sage.graphs.base.static_sparse_graph import tarjan_strongly_connected_components
809
+ sage: G = digraphs.RandomDirectedGNP(10, .4)
810
+ sage: G._backend
811
+ <sage.graphs.base.sparse_graph.SparseGraphBackend ...>
812
+ sage: H = DiGraph(G, immutable=True)
813
+ sage: H._backend
814
+ <sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
815
+ sage: tarjan_strongly_connected_components(G) == tarjan_strongly_connected_components(H)
816
+ True
817
+ """
818
+ from sage.graphs.digraph import DiGraph
819
+
820
+ if not isinstance(G, DiGraph):
821
+ raise ValueError("G must be a DiGraph.")
822
+
823
+ cdef list int_to_vertex
824
+ cdef StaticSparseCGraph cg
825
+ cdef short_digraph g
826
+ if isinstance(G, StaticSparseBackend):
827
+ cg = <StaticSparseCGraph> G._cg
828
+ g = <short_digraph> cg.g
829
+ int_to_vertex = cg._vertex_to_labels
830
+ else:
831
+ int_to_vertex = list(G)
832
+ init_short_digraph(g, G, edge_labelled=False, vertex_list=int_to_vertex)
833
+
834
+ cdef MemoryAllocator mem = MemoryAllocator()
835
+ cdef int * scc = <int*> mem.malloc(g.n * sizeof(int))
836
+ sig_on()
837
+ cdef int nscc = tarjan_strongly_connected_components_C(g, scc)
838
+ sig_off()
839
+
840
+ if not isinstance(G, StaticSparseBackend):
841
+ free_short_digraph(g)
842
+
843
+ cdef int i
844
+ cdef list output = [[] for i in range(nscc)]
845
+
846
+ for i, v in enumerate(int_to_vertex):
847
+ output[scc[i]].append(v)
848
+ return output
849
+
850
+
851
+ cdef void strongly_connected_components_digraph_C(short_digraph g, int nscc, int *scc, short_digraph output) noexcept:
852
+ r"""
853
+ Compute the strongly connected components (SCCs) digraph of `g`.
854
+
855
+ The strongly connected components digraph of `g` is a graph having a vertex
856
+ for each SCC of `g` and an arc from component `C_1` to component `C_2` if
857
+ and only if there is an arc in `g` from a vertex in `C_1` to a vertex in
858
+ `C_2`. The strongly connected components digraph is acyclic by definition.
859
+
860
+ This routine inputs the graph ``g``, the number of SCCs ``nscc``, and an
861
+ array containing in position ``v`` the SCC of vertex ``v`` (these values
862
+ must be already computed). The output is stored in variable ``output``,
863
+ which should be empty at the beginning.
864
+ """
865
+ cdef MemoryAllocator mem = MemoryAllocator()
866
+ cdef size_t v, w, i
867
+ cdef size_t s_nscc = <size_t>nscc
868
+ cdef vector[vector[int]] scc_list = vector[vector[int]](nscc, vector[int]())
869
+ cdef vector[vector[int]] sons = vector[vector[int]](nscc + 1, vector[int]())
870
+ cdef short *neighbors = <short *> mem.calloc(nscc, sizeof(short))
871
+ cdef long m = 0
872
+ cdef uint32_t *p_tmp
873
+
874
+ for v in range(s_nscc):
875
+ scc_list[v] = vector[int]()
876
+ sons[v] = vector[int]()
877
+ sons[nscc] = vector[int]()
878
+
879
+ for i in range(<size_t>g.n):
880
+ scc_list[scc[i]].push_back(i)
881
+
882
+ for v in range(s_nscc):
883
+ for i in range(scc_list[v].size()):
884
+ p_tmp = g.neighbors[scc_list[v][i]]
885
+ while p_tmp<g.neighbors[scc_list[v][i] + 1]:
886
+ w = <int> scc[p_tmp[0]]
887
+ p_tmp += 1
888
+ if not (neighbors[w] or w == v):
889
+ neighbors[w] = 1
890
+ sons[v].push_back(w)
891
+ m += 1
892
+ for w in range(sons[v].size()):
893
+ neighbors[sons[v][w]] = 0
894
+
895
+ output.n = nscc
896
+ output.m = m
897
+ output.edge_labels = NULL
898
+
899
+ output.neighbors = <uint32_t **> check_allocarray((1+<int>output.n), sizeof(uint32_t *))
900
+
901
+ if not m:
902
+ output.edges = NULL
903
+ for v in range(1, s_nscc + 1):
904
+ output.neighbors[v] = NULL
905
+
906
+ output.edges = <uint32_t *> check_allocarray(m, sizeof(uint32_t))
907
+ output.neighbors[0] = output.edges
908
+
909
+ for v in range(1, s_nscc + 1):
910
+ output.neighbors[v] = output.neighbors[v - 1] + sons[v - 1].size()
911
+ for i in range(sons[v].size()):
912
+ output.neighbors[v][i] = sons[v][i]
913
+
914
+
915
+ def strongly_connected_components_digraph(G):
916
+ r"""
917
+ Return the digraph of the strongly connected components (SCCs).
918
+
919
+ This routine is used to test ``strongly_connected_components_digraph_C``,
920
+ but it is not used by the Sage digraph. It outputs a pair ``[g_scc,scc]``,
921
+ where ``g_scc`` is the SCC digraph of g, ``scc`` is a dictionary associating
922
+ to each vertex ``v`` the number of the SCC of ``v``, as it appears in
923
+ ``g_scc``.
924
+
925
+ EXAMPLES::
926
+
927
+ sage: from sage.graphs.base.static_sparse_graph import strongly_connected_components_digraph
928
+ sage: strongly_connected_components_digraph(digraphs.Path(3))
929
+ (Digraph on 3 vertices, {0: 2, 1: 1, 2: 0})
930
+ sage: strongly_connected_components_digraph(DiGraph(4))
931
+ (Digraph on 4 vertices, {0: 0, 1: 1, 2: 2, 3: 3})
932
+
933
+ TESTS::
934
+
935
+ sage: from sage.graphs.base.static_sparse_graph import strongly_connected_components_digraph
936
+ sage: import random
937
+ sage: for i in range(100):
938
+ ....: n = random.randint(2,20)
939
+ ....: m = random.randint(1, n*(n-1))
940
+ ....: g = digraphs.RandomDirectedGNM(n,m)
941
+ ....: scc_digraph,sccs = strongly_connected_components_digraph(g)
942
+ ....: assert(scc_digraph.is_directed_acyclic())
943
+ ....: for e in g.edges(sort=False):
944
+ ....: assert(sccs[e[0]]==sccs[e[1]] or scc_digraph.has_edge(sccs[e[0]],sccs[e[1]]))
945
+ ....: assert(sccs[e[0]] >= sccs[e[1]])
946
+
947
+ Immutable digraphs::
948
+
949
+ sage: from sage.graphs.base.static_sparse_graph import strongly_connected_components_digraph
950
+ sage: G = digraphs.RandomDirectedGNP(10, .4)
951
+ sage: G._backend
952
+ <sage.graphs.base.sparse_graph.SparseGraphBackend ...>
953
+ sage: H = DiGraph(G, immutable=True)
954
+ sage: H._backend
955
+ <sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
956
+ sage: A = strongly_connected_components_digraph(G)[0]
957
+ sage: B = strongly_connected_components_digraph(H)[0]
958
+ sage: A.is_isomorphic(B)
959
+ True
960
+ """
961
+ from sage.graphs.digraph import DiGraph
962
+ if not isinstance(G, DiGraph):
963
+ raise ValueError("G must be a DiGraph.")
964
+
965
+ cdef list int_to_vertex
966
+ cdef StaticSparseCGraph cg
967
+ cdef short_digraph g, scc_g
968
+ if isinstance(G, StaticSparseBackend):
969
+ cg = <StaticSparseCGraph> G._cg
970
+ g = <short_digraph> cg.g
971
+ int_to_vertex = cg._vertex_to_labels
972
+ else:
973
+ int_to_vertex = list(G)
974
+ init_short_digraph(g, G, edge_labelled=False, vertex_list=int_to_vertex)
975
+
976
+ cdef MemoryAllocator mem = MemoryAllocator()
977
+ cdef int * scc = <int*> mem.malloc(g.n * sizeof(int))
978
+ cdef int i, j, nscc
979
+ cdef list edges = []
980
+
981
+ sig_on()
982
+ nscc = tarjan_strongly_connected_components_C(g, scc)
983
+ strongly_connected_components_digraph_C(g, nscc, scc, scc_g)
984
+
985
+ output = DiGraph(nscc)
986
+
987
+ for i in range(scc_g.n):
988
+ for j in range(scc_g.neighbors[i + 1] - scc_g.neighbors[i]):
989
+ edges.append((i, scc_g.neighbors[i][j]))
990
+ output.add_edges(edges)
991
+ sig_off()
992
+
993
+ if not isinstance(G, StaticSparseBackend):
994
+ free_short_digraph(g)
995
+ free_short_digraph(scc_g)
996
+
997
+ return output, {v: scc[i] for i, v in enumerate(int_to_vertex)}
998
+
999
+
1000
+ cdef strongly_connected_component_containing_vertex(short_digraph g, short_digraph g_reversed, int v, bitset_t scc):
1001
+ """
1002
+ Feed ``scc`` with the vertices in the strongly connected component of ``v``.
1003
+ """
1004
+ # Computing the set of vertices that can be reached from v in g
1005
+ can_be_reached_from(g, v, scc)
1006
+ # Computing the set of vertices that can be reached from v in g *reversed*
1007
+ cdef bitset_t scc_reversed
1008
+ bitset_init(scc_reversed, g.n)
1009
+ can_be_reached_from(g_reversed, v, scc_reversed)
1010
+ # The scc containing v is the intersection of both sets
1011
+ bitset_intersection(scc, scc, scc_reversed)
1012
+
1013
+
1014
+ cdef void free_short_digraph(short_digraph g) noexcept:
1015
+ """
1016
+ Free the resources used by ``g``
1017
+ """
1018
+ sig_free(g.edges)
1019
+ sig_free(g.neighbors)
1020
+ if g.edge_labels != NULL:
1021
+ cpython.Py_XDECREF(g.edge_labels)
1022
+
1023
+
1024
+ def triangles_count(G):
1025
+ r"""
1026
+ Return the number of triangles containing `v`, for every `v`.
1027
+
1028
+ INPUT:
1029
+
1030
+ - ``G`` -- a graph
1031
+
1032
+ EXAMPLES::
1033
+
1034
+ sage: from sage.graphs.base.static_sparse_graph import triangles_count
1035
+ sage: triangles_count(graphs.PetersenGraph())
1036
+ {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0}
1037
+ sage: sum(triangles_count(graphs.CompleteGraph(15)).values()) == 3*binomial(15,3) # needs sage.symbolic
1038
+ True
1039
+
1040
+ TESTS:
1041
+
1042
+ Immutable graphs::
1043
+
1044
+ sage: from sage.graphs.base.static_sparse_graph import triangles_count
1045
+ sage: G = graphs.RandomGNP(10, .7)
1046
+ sage: G._backend
1047
+ <sage.graphs.base.sparse_graph.SparseGraphBackend ...>
1048
+ sage: H = Graph(G, immutable=True)
1049
+ sage: H._backend
1050
+ <sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
1051
+ sage: triangles_count(G) == triangles_count(H)
1052
+ True
1053
+ """
1054
+ from sage.rings.integer import Integer
1055
+ G._scream_if_not_simple()
1056
+
1057
+ # g is a copy of G. If G is internally a static sparse graph, we use it.
1058
+ cdef list int_to_vertex
1059
+ cdef StaticSparseCGraph cg
1060
+ cdef short_digraph g
1061
+ if isinstance(G, StaticSparseBackend):
1062
+ cg = <StaticSparseCGraph> G._cg
1063
+ g = <short_digraph> cg.g
1064
+ int_to_vertex = cg._vertex_to_labels
1065
+ else:
1066
+ int_to_vertex = list(G)
1067
+ init_short_digraph(g, G, edge_labelled=False, vertex_list=int_to_vertex)
1068
+
1069
+ cdef uint64_t * count = <uint64_t *> check_calloc(G.order(), sizeof(uint64_t))
1070
+
1071
+ cdef uint64_t tmp_count = 0
1072
+ cdef uint32_t u, v, i
1073
+ cdef uint32_t * p1
1074
+ cdef uint32_t * p2
1075
+
1076
+ for u in range(<uint32_t>g.n):
1077
+ for i in range(<uint32_t>out_degree(g, u)):
1078
+ v = g.neighbors[u][i]
1079
+ if v <= u:
1080
+ continue
1081
+
1082
+ # Size of [N(u) inter N(v)]. Both are sorted lists.
1083
+ p1 = g.neighbors[u]
1084
+ p2 = g.neighbors[v]
1085
+ tmp_count = 0
1086
+ while (p1 < g.neighbors[u + 1] and p2 < g.neighbors[v + 1]):
1087
+ if p1[0] == p2[0]:
1088
+ tmp_count += 1
1089
+ p1 += 1
1090
+ p2 += 1
1091
+ elif p1[0] < p2[0]:
1092
+ p1 += 1
1093
+ else:
1094
+ p2 += 1
1095
+
1096
+ count[u] += tmp_count
1097
+ count[v] += tmp_count
1098
+
1099
+ ans = {w: Integer(count[i] // 2) for i, w in enumerate(int_to_vertex)}
1100
+
1101
+ if not isinstance(G, StaticSparseBackend):
1102
+ free_short_digraph(g)
1103
+ sig_free(count)
1104
+ return ans
1105
+
1106
+
1107
+ def spectral_radius(G, prec=1e-10):
1108
+ r"""
1109
+ Return an interval of floating point number that encloses the spectral
1110
+ radius of this graph
1111
+
1112
+ The input graph ``G`` must be *strongly connected*.
1113
+
1114
+ INPUT:
1115
+
1116
+ - ``prec`` -- (default: ``1e-10``) an upper bound for the relative precision
1117
+ of the interval
1118
+
1119
+ The algorithm is iterative and uses an inequality valid for nonnegative
1120
+ matrices. Namely, if `A` is a nonnegative square matrix with
1121
+ Perron-Frobenius eigenvalue `\lambda` then the following inequality is valid
1122
+ for any vector `x`
1123
+
1124
+ .. MATH::
1125
+
1126
+ \min_i \frac{(Ax)_i}{x_i} \leq \lambda \leq \max_i \frac{(Ax)_i}{x_i}
1127
+
1128
+ .. NOTE::
1129
+
1130
+ The speed of convergence of the algorithm is governed by the spectral
1131
+ gap (the distance to the second largest modulus of other eigenvalues).
1132
+ If this gap is small, then this function might not be appropriate.
1133
+
1134
+ The algorithm is not smart and not parallel! It uses basic interval
1135
+ arithmetic and native floating point arithmetic.
1136
+
1137
+ EXAMPLES::
1138
+
1139
+ sage: from sage.graphs.base.static_sparse_graph import spectral_radius
1140
+
1141
+ sage: G = DiGraph([(0,0),(0,1),(1,0)], loops=True)
1142
+ sage: phi = (RR(1) + RR(5).sqrt() ) / 2
1143
+ sage: phi # abs tol 1e-14
1144
+ 1.618033988749895
1145
+ sage: e_min, e_max = spectral_radius(G, 1e-14)
1146
+ sage: e_min, e_max # abs tol 1e-14
1147
+ (1.618033988749894, 1.618033988749896)
1148
+ sage: (e_max - e_min) # abs tol 1e-14
1149
+ 1e-14
1150
+ sage: e_min < phi < e_max
1151
+ True
1152
+
1153
+ This function also works for graphs::
1154
+
1155
+ sage: G = Graph([(0,1),(0,2),(1,2),(1,3),(2,4),(3,4)])
1156
+ sage: e_min, e_max = spectral_radius(G, 1e-14)
1157
+ sage: e = max(G.adjacency_matrix().charpoly().roots(AA, multiplicities=False)) # needs sage.modules sage.rings.number_field
1158
+ sage: e_min < e < e_max # needs sage.modules sage.rings.number_field sage.symbolic
1159
+ True
1160
+
1161
+ sage: G.spectral_radius() # abs tol 1e-9
1162
+ (2.48119430408, 2.4811943041)
1163
+
1164
+ A larger example::
1165
+
1166
+ sage: # needs sage.modules
1167
+ sage: G = DiGraph()
1168
+ sage: G.add_edges((i,i+1) for i in range(200))
1169
+ sage: G.add_edge(200,0)
1170
+ sage: G.add_edge(1,0)
1171
+ sage: e_min, e_max = spectral_radius(G, 0.00001)
1172
+ sage: p = G.adjacency_matrix(sparse=True).charpoly()
1173
+ sage: p
1174
+ x^201 - x^199 - 1
1175
+ sage: r = p.roots(AA, multiplicities=False)[0] # needs sage.rings.number_field
1176
+ sage: e_min < r < e_max # needs sage.rings.number_field
1177
+ True
1178
+
1179
+ A much larger example::
1180
+
1181
+ sage: G = DiGraph(100000)
1182
+ sage: r = list(range(100000))
1183
+ sage: while not G.is_strongly_connected():
1184
+ ....: shuffle(r)
1185
+ ....: G.add_edges(enumerate(r), loops=False)
1186
+ sage: spectral_radius(G, 1e-10) # random # long time
1187
+ (1.9997956006500042, 1.9998043797692782)
1188
+
1189
+ The algorithm takes care of multiple edges::
1190
+
1191
+ sage: G = DiGraph(2,loops=True,multiedges=True)
1192
+ sage: G.add_edges([(0,0),(0,0),(0,1),(1,0)])
1193
+ sage: spectral_radius(G, 1e-14) # abs tol 1e-14
1194
+ (2.414213562373094, 2.414213562373095)
1195
+ sage: max(G.adjacency_matrix().eigenvalues(AA)) # needs sage.modules sage.rings.number_field
1196
+ 2.414213562373095?
1197
+
1198
+ Some bipartite graphs::
1199
+
1200
+ sage: G = Graph([(0,1),(0,3),(2,3)])
1201
+ sage: G.spectral_radius() # abs tol 1e-10
1202
+ (1.6180339887253428, 1.6180339887592732)
1203
+
1204
+ sage: G = DiGraph([(0,1),(0,3),(2,3),(3,0),(1,0),(1,2)])
1205
+ sage: G.spectral_radius() # abs tol 1e-10
1206
+ (1.5537739740270458, 1.553773974033029)
1207
+
1208
+ sage: G = graphs.CompleteBipartiteGraph(1,3)
1209
+ sage: G.spectral_radius() # abs tol 1e-10
1210
+ (1.7320508075688772, 1.7320508075688774)
1211
+
1212
+ TESTS::
1213
+
1214
+ sage: from sage.graphs.base.static_sparse_graph import spectral_radius
1215
+
1216
+ sage: Graph(1).spectral_radius()
1217
+ (0.0, 0.0)
1218
+ sage: Graph([(0,0)], loops=True).spectral_radius()
1219
+ (1.0, 1.0)
1220
+
1221
+ sage: spectral_radius(Graph([(0,1),(0,2)]), 1e-20)
1222
+ Traceback (most recent call last):
1223
+ ...
1224
+ ValueError: precision (=1.00000000000000e-20) is too small
1225
+
1226
+ sage: for _ in range(100): # needs sage.modules sage.rings.number_field
1227
+ ....: G = digraphs.RandomDirectedGNM(10,35)
1228
+ ....: if not G.is_strongly_connected():
1229
+ ....: continue
1230
+ ....: e = max(G.adjacency_matrix().charpoly().roots(AA,multiplicities=False))
1231
+ ....: e_min, e_max = G.spectral_radius(1e-13)
1232
+ ....: assert e_min < e < e_max
1233
+
1234
+ sage: spectral_radius(Graph(), 1e-10)
1235
+ Traceback (most recent call last):
1236
+ ...
1237
+ ValueError: empty graph
1238
+
1239
+ sage: G = DiGraph([(0,1),(1,2),(2,0),(2,0)], multiedges=True)
1240
+ sage: G.spectral_radius()
1241
+ Traceback (most recent call last):
1242
+ ...
1243
+ ValueError: the graph must be aperiodic
1244
+ """
1245
+ if not G:
1246
+ raise ValueError("empty graph")
1247
+ if G.is_directed():
1248
+ if not G.is_strongly_connected():
1249
+ raise ValueError("G must be strongly connected")
1250
+ elif not G.is_connected():
1251
+ raise ValueError("G must be connected")
1252
+
1253
+ cdef double e_min, e_max
1254
+
1255
+ if G.num_verts() == 1:
1256
+ e_min = e_max = G.num_edges()
1257
+ return (e_min, e_max)
1258
+
1259
+ is_bipartite, colors = G.is_bipartite(certificate=True)
1260
+ if is_bipartite:
1261
+ # NOTE: for bipartite graph there are two eigenvalues of maximum modulus
1262
+ # and the iteration is likely to reach a cycle of length 2 and hence the
1263
+ # algorithm never terminate. Here we compute the "square" reduced to
1264
+ # one component of the bipartition.
1265
+ from sage.graphs.digraph import DiGraph
1266
+ H = DiGraph(loops=True, multiedges=True)
1267
+ if G.is_directed():
1268
+ neighbors_iterator = G.neighbor_out_iterator
1269
+ else:
1270
+ neighbors_iterator = G.neighbor_iterator
1271
+ for u0 in G:
1272
+ if colors[u0] == 0:
1273
+ for u1 in neighbors_iterator(u0):
1274
+ for u2 in neighbors_iterator(u1):
1275
+ H.add_edge(u0, u2)
1276
+
1277
+ e_min, e_max = spectral_radius(H, prec)
1278
+ return sqrt(e_min), sqrt(e_max)
1279
+
1280
+ cdef double c_prec = prec
1281
+ if 1+c_prec/2 == 1:
1282
+ raise ValueError("precision (={!r}) is too small".format(prec))
1283
+
1284
+ # test if the graph is aperiodic
1285
+ from sage.graphs.digraph import DiGraph
1286
+ if not DiGraph(G).is_aperiodic():
1287
+ raise ValueError("the graph must be aperiodic")
1288
+
1289
+ # make a copy of G if needed to obtain a static sparse graph
1290
+ # NOTE: the following potentially copies the labels of the graph which is
1291
+ # completely useless for the computation!
1292
+ cdef short_digraph g
1293
+ G = G.copy(immutable=True)
1294
+ g[0] = (<StaticSparseCGraph> (<StaticSparseBackend> G._backend)._cg).g[0]
1295
+
1296
+ cdef size_t n = <size_t>g.n
1297
+ cdef size_t m = <size_t>g.m
1298
+ cdef uint32_t ** neighbors = g.neighbors
1299
+
1300
+ # v1 and v2 are two arrays of length n, allocated as one array
1301
+ # of length 2n for efficiency.
1302
+ cdef double * vmem = <double *>check_allocarray(2*n, sizeof(double))
1303
+ cdef double * v1 = vmem
1304
+ cdef double * v2 = vmem + n
1305
+ cdef double * v3
1306
+
1307
+ cdef size_t i
1308
+ cdef uint32_t *p
1309
+ cdef double s
1310
+
1311
+ for i in range(n):
1312
+ v1[i] = 1
1313
+ s = n
1314
+
1315
+ cdef int old_rounding = fegetround()
1316
+ e_max = m
1317
+ e_min = 0
1318
+ try:
1319
+ sig_on()
1320
+ while (e_max - e_min) > e_max * c_prec:
1321
+ # renormalize
1322
+ s = n/s
1323
+ for i in range(n):
1324
+ v1[i] *= s
1325
+
1326
+ # computing e_max (with upward rounding)
1327
+ e_max = 0
1328
+ fesetround(FE_UPWARD)
1329
+ p = neighbors[0]
1330
+ for i in range(n):
1331
+ v2[i] = 0
1332
+ while p < neighbors[i + 1]:
1333
+ v2[i] += v1[p[0]]
1334
+ p += 1
1335
+ e = v2[i] / v1[i]
1336
+ if e > e_max:
1337
+ e_max = e
1338
+
1339
+ # computing e_min (with downward rounding)
1340
+ e_min = m
1341
+ fesetround(FE_DOWNWARD)
1342
+ p = neighbors[0]
1343
+ for i in range(n):
1344
+ v2[i] = 0
1345
+ while p < neighbors[i + 1]:
1346
+ v2[i] += v1[p[0]]
1347
+ p += 1
1348
+ s += v2[i]
1349
+ e = v2[i] / v1[i]
1350
+ if e < e_min:
1351
+ e_min = e
1352
+
1353
+ # computing the next vector (with nearest rounding)
1354
+ fesetround(FE_TONEAREST)
1355
+ s = 0
1356
+ p = neighbors[0]
1357
+ for i in range(n):
1358
+ v2[i] = 0
1359
+ while p < neighbors[i + 1]:
1360
+ v2[i] += v1[p[0]]
1361
+ p += 1
1362
+ s += v2[i]
1363
+ v3 = v1
1364
+ v1 = v2
1365
+ v2 = v3
1366
+
1367
+ sig_off()
1368
+ finally:
1369
+ # be sure that the rounding is back to default
1370
+ fesetround(old_rounding)
1371
+
1372
+ # and that the memory is freed
1373
+ sig_free(vmem)
1374
+
1375
+ return (e_min, e_max)