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,3045 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ r"""
3
+ Interface to run Boost algorithms
4
+
5
+ Wrapper for a Boost graph. The Boost graphs are Cython C++ variables, and they
6
+ cannot be converted to Python objects: as a consequence, only functions defined
7
+ with cdef are able to create, read, modify, and delete these graphs.
8
+
9
+ A very important feature of Boost graph library is that all object are generic:
10
+ for instance, adjacency lists can be stored using different data structures,
11
+ and (most of) the functions work with all implementations provided. This feature
12
+ is implemented in our interface using fused types: however, Cython's support for
13
+ fused types is still experimental, and some features are missing. For instance,
14
+ there cannot be nested generic function calls, and no variable can have a
15
+ generic type, apart from the arguments of a generic function.
16
+
17
+ All the input functions use pointers, because otherwise we might have problems
18
+ with ``delete()``.
19
+
20
+ **Basic Boost Graph operations:**
21
+
22
+ .. csv-table::
23
+ :class: contentstable
24
+ :widths: 30, 70
25
+ :delim: |
26
+
27
+ :func:`clustering_coeff` | Return the clustering coefficient of all vertices in the graph.
28
+ :func:`edge_connectivity` | Return the edge connectivity of the graph.
29
+ :func:`dominator_tree` | Return a dominator tree of the graph.
30
+ :func:`bandwidth_heuristics` | Use heuristics to approximate the bandwidth of the graph.
31
+ :func:`min_spanning_tree` | Compute a minimum spanning tree of a (weighted) graph.
32
+ :func:`shortest_paths` | Use Dijkstra or Bellman-Ford algorithm to compute the single-source shortest paths.
33
+ :func:`johnson_shortest_paths` | Use Johnson algorithm to compute the all-pairs shortest paths.
34
+ :func:`floyd_warshall_shortest_paths` | Use Floyd-Warshall algorithm to compute the all-pairs shortest paths.
35
+ :func:`johnson_closeness_centrality` | Use Johnson algorithm to compute the closeness centrality of all vertices.
36
+ :func:`blocks_and_cut_vertices` | Use Tarjan's algorithm to compute the blocks and cut vertices of the graph.
37
+ :func:`min_cycle_basis` | Return a minimum weight cycle basis of the input graph.
38
+
39
+ Functions
40
+ ---------
41
+ """
42
+
43
+ # ****************************************************************************
44
+ # Copyright (C) 2015 Michele Borassi michele.borassi@imtlucca.it
45
+ #
46
+ # This program is free software: you can redistribute it and/or modify
47
+ # it under the terms of the GNU General Public License as published by
48
+ # the Free Software Foundation, either version 2 of the License, or
49
+ # (at your option) any later version.
50
+ # http://www.gnu.org/licenses/
51
+ # ****************************************************************************
52
+
53
+ cimport cython
54
+ from cysignals.signals cimport sig_check, sig_on, sig_off
55
+ from libcpp.set cimport set as cset
56
+ from libcpp.pair cimport pair
57
+
58
+
59
+ cdef boost_graph_from_sage_graph(BoostGenGraph *g, g_sage, vertex_to_int, reverse=False):
60
+ r"""
61
+ Initialize the Boost graph ``g`` to be equal to ``g_sage``.
62
+
63
+ INPUT:
64
+
65
+ - ``g`` -- a Boost graph; it must represent an empty graph (an exception is
66
+ raised otherwise)
67
+
68
+ - ``g_sage`` -- a Sage graph
69
+
70
+ - ``vertex_to_int`` -- dictionary; it is a mapping from the vertex set of
71
+ ``g_sage`` to `(0, \ldots, n-1)`
72
+
73
+ - ``reverse`` -- boolean (default: ``False``); when set to ``True``, the
74
+ Boost graph is initialized with reversed edges
75
+ """
76
+ from sage.graphs.generic_graph import GenericGraph
77
+
78
+ if not isinstance(g_sage, GenericGraph):
79
+ raise TypeError("the input must be a Sage graph")
80
+
81
+ if g.num_verts():
82
+ raise AssertionError("the given Boost graph must be empty")
83
+
84
+ cdef int N = g_sage.num_verts()
85
+ cdef int i
86
+
87
+ for i in range(N):
88
+ g.add_vertex()
89
+
90
+ if reverse:
91
+ for u, v in g_sage.edge_iterator(labels=None):
92
+ g.add_edge(vertex_to_int[v], vertex_to_int[u])
93
+ else:
94
+ for u, v in g_sage.edge_iterator(labels=None):
95
+ g.add_edge(vertex_to_int[u], vertex_to_int[v])
96
+
97
+
98
+ cdef boost_weighted_graph_from_sage_graph(BoostWeightedGraph *g,
99
+ g_sage,
100
+ vertex_to_int,
101
+ weight_function=None,
102
+ reverse=False):
103
+ r"""
104
+ Initialize the Boost weighted graph ``g`` to be equal to ``g_sage``.
105
+
106
+ INPUT:
107
+
108
+ - ``g`` -- a Boost weighted graph; it must represent an empty weighted graph
109
+ (an exception is raised otherwise)
110
+
111
+ - ``g_sage`` -- a Sage graph
112
+
113
+ - ``vertex_to_int`` -- dictionary; it is a mapping from the vertex set of
114
+ ``g_sage`` to `(0, \ldots, n-1)`
115
+
116
+ - ``weight_function`` -- function (default: ``None``); a function which
117
+ inputs an edge ``e`` and outputs a number. The edge weights are chosen as
118
+ follows, and they must be convertible to floats, otherwise an error is
119
+ raised.
120
+
121
+ - If ``weight_function`` is not ``None``, this function is used
122
+
123
+ - If ``weight_function`` is ``None`` and ``g`` is weighted, the edge
124
+ labels of ``g`` are used; in other words, the weight of an edge
125
+ ``e = (u, v, l)`` is ``l``
126
+
127
+ - Otherwise, all weights are set to 1
128
+
129
+ - ``reverse`` -- boolean (default: ``False``); when set to ``True``, the
130
+ Boost graph is initialized with reversed edges
131
+ """
132
+ from sage.graphs.generic_graph import GenericGraph
133
+
134
+ if not isinstance(g_sage, GenericGraph):
135
+ raise TypeError("the input must be a Sage graph")
136
+
137
+ if g.num_verts():
138
+ raise AssertionError("the given Boost graph must be empty")
139
+
140
+ cdef int N = g_sage.num_verts()
141
+ cdef int i
142
+
143
+ for i in range(N):
144
+ g.add_vertex()
145
+
146
+ if weight_function is not None:
147
+ if reverse:
148
+ for e in g_sage.edge_iterator():
149
+ g.add_edge(vertex_to_int[e[1]],
150
+ vertex_to_int[e[0]],
151
+ float(weight_function(e)))
152
+ else:
153
+ for e in g_sage.edge_iterator():
154
+ g.add_edge(vertex_to_int[e[0]],
155
+ vertex_to_int[e[1]],
156
+ float(weight_function(e)))
157
+ elif g_sage.weighted():
158
+ if reverse:
159
+ for u, v, w in g_sage.edge_iterator():
160
+ g.add_edge(vertex_to_int[v], vertex_to_int[u], float(w))
161
+ else:
162
+ for u, v, w in g_sage.edge_iterator():
163
+ g.add_edge(vertex_to_int[u], vertex_to_int[v], float(w))
164
+ else:
165
+ if reverse:
166
+ for u, v in g_sage.edge_iterator(labels=False):
167
+ g.add_edge(vertex_to_int[v], vertex_to_int[u], 1)
168
+ else:
169
+ for u, v in g_sage.edge_iterator(labels=False):
170
+ g.add_edge(vertex_to_int[u], vertex_to_int[v], 1)
171
+
172
+
173
+ cdef boost_edge_connectivity(BoostVecGenGraph *g):
174
+ r"""
175
+ Compute the edge connectivity of the input Boost graph.
176
+
177
+ The output is a pair ``[ec,edges]``, where ``ec`` is the edge connectivity,
178
+ ``edges`` is the list of edges in a minimum cut.
179
+ """
180
+ cdef result_ec result
181
+
182
+ sig_on()
183
+ result = g[0].edge_connectivity()
184
+ sig_off()
185
+
186
+ cdef size_t i
187
+ edges = [(result.edges[i], result.edges[i+1])
188
+ for i in range(0, result.edges.size(), 2)]
189
+
190
+ return (result.ec, edges)
191
+
192
+
193
+ cpdef edge_connectivity(g):
194
+ r"""
195
+ Compute the edge connectivity of the input graph, using Boost.
196
+
197
+ OUTPUT: a pair ``(ec, edges)``, where ``ec`` is the edge
198
+ connectivity, ``edges`` is the list of edges in a minimum cut.
199
+
200
+ .. SEEALSO::
201
+
202
+ :meth:`sage.graphs.generic_graph.GenericGraph.edge_connectivity`
203
+
204
+ EXAMPLES:
205
+
206
+ Computing the edge connectivity of a clique::
207
+
208
+ sage: from sage.graphs.base.boost_graph import edge_connectivity
209
+ sage: g = graphs.CompleteGraph(5)
210
+ sage: edge_connectivity(g)
211
+ (4, [(0, 1), (0, 2), (0, 3), (0, 4)])
212
+
213
+ Vertex-labeled graphs::
214
+
215
+ sage: from sage.graphs.base.boost_graph import edge_connectivity
216
+ sage: g = graphs.GridGraph([2,2])
217
+ sage: edge_connectivity(g)
218
+ (2, [((0, 0), (0, 1)), ((0, 0), (1, 0))])
219
+ """
220
+ from sage.graphs.graph import Graph
221
+ from sage.graphs.digraph import DiGraph
222
+
223
+ # These variables are automatically deleted when the function terminates.
224
+ cdef BoostVecGraph g_boost_und
225
+ cdef BoostVecDiGraph g_boost_dir
226
+ cdef v_index i
227
+ cdef list int_to_vertex = list(g)
228
+ cdef dict vertex_to_int = {v: i for i, v in enumerate(int_to_vertex)}
229
+
230
+ if isinstance(g, Graph):
231
+ boost_graph_from_sage_graph(&g_boost_und, g, vertex_to_int)
232
+ ec, edges = boost_edge_connectivity(&g_boost_und)
233
+
234
+ elif isinstance(g, DiGraph):
235
+ from sage.misc.stopgap import stopgap
236
+ stopgap("The edge connectivity of directed graphs is not implemented "
237
+ "in Boost. The result may be mathematically unreliable.", 18753)
238
+
239
+ boost_graph_from_sage_graph(&g_boost_dir, g, vertex_to_int)
240
+ ec, edges = boost_edge_connectivity(&g_boost_dir)
241
+
242
+ else:
243
+ raise TypeError("the input must be a Sage graph")
244
+
245
+ return (ec, [(int_to_vertex[u], int_to_vertex[v]) for u, v in edges])
246
+
247
+
248
+ cdef boost_clustering_coeff(BoostGenGraph *g, vertices):
249
+ r"""
250
+ Compute the clustering coefficient of all vertices in the list provided.
251
+
252
+ The output is a pair ``[average_clustering_coefficient, clust_of_v]``, where
253
+ ``average_clustering_coefficient`` is the average clustering of the vertices
254
+ in variable ``vertices``, ``clust_of_v`` is a dictionary that associates to
255
+ each vertex (stored as an integer) its clustering coefficient.
256
+ """
257
+ cdef result_cc result
258
+ cdef double result_d
259
+ cdef v_index vi
260
+ cdef dict clust_of_v
261
+
262
+ if len(vertices) == g.num_verts():
263
+ sig_on()
264
+ result = g[0].clustering_coeff_all()
265
+ sig_off()
266
+ clust_of_v = {v: result.clust_of_v[v] for v in range(g.num_verts())}
267
+ return (result.average_clustering_coefficient, clust_of_v)
268
+
269
+ else:
270
+ clust_of_v = {}
271
+ for v in vertices:
272
+ vi = v
273
+ sig_on()
274
+ result_d = g[0].clustering_coeff(vi)
275
+ sig_off()
276
+ clust_of_v[v] = result_d
277
+ return ((sum(clust_of_v.itervalues()) / len(clust_of_v)), clust_of_v)
278
+
279
+
280
+ cpdef clustering_coeff(g, vertices=None):
281
+ r"""
282
+ Compute the clustering coefficient of the input graph, using Boost.
283
+
284
+ .. SEEALSO::
285
+
286
+ :meth:`sage.graphs.generic_graph.GenericGraph.clustering_coeff`
287
+
288
+ INPUT:
289
+
290
+ - ``g`` -- the input Sage Graph
291
+
292
+ - ``vertices`` -- list (default: ``None``); the list of vertices to analyze
293
+ (if ``None``, compute the clustering coefficient of all vertices)
294
+
295
+ OUTPUT: a pair ``(average_clustering_coefficient, clust_of_v)``, where
296
+ ``average_clustering_coefficient`` is the average clustering of the vertices
297
+ in variable ``vertices``, ``clust_of_v`` is a dictionary that associates to
298
+ each vertex its clustering coefficient. If ``vertices`` is ``None``, all
299
+ vertices are considered.
300
+
301
+ EXAMPLES:
302
+
303
+ Computing the clustering coefficient of a clique::
304
+
305
+ sage: from sage.graphs.base.boost_graph import clustering_coeff
306
+ sage: g = graphs.CompleteGraph(5)
307
+ sage: clustering_coeff(g)
308
+ (1.0, {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0})
309
+ sage: clustering_coeff(g, vertices = [0,1,2])
310
+ (1.0, {0: 1.0, 1: 1.0, 2: 1.0})
311
+
312
+ Of a non-clique graph with triangles::
313
+
314
+ sage: g = graphs.IcosahedralGraph()
315
+ sage: clustering_coeff(g, vertices=[1,2,3])
316
+ (0.5, {1: 0.5, 2: 0.5, 3: 0.5})
317
+
318
+ With labels::
319
+
320
+ sage: g.relabel(list("abcdefghiklm"))
321
+ sage: clustering_coeff(g, vertices='abde')
322
+ (0.5, {'a': 0.5, 'b': 0.5, 'd': 0.5, 'e': 0.5})
323
+ """
324
+ from sage.graphs.graph import Graph
325
+
326
+ # These variables are automatically deleted when the function terminates.
327
+ cdef BoostVecGraph g_boost
328
+ cdef v_index i
329
+ cdef list g_vertices = list(g)
330
+ cdef dict vertex_to_int = {v: i for i, v in enumerate(g_vertices)}
331
+
332
+ if not isinstance(g, Graph):
333
+ raise TypeError("the input must be a Sage Graph")
334
+
335
+ boost_graph_from_sage_graph(&g_boost, g, vertex_to_int)
336
+
337
+ if vertices is None:
338
+ vertices = g_vertices
339
+
340
+ cdef list vertices_boost = [vertex_to_int[v] for v in vertices]
341
+ average_clustering, clust_v_int = boost_clustering_coeff(&g_boost, vertices_boost)
342
+ cdef dict clust_v_sage = {g_vertices[v]: clust_v_int[v] for v in vertices_boost}
343
+ return (average_clustering, clust_v_sage)
344
+
345
+
346
+ @cython.binding(True)
347
+ cpdef dominator_tree(g, root, return_dict=False, reverse=False):
348
+ r"""
349
+ Use Boost to compute the dominator tree of ``g``, rooted at ``root``.
350
+
351
+ A node `d` dominates a node `n` if every path from the entry node
352
+ ``root`` to `n` must go through `d`. The immediate dominator of a node
353
+ `n` is the unique node that strictly dominates `n` but does not dominate
354
+ any other node that dominates `n`. A dominator tree is a tree where each
355
+ node's children are those nodes it immediately dominates. For more
356
+ information, see the :wikipedia:`Dominator_(graph_theory)`.
357
+
358
+ If the graph is connected and undirected, the parent of a vertex `v` is:
359
+
360
+ - the root if `v` is in the same biconnected component as the root;
361
+
362
+ - the first cut vertex in a path from `v` to the root, otherwise.
363
+
364
+ If the graph is not connected, the dominator tree of the whole graph is
365
+ equal to the dominator tree of the connected component of the root.
366
+
367
+ If the graph is directed, computing a dominator tree is more complicated,
368
+ and it needs time `O(m\log m)`, where `m` is the number of edges. The
369
+ implementation provided by Boost is the most general one, so it needs time
370
+ `O(m\log m)` even for undirected graphs.
371
+
372
+ INPUT:
373
+
374
+ - ``g`` -- the input Sage (Di)Graph
375
+
376
+ - ``root`` -- the root of the dominator tree
377
+
378
+ - ``return_dict`` -- boolean (default: ``False``); if ``True``, the function
379
+ returns a dictionary associating to each vertex its parent in the
380
+ dominator tree. If ``False`` (default), it returns the whole tree, as a
381
+ ``Graph`` or a ``DiGraph``.
382
+
383
+ - ``reverse`` -- boolean (default: ``False``); when set to ``True``,
384
+ computes the dominator tree in the reverse graph
385
+
386
+ OUTPUT:
387
+
388
+ The dominator tree, as a graph or as a dictionary, depending on the
389
+ value of ``return_dict``. If the output is a dictionary, it will contain
390
+ ``None`` in correspondence of ``root`` and of vertices that are not
391
+ reachable from ``root``. If the output is a graph, it will not contain
392
+ vertices that are not reachable from ``root``.
393
+
394
+ EXAMPLES:
395
+
396
+ An undirected grid is biconnected, and its dominator tree is a star
397
+ (everyone's parent is the root)::
398
+
399
+ sage: g = graphs.GridGraph([2,2]).dominator_tree((0,0))
400
+ sage: g.to_dictionary()
401
+ {(0, 0): [(0, 1), (1, 0), (1, 1)], (0, 1): [(0, 0)], (1, 0): [(0, 0)], (1, 1): [(0, 0)]}
402
+
403
+ If the graph is made by two 3-cycles `C_1,C_2` connected by an edge `(v,w)`,
404
+ with `v \in C_1`, `w \in C_2`, the cut vertices are `v` and `w`, the
405
+ biconnected components are `C_1`, `C_2`, and the edge `(v,w)`. If the root
406
+ is in `C_1`, the parent of each vertex in `C_1` is the root, the parent of
407
+ `w` is `v`, and the parent of each vertex in `C_2` is `w`::
408
+
409
+ sage: G = 2 * graphs.CycleGraph(3)
410
+ sage: v = 0
411
+ sage: w = 3
412
+ sage: G.add_edge(v,w)
413
+ sage: G.dominator_tree(1, return_dict=True)
414
+ {0: 1, 1: None, 2: 1, 3: 0, 4: 3, 5: 3}
415
+
416
+ An example with a directed graph::
417
+
418
+ sage: g = digraphs.Circuit(10).dominator_tree(5)
419
+ sage: g.to_dictionary()
420
+ {0: [1], 1: [2], 2: [3], 3: [4], 4: [], 5: [6], 6: [7], 7: [8], 8: [9], 9: [0]}
421
+ sage: g = digraphs.Circuit(10).dominator_tree(5, reverse=True)
422
+ sage: g.to_dictionary()
423
+ {0: [9], 1: [0], 2: [1], 3: [2], 4: [3], 5: [4], 6: [], 7: [6], 8: [7], 9: [8]}
424
+
425
+ If the output is a dictionary::
426
+
427
+ sage: graphs.GridGraph([2,2]).dominator_tree((0,0), return_dict=True)
428
+ {(0, 0): None, (0, 1): (0, 0), (1, 0): (0, 0), (1, 1): (0, 0)}
429
+
430
+ TESTS:
431
+
432
+ If ``g`` is not a graph, an error is raised::
433
+
434
+ sage: from sage.graphs.base.boost_graph import dominator_tree
435
+ sage: dominator_tree('I am not a graph', 0)
436
+ Traceback (most recent call last):
437
+ ...
438
+ TypeError: the input must be a Sage Graph or DiGraph
439
+
440
+ If ``root`` is not a vertex, an error is raised::
441
+
442
+ sage: digraphs.TransitiveTournament(10).dominator_tree('Not a vertex!')
443
+ Traceback (most recent call last):
444
+ ...
445
+ ValueError: the input root must be a vertex of the given graph
446
+ sage: graphs.GridGraph([2,2]).dominator_tree(0)
447
+ Traceback (most recent call last):
448
+ ...
449
+ ValueError: the input root must be a vertex of the given graph
450
+ """
451
+ from sage.graphs.graph import Graph
452
+ from sage.graphs.digraph import DiGraph
453
+
454
+ if not isinstance(g, (Graph, DiGraph)):
455
+ raise TypeError("the input must be a Sage Graph or DiGraph")
456
+ if root not in g:
457
+ raise ValueError("the input root must be a vertex of the given graph")
458
+
459
+ # These variables are automatically deleted when the function terminates.
460
+ cdef BoostVecGraph g_boost_und
461
+ cdef BoostVecDiGraph g_boost_dir
462
+ cdef vector[v_index] result
463
+ cdef v_index i
464
+ cdef list int_to_vertex = list(g)
465
+ cdef dict vertex_to_int = {v: i for i, v in enumerate(int_to_vertex)}
466
+
467
+ if isinstance(g, Graph):
468
+ boost_graph_from_sage_graph(&g_boost_und, g, vertex_to_int, reverse)
469
+ i = vertex_to_int[root]
470
+ sig_on()
471
+ result = g_boost_und.dominator_tree(i)
472
+ sig_off()
473
+
474
+ elif isinstance(g, DiGraph):
475
+ boost_graph_from_sage_graph(&g_boost_dir, g, vertex_to_int, reverse)
476
+ i = vertex_to_int[root]
477
+ sig_on()
478
+ result = g_boost_dir.dominator_tree(i)
479
+ sig_off()
480
+
481
+ cdef v_index no_parent = -1
482
+
483
+ if return_dict:
484
+ return {v: (None if result[i] == no_parent else int_to_vertex[<int> result[i]]) for i, v in enumerate(int_to_vertex)}
485
+
486
+ cdef list edges = [[int_to_vertex[<int> result[i]], v] for i, v in enumerate(int_to_vertex) if result[i] != no_parent]
487
+
488
+ if g.is_directed():
489
+ if not edges:
490
+ g = DiGraph()
491
+ g.add_vertex(root)
492
+ return g
493
+ else:
494
+ return DiGraph(edges)
495
+ else:
496
+ if not edges:
497
+ g = Graph()
498
+ g.add_vertex(root)
499
+ return g
500
+ else:
501
+ return Graph(edges)
502
+
503
+
504
+ cpdef bandwidth_heuristics(g, algorithm='cuthill_mckee'):
505
+ r"""
506
+ Use Boost heuristics to approximate the bandwidth of the input graph.
507
+
508
+ The bandwidth `bw(M)` of a matrix `M` is the smallest integer `k` such that
509
+ all nonzero entries of `M` are at distance `k` from the diagonal. The
510
+ bandwidth `bw(g)` of an undirected graph `g` is the minimum bandwidth of
511
+ the adjacency matrix of `g`, over all possible relabellings of its vertices
512
+ (for more information, see the
513
+ :mod:`~sage.graphs.graph_decompositions.bandwidth`
514
+ module).
515
+
516
+ Unfortunately, exactly computing the bandwidth is NP-hard (and an
517
+ exponential algorithm is implemented in Sagemath in routine
518
+ :func:`~sage.graphs.graph_decompositions.bandwidth.bandwidth`). Here, we
519
+ implement two heuristics to find good orderings: Cuthill-McKee, reverse
520
+ Cuthill-McKee (also known as ``RCM``) and King.
521
+
522
+ This function works only in undirected graphs, and its running time is
523
+ `O(md_{max}\log d_{max})` for the Cuthill-McKee ordering, and
524
+ `O(md_{max}^2\log d_{max})` for the King ordering, where `m` is the number
525
+ of edges, and `d_{max}` is the maximum degree in the graph.
526
+
527
+ INPUT:
528
+
529
+ - ``g`` -- the input Sage graph
530
+
531
+ - ``algorithm`` -- string (default: ``'cuthill_mckee'``); the heuristic used
532
+ to compute the ordering among ``'cuthill_mckee'``,
533
+ ``'reverse_cuthill_mckee'`` and ``'king'``
534
+
535
+ OUTPUT:
536
+
537
+ A pair ``[bandwidth, ordering]``, where ``ordering`` is the ordering of
538
+ vertices, ``bandwidth`` is the bandwidth of that specific ordering (which
539
+ is not necessarily the bandwidth of the graph, because this is a heuristic).
540
+
541
+ EXAMPLES::
542
+
543
+ sage: from sage.graphs.base.boost_graph import bandwidth_heuristics
544
+ sage: bandwidth_heuristics(graphs.PathGraph(10))
545
+ (1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
546
+ sage: bandwidth_heuristics(graphs.GridGraph([3,3]))
547
+ (3, [(0, 0), (1, 0), (0, 1), (2, 0), (1, 1), (0, 2), (2, 1), (1, 2), (2, 2)])
548
+ sage: bandwidth_heuristics(graphs.GridGraph([3,3]), algorithm='reverse_cuthill_mckee')
549
+ (3, [(2, 2), (1, 2), (2, 1), (0, 2), (1, 1), (2, 0), (0, 1), (1, 0), (0, 0)])
550
+ sage: bandwidth_heuristics(graphs.GridGraph([3,3]), algorithm='king')
551
+ (3, [(0, 0), (1, 0), (0, 1), (2, 0), (1, 1), (0, 2), (2, 1), (1, 2), (2, 2)])
552
+
553
+ TESTS:
554
+
555
+ Given an input which is not a graph::
556
+
557
+ sage: from sage.graphs.base.boost_graph import bandwidth_heuristics
558
+ sage: bandwidth_heuristics(digraphs.Path(10))
559
+ Traceback (most recent call last):
560
+ ...
561
+ TypeError: the input must be a Sage Graph
562
+ sage: bandwidth_heuristics("I am not a graph!")
563
+ Traceback (most recent call last):
564
+ ...
565
+ TypeError: the input must be a Sage Graph
566
+
567
+ Given a wrong algorithm::
568
+
569
+ sage: from sage.graphs.base.boost_graph import bandwidth_heuristics
570
+ sage: bandwidth_heuristics(graphs.PathGraph(3), algorithm='tip top')
571
+ Traceback (most recent call last):
572
+ ...
573
+ ValueError: unknown algorithm 'tip top'
574
+
575
+ Given a graph with no edges::
576
+
577
+ sage: from sage.graphs.base.boost_graph import bandwidth_heuristics
578
+ sage: bandwidth_heuristics(Graph())
579
+ (0, [])
580
+ sage: bandwidth_heuristics(graphs.RandomGNM(10,0)) # needs networkx
581
+ (0, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
582
+ """
583
+ from sage.graphs.graph import Graph
584
+
585
+ # Tests for errors and trivial cases
586
+ if not isinstance(g, Graph):
587
+ raise TypeError("the input must be a Sage Graph")
588
+ if algorithm not in ['cuthill_mckee', 'reverse_cuthill_mckee', 'king']:
589
+ raise ValueError(f"unknown algorithm {algorithm!r}")
590
+ if not g.num_edges():
591
+ return (0, list(g))
592
+
593
+ cdef bint reverse = False
594
+ if algorithm == 'reverse_cuthill_mckee':
595
+ reverse = True
596
+ algorithm = 'cuthill_mckee'
597
+
598
+ # These variables are automatically deleted when the function terminates.
599
+ cdef BoostVecGraph g_boost
600
+ cdef vector[v_index] result
601
+ cdef v_index i
602
+ cdef list int_to_vertex = list(g)
603
+ cdef dict vertex_to_int = {v: i for i, v in enumerate(int_to_vertex)}
604
+
605
+ boost_graph_from_sage_graph(&g_boost, g, vertex_to_int)
606
+ cdef bint use_cuthill_mckee = (algorithm == 'cuthill_mckee')
607
+ sig_on()
608
+ result = g_boost.bandwidth_ordering(use_cuthill_mckee)
609
+ sig_off()
610
+
611
+ cdef int n = g.num_verts()
612
+ cdef dict pos = {int_to_vertex[<int> result[i]]: i for i in range(n)}
613
+ cdef int bandwidth = max([abs(pos[u] - pos[v])
614
+ for u, v in g.edge_iterator(labels=False)])
615
+
616
+ if reverse:
617
+ return (bandwidth, [int_to_vertex[<int> result[i]]
618
+ for i in range(n - 1, -1, -1)])
619
+ return (bandwidth, [int_to_vertex[<int> result[i]] for i in range(n)])
620
+
621
+
622
+ cpdef min_spanning_tree(g,
623
+ weight_function=None,
624
+ algorithm='Kruskal'):
625
+ r"""
626
+ Use Boost to compute the minimum spanning tree of the input graph.
627
+
628
+ INPUT:
629
+
630
+ - ``g`` -- the input Sage graph
631
+
632
+ - ``weight_function`` -- function (default: ``None``); a function that
633
+ inputs an edge ``e`` and outputs its weight. An edge has the form
634
+ ``(u,v,l)``, where ``u`` and ``v`` are vertices, ``l`` is a label (that
635
+ can be of any kind). The ``weight_function`` can be used to transform the
636
+ label into a weight (see the example below). In particular:
637
+
638
+ - if ``weight_function`` is not ``None``, the weight of an edge ``e`` is
639
+ ``weight_function(e)``;
640
+
641
+ - if ``weight_function`` is ``None`` (default) and ``g`` is weighted (that
642
+ is, ``g.weighted()==True``), for each edge ``e=(u,v,l)``, we set weight
643
+ ``l``;
644
+
645
+ - if ``weight_function`` is ``None`` and ``g`` is not weighted, we set all
646
+ weights to 1 (hence, the output can be any spanning tree).
647
+
648
+ Note that, if the weight is not convertible to a number with function
649
+ ``float()``, an error is raised (see tests below).
650
+
651
+ - ``algorithm`` -- string (default: ``'Kruskal'``); the algorithm to use
652
+ among ``'Kruskal'`` and ``'Prim'``
653
+
654
+ OUTPUT:
655
+
656
+ The edges of a minimum spanning tree of ``g``, if one exists, otherwise
657
+ the empty list.
658
+
659
+ .. SEEALSO::
660
+
661
+ - :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`
662
+
663
+ EXAMPLES::
664
+
665
+ sage: from sage.graphs.base.boost_graph import min_spanning_tree
666
+ sage: min_spanning_tree(graphs.PathGraph(4))
667
+ [(0, 1, None), (1, 2, None), (2, 3, None)]
668
+
669
+ sage: G = Graph([(0,1,{'name':'a','weight':1}), (0,2,{'name':'b','weight':3}), (1,2,{'name':'b','weight':1})])
670
+ sage: min_spanning_tree(G, weight_function=lambda e: e[2]['weight'])
671
+ [(0, 1, {'name': 'a', 'weight': 1}), (1, 2, {'name': 'b', 'weight': 1})]
672
+
673
+ TESTS:
674
+
675
+ Given an input which is not a graph::
676
+
677
+ sage: min_spanning_tree("I am not a graph!")
678
+ Traceback (most recent call last):
679
+ ...
680
+ TypeError: the input must be a Sage Graph
681
+
682
+ Given a wrong algorithm::
683
+
684
+ sage: min_spanning_tree(graphs.PathGraph(3), algorithm='tip top')
685
+ Traceback (most recent call last):
686
+ ...
687
+ ValueError: algorithm 'tip top' not yet implemented, please contribute
688
+
689
+ If the weight is not a number::
690
+
691
+ sage: g = Graph([(0,1,1), (1,2,'a')], weighted=True)
692
+ sage: min_spanning_tree(g)
693
+ Traceback (most recent call last):
694
+ ...
695
+ ValueError: could not convert string to float:...
696
+
697
+ sage: g = Graph([(0,1,1), (1,2,[1,2,3])], weighted=True)
698
+ sage: min_spanning_tree(g)
699
+ Traceback (most recent call last):
700
+ ...
701
+ TypeError: float() argument must be a string or a... number...
702
+
703
+ Check that the method is robust to incomparable vertices::
704
+
705
+ sage: G = Graph([(1, 2, 10), (1, 'a', 1), ('a', 'b', 1), ('b', 2, 1)], weighted=True)
706
+ sage: E = min_spanning_tree(G, algorithm='Kruskal')
707
+ sage: sum(w for _, _, w in E)
708
+ 3
709
+ sage: F = min_spanning_tree(G, algorithm='Prim')
710
+ sage: sum(w for _, _, w in F)
711
+ 3
712
+ """
713
+ from sage.graphs.graph import Graph
714
+
715
+ if not isinstance(g, Graph):
716
+ raise TypeError("the input must be a Sage Graph")
717
+ if algorithm not in ['Kruskal', 'Prim']:
718
+ raise ValueError("algorithm '%s' not yet implemented, please contribute" % (algorithm))
719
+
720
+ if g.allows_loops() or g.allows_multiple_edges():
721
+ g = g.to_simple()
722
+ # Now g has no self loops and no multiple edges.
723
+ # These variables are automatically deleted when the function terminates.
724
+ cdef BoostVecWeightedGraph g_boost
725
+ cdef vector[v_index] result
726
+ cdef v_index i
727
+ cdef list int_to_vertex = list(g)
728
+ cdef dict vertex_to_int = {v: i for i, v in enumerate(int_to_vertex)}
729
+
730
+ boost_weighted_graph_from_sage_graph(&g_boost, g, vertex_to_int, weight_function)
731
+
732
+ if algorithm == 'Kruskal':
733
+ sig_on()
734
+ result = g_boost.kruskal_min_spanning_tree()
735
+ sig_off()
736
+ elif algorithm == 'Prim':
737
+ sig_on()
738
+ result = g_boost.prim_min_spanning_tree()
739
+ sig_off()
740
+
741
+ cdef v_index n = g.num_verts()
742
+
743
+ if <v_index> result.size() != 2 * (n - 1):
744
+ return []
745
+ edges = [(int_to_vertex[<int> result[2*i]], int_to_vertex[<int> result[2*i + 1]]) for i in range(n - 1)]
746
+ return [(u, v, g.edge_label(u, v)) for u, v in edges]
747
+
748
+
749
+ cpdef blocks_and_cut_vertices(g):
750
+ r"""
751
+ Compute the blocks and cut vertices of the graph.
752
+
753
+ This method uses the implementation of Tarjan's algorithm available in the
754
+ Boost library .
755
+
756
+ INPUT:
757
+
758
+ - ``g`` -- the input Sage graph
759
+
760
+ OUTPUT:
761
+
762
+ A 2-dimensional vector with m+1 rows (m is the number of biconnected
763
+ components), where each of the first m rows correspond to vertices in a
764
+ block, and the last row is the list of cut vertices.
765
+
766
+ .. SEEALSO::
767
+
768
+ - :meth:`sage.graphs.generic_graph.GenericGraph.blocks_and_cut_vertices`
769
+
770
+ EXAMPLES::
771
+
772
+ sage: from sage.graphs.base.boost_graph import blocks_and_cut_vertices
773
+ sage: g = graphs.KrackhardtKiteGraph()
774
+ sage: blocks_and_cut_vertices(g)
775
+ ([[8, 9], [7, 8], [0, 1, 2, 3, 5, 4, 6, 7]], [8, 7])
776
+
777
+ sage: G = Graph([(0,1,{'name':'a','weight':1}), (0,2,{'name':'b','weight':3}), (1,2,{'name':'b','weight':1})])
778
+ sage: blocks_and_cut_vertices(G)
779
+ ([[0, 1, 2]], [])
780
+
781
+ TESTS:
782
+
783
+ Given an input which is not a graph::
784
+
785
+ sage: blocks_and_cut_vertices("I am not a graph!")
786
+ Traceback (most recent call last):
787
+ ...
788
+ TypeError: the input must be a Sage graph
789
+ """
790
+ from sage.graphs.generic_graph import GenericGraph
791
+
792
+ if not isinstance(g, GenericGraph):
793
+ raise TypeError("the input must be a Sage graph")
794
+
795
+ if g.allows_loops() or g.allows_multiple_edges() or g.is_directed():
796
+ g = g.to_simple()
797
+
798
+ cdef BoostVecGraph g_boost
799
+ cdef vector[vector[v_index]] result
800
+ cdef v_index vi
801
+ cdef list int_to_vertex = list(g)
802
+ cdef dict vertex_to_int = {vv: vi for vi, vv in enumerate(int_to_vertex)}
803
+ cdef list vertex_status = [-1] * g.order()
804
+
805
+ boost_graph_from_sage_graph(&g_boost, g, vertex_to_int)
806
+ sig_on()
807
+ result = g_boost.blocks_and_cut_vertices()
808
+ sig_off()
809
+
810
+ cdef list result_blocks = []
811
+ cdef set result_cut = set()
812
+ cdef list result_temp = []
813
+ cdef v_index v
814
+
815
+ # We iterate over the vertices in the blocks and find articulation points
816
+ for i in range(len(result)):
817
+ for v in result[i]:
818
+ # The vertex is seen for the first time
819
+ if vertex_status[v] == -1:
820
+ result_temp.append(int_to_vertex[<int> v])
821
+ vertex_status[v] = i
822
+ # Vertex belongs to a previous block also, must be a cut vertex
823
+ elif vertex_status[v] < i:
824
+ result_cut.add(int_to_vertex[<int> v])
825
+ result_temp.append(int_to_vertex[<int> v])
826
+ # Change the block number to avoid adding the vertex twice
827
+ # as a cut vertex if it is repeated in block i
828
+ vertex_status[v] = i
829
+ # elif vertex_status[v] == i:
830
+ # Nothing to do since we have already added the vertex to block i
831
+
832
+ result_blocks.append(result_temp)
833
+ result_temp = []
834
+
835
+ # If a vertex does not belong to any block, it must be an isolated vertex.
836
+ # Hence, it is considered a block.
837
+ for i in range(g.order()):
838
+ if vertex_status[i] == -1:
839
+ result_blocks.append([int_to_vertex[<int> i]])
840
+
841
+ return (result_blocks, list(result_cut))
842
+
843
+
844
+ cpdef shortest_paths(g, start, weight_function=None, algorithm=None):
845
+ r"""
846
+ Compute the shortest paths from ``start`` to all other vertices.
847
+
848
+ This routine outputs all shortest paths from node ``start`` to any other
849
+ node in the graph. The input graph can be weighted: if the algorithm is
850
+ Dijkstra, no negative weights are allowed, while if the algorithm is
851
+ Bellman-Ford, negative weights are allowed, but there must be no negative
852
+ cycle (otherwise, the shortest paths might not exist).
853
+
854
+ However, Dijkstra algorithm is more efficient: for this reason, we suggest
855
+ to use Bellman-Ford only if necessary (which is also the default option).
856
+ Note that, if the graph is undirected, a negative edge automatically creates
857
+ a negative cycle: for this reason, in this case, Dijkstra algorithm is
858
+ always better.
859
+
860
+ The running-time is `O(n \log n+m)` for Dijkstra algorithm and `O(mn)` for
861
+ Bellman-Ford algorithm, where `n` is the number of nodes and `m` is the
862
+ number of edges.
863
+
864
+ INPUT:
865
+
866
+ - ``g`` -- the input Sage graph
867
+
868
+ - ``start`` -- the starting vertex to compute shortest paths
869
+
870
+ - ``weight_function`` -- function (default: ``None``); a function that
871
+ associates a weight to each edge. If ``None`` (default), the weights of
872
+ ``g`` are used, if ``g.weighted()==True``, otherwise all edges have
873
+ weight 1.
874
+
875
+ - ``algorithm`` -- string (default: ``None``); one of the following
876
+ algorithms:
877
+
878
+ - ``'Dijkstra'``, ``'Dijkstra_Boost'``: the Dijkstra algorithm implemented
879
+ in Boost (works only with positive weights)
880
+
881
+ - ``'Bellman-Ford'``, ``'Bellman-Ford_Boost'``: the Bellman-Ford algorithm
882
+ implemented in Boost (works also with negative weights, if there is no
883
+ negative cycle)
884
+
885
+ OUTPUT:
886
+
887
+ A pair of dictionaries ``(distances, predecessors)`` such that, for each
888
+ vertex ``v``, ``distances[v]`` is the distance from ``start`` to ``v``,
889
+ ``predecessors[v]`` is the last vertex in a shortest path from ``start`` to
890
+ ``v``.
891
+
892
+ EXAMPLES:
893
+
894
+ Undirected graphs::
895
+
896
+ sage: from sage.graphs.base.boost_graph import shortest_paths
897
+ sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
898
+ sage: shortest_paths(g, 1)
899
+ ({0: 1, 1: 0, 2: 2, 3: 3}, {0: 1, 1: None, 2: 1, 3: 2})
900
+ sage: g = graphs.GridGraph([2,2])
901
+ sage: shortest_paths(g,(0,0),weight_function=lambda e:2)
902
+ ({(0, 0): 0, (0, 1): 2, (1, 0): 2, (1, 1): 4},
903
+ {(0, 0): None, (0, 1): (0, 0), (1, 0): (0, 0), (1, 1): (0, 1)})
904
+
905
+ Directed graphs::
906
+
907
+ sage: g = DiGraph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
908
+ sage: shortest_paths(g, 1)
909
+ ({1: 0, 2: 2, 3: 3}, {1: None, 2: 1, 3: 2})
910
+
911
+ TESTS:
912
+
913
+ Given an input which is not a graph::
914
+
915
+ sage: shortest_paths("I am not a graph!", 1)
916
+ Traceback (most recent call last):
917
+ ...
918
+ TypeError: the input must be a Sage graph
919
+
920
+ If there is a negative cycle::
921
+
922
+ sage: from sage.graphs.base.boost_graph import shortest_paths
923
+ sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True)
924
+ sage: shortest_paths(g, 1)
925
+ Traceback (most recent call last):
926
+ ...
927
+ ValueError: the graph contains a negative cycle
928
+
929
+ If Dijkstra is used with negative weights::
930
+
931
+ sage: from sage.graphs.base.boost_graph import shortest_paths
932
+ sage: g = DiGraph([(0,1,1),(1,2,-2),(1,3,4),(2,3,1)], weighted=True)
933
+ sage: shortest_paths(g, 1, algorithm='Dijkstra')
934
+ Traceback (most recent call last):
935
+ ...
936
+ RuntimeError: Dijkstra algorithm does not work with negative weights, use Bellman-Ford instead
937
+
938
+ Wrong starting vertex::
939
+
940
+ sage: shortest_paths(g, 55)
941
+ Traceback (most recent call last):
942
+ ...
943
+ ValueError: the starting vertex 55 is not in the graph
944
+ """
945
+ from sage.graphs.generic_graph import GenericGraph
946
+
947
+ if not isinstance(g, GenericGraph):
948
+ raise TypeError("the input must be a Sage graph")
949
+
950
+ if start not in g:
951
+ raise ValueError("the starting vertex " + str(start) + " is not in " +
952
+ "the graph")
953
+
954
+ if not g.num_edges():
955
+ return ({start: 0}, {start: None})
956
+
957
+ # These variables are automatically deleted when the function terminates.
958
+ cdef v_index vi
959
+ cdef dict int_to_v = dict(enumerate(g))
960
+ cdef dict v_to_int = {vv: vi for vi, vv in enumerate(g)}
961
+ cdef BoostVecWeightedDiGraphU g_boost_dir
962
+ cdef BoostVecWeightedGraph g_boost_und
963
+ cdef result_distances result
964
+
965
+ if algorithm is None:
966
+ # Check if there are edges with negative weights
967
+ if weight_function is not None:
968
+ for e in g.edge_iterator():
969
+ if float(weight_function(e)) < 0:
970
+ algorithm = 'Bellman-Ford'
971
+ break
972
+ elif g.weighted():
973
+ for _, _, w in g.edge_iterator():
974
+ if float(w) < 0:
975
+ algorithm = 'Bellman-Ford'
976
+ break
977
+
978
+ if algorithm is None:
979
+ algorithm = 'Dijkstra'
980
+
981
+ if algorithm in ['Bellman-Ford', 'Bellman-Ford_Boost']:
982
+ if g.is_directed():
983
+ boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
984
+ vi = v_to_int[start]
985
+ sig_on()
986
+ result = g_boost_dir.bellman_ford_shortest_paths(vi)
987
+ sig_off()
988
+ else:
989
+ boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
990
+ vi = v_to_int[start]
991
+ sig_on()
992
+ result = g_boost_und.bellman_ford_shortest_paths(vi)
993
+ sig_off()
994
+ if not result.distances.size():
995
+ raise ValueError("the graph contains a negative cycle")
996
+
997
+ elif algorithm in ['Dijkstra', 'Dijkstra_Boost']:
998
+ try:
999
+ if g.is_directed():
1000
+ boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
1001
+ vi = v_to_int[start]
1002
+ sig_on()
1003
+ result = g_boost_dir.dijkstra_shortest_paths(vi)
1004
+ sig_off()
1005
+ else:
1006
+ boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
1007
+ vi = v_to_int[start]
1008
+ sig_on()
1009
+ result = g_boost_und.dijkstra_shortest_paths(vi)
1010
+ sig_off()
1011
+ if not result.distances.size():
1012
+ raise RuntimeError("Dijkstra algorithm does not work with negative weights, use Bellman-Ford instead")
1013
+ except RuntimeError:
1014
+ raise RuntimeError("Dijkstra algorithm does not work with negative weights, use Bellman-Ford instead")
1015
+
1016
+ else:
1017
+ raise ValueError(f"unknown algorithm {algorithm!r}")
1018
+
1019
+ dist = {}
1020
+ pred = {}
1021
+
1022
+ if weight_function is not None:
1023
+ correct_type = type(weight_function(next(g.edge_iterator())))
1024
+ elif g.weighted():
1025
+ correct_type = type(next(g.edge_iterator())[2])
1026
+ else:
1027
+ correct_type = int
1028
+ # Needed for rational curves.
1029
+ try:
1030
+ from sage.rings.real_mpfr import RealNumber, RR
1031
+ except ImportError:
1032
+ pass
1033
+ else:
1034
+ if correct_type == RealNumber:
1035
+ correct_type = RR
1036
+
1037
+ import sys
1038
+ for v in range(g.num_verts()):
1039
+ if result.distances[v] != sys.float_info.max:
1040
+ w = int_to_v[v]
1041
+ dist[w] = correct_type(result.distances[v])
1042
+ pred[w] = int_to_v[result.predecessors[v]] if result.predecessors[v] != v else None
1043
+ return (dist, pred)
1044
+
1045
+
1046
+ cdef get_predecessors(BoostWeightedGraph g, result, int_to_v, directed, weight_type):
1047
+ r"""
1048
+ Return the predecessor matrix from the distance matrix of the graph.
1049
+
1050
+ INPUT:
1051
+
1052
+ - ``g`` -- the input boost graph
1053
+
1054
+ - ``result`` -- the matrix of shortest distances
1055
+
1056
+ - ``int_to_v`` -- list; it is a mapping from `(0, \ldots, n-1)`
1057
+ to the vertex set of the original sage graph
1058
+
1059
+ - ``directed`` -- boolean; whether the input graph is directed
1060
+
1061
+ - ``weight_type`` -- correct data type for edge weights
1062
+
1063
+ OUTPUT:
1064
+
1065
+ A dictionary of dictionaries ``pred`` such that ``pred[u][v]`` indicates
1066
+ the predecessor of `v` in the shortest path from `u` to `v`.
1067
+
1068
+ TESTS::
1069
+
1070
+ sage: from sage.graphs.base.boost_graph import johnson_shortest_paths
1071
+ sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
1072
+ sage: expected = {0: {0: None, 1: 0, 2: 1, 3: 2},
1073
+ ....: 1: {0: 1, 1: None, 2: 1, 3: 2},
1074
+ ....: 2: {0: 1, 1: 2, 2: None, 3: 2},
1075
+ ....: 3: {0: 1, 1: 2, 2: 3, 3: None}}
1076
+ sage: johnson_shortest_paths(g, distances=False, predecessors=True) == expected
1077
+ True
1078
+ """
1079
+ cdef vector[pair[v_index, pair[v_index, double]]] edges
1080
+ sig_on()
1081
+ edges = g.edge_list()
1082
+ sig_off()
1083
+ cdef int N = g.num_verts()
1084
+ cdef dict pred = {v: {v: None} for v in int_to_v}
1085
+ import sys
1086
+ for p in edges:
1087
+ dst = weight_type(p.second.second)
1088
+ # dst is the weight of the edge (u, v)
1089
+ u = p.first
1090
+ v = p.second.first
1091
+ for k in range(N):
1092
+ if result[k][u] == sys.float_info.max or result[k][v] == sys.float_info.max:
1093
+ continue
1094
+ if weight_type(result[k][u]) + dst == weight_type(result[k][v]):
1095
+ pred[int_to_v[k]][int_to_v[v]] = int_to_v[u]
1096
+ if directed:
1097
+ continue
1098
+ if weight_type(result[k][u]) == weight_type(result[k][v]) + dst:
1099
+ pred[int_to_v[k]][int_to_v[u]] = int_to_v[v]
1100
+ return pred
1101
+
1102
+
1103
+ cpdef johnson_shortest_paths(g, weight_function=None, distances=True, predecessors=False):
1104
+ r"""
1105
+ Use Johnson algorithm to solve the all-pairs-shortest-paths.
1106
+
1107
+ This routine outputs the distance between each pair of vertices and the
1108
+ predecessors matrix (depending on the values of boolean ``distances`` and
1109
+ ``predecessors``) using a dictionary of dictionaries. It works on all kinds
1110
+ of graphs, but it is designed specifically for graphs with negative weights
1111
+ (otherwise there are more efficient algorithms, like Dijkstra).
1112
+
1113
+ The time-complexity is `O(mn\log n)`, where `n` is the number of nodes and
1114
+ `m` is the number of edges.
1115
+
1116
+ INPUT:
1117
+
1118
+ - ``g`` -- the input Sage graph
1119
+
1120
+ - ``weight_function`` -- function (default: ``None``); a function that
1121
+ associates a weight to each edge. If ``None`` (default), the weights of
1122
+ ``g`` are used, if ``g.weighted()==True``, otherwise all edges have
1123
+ weight 1.
1124
+
1125
+ - ``distances`` -- boolean (default: ``True``); whether to return the
1126
+ dictionary of shortest distances
1127
+
1128
+ - ``predecessors`` -- boolean (default: ``False``); whether to return the
1129
+ predecessors matrix
1130
+
1131
+ OUTPUT:
1132
+
1133
+ Depending on the input, this function return the dictionary of
1134
+ predecessors, the dictionary of distances, or a pair of dictionaries
1135
+ ``(distances, predecessors)`` where ``distance[u][v]`` denotes the distance
1136
+ of a shortest path from `u` to `v` and ``predecessors[u][v]`` indicates the
1137
+ predecessor of `w` on a shortest path from `u` to `v`.
1138
+
1139
+ EXAMPLES:
1140
+
1141
+ Undirected graphs::
1142
+
1143
+ sage: from sage.graphs.base.boost_graph import johnson_shortest_paths
1144
+ sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
1145
+ sage: johnson_shortest_paths(g)
1146
+ {0: {0: 0, 1: 1, 2: 3, 3: 4},
1147
+ 1: {0: 1, 1: 0, 2: 2, 3: 3},
1148
+ 2: {0: 3, 1: 2, 2: 0, 3: 1},
1149
+ 3: {0: 4, 1: 3, 2: 1, 3: 0}}
1150
+ sage: expected = {0: {0: None, 1: 0, 2: 1, 3: 2},
1151
+ ....: 1: {0: 1, 1: None, 2: 1, 3: 2},
1152
+ ....: 2: {0: 1, 1: 2, 2: None, 3: 2},
1153
+ ....: 3: {0: 1, 1: 2, 2: 3, 3: None}}
1154
+ sage: johnson_shortest_paths(g, distances=False, predecessors=True) == expected
1155
+ True
1156
+
1157
+ Directed graphs::
1158
+
1159
+ sage: g = DiGraph([(0,1,1),(1,2,-2),(1,3,4),(2,3,1)], weighted=True)
1160
+ sage: johnson_shortest_paths(g)
1161
+ {0: {0: 0, 1: 1, 2: -1, 3: 0},
1162
+ 1: {1: 0, 2: -2, 3: -1},
1163
+ 2: {2: 0, 3: 1},
1164
+ 3: {3: 0}}
1165
+ sage: g = DiGraph([(1,2,3),(2,3,2),(1,4,1),(4,2,1)], weighted=True)
1166
+ sage: johnson_shortest_paths(g, distances=False, predecessors=True)
1167
+ {1: {1: None, 2: 4, 3: 2, 4: 1},
1168
+ 2: {2: None, 3: 2},
1169
+ 3: {3: None},
1170
+ 4: {2: 4, 3: 2, 4: None}}
1171
+
1172
+ TESTS:
1173
+
1174
+ Given an input which is not a graph::
1175
+
1176
+ sage: johnson_shortest_paths("I am not a graph!")
1177
+ Traceback (most recent call last):
1178
+ ...
1179
+ TypeError: the input must be a Sage graph
1180
+
1181
+ If there is a negative cycle::
1182
+
1183
+ sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True)
1184
+ sage: johnson_shortest_paths(g)
1185
+ Traceback (most recent call last):
1186
+ ...
1187
+ ValueError: the graph contains a negative cycle
1188
+ """
1189
+ from sage.graphs.generic_graph import GenericGraph
1190
+ cdef dict dist = {}
1191
+ cdef dict pred = {}
1192
+
1193
+ if not isinstance(g, GenericGraph):
1194
+ raise TypeError("the input must be a Sage graph")
1195
+ elif not g.num_edges():
1196
+ dist = {v: {v: 0} for v in g}
1197
+ pred = {v: {v: None} for v in g}
1198
+ if distances and predecessors:
1199
+ return (dist, pred)
1200
+ if distances:
1201
+ return dist
1202
+ if predecessors:
1203
+ return pred
1204
+ # These variables are automatically deleted when the function terminates.
1205
+ cdef v_index i
1206
+ cdef list int_to_v = list(g)
1207
+ cdef dict v_to_int = {v: i for i, v in enumerate(int_to_v)}
1208
+ cdef BoostVecWeightedDiGraphU g_boost_dir
1209
+ cdef BoostVecWeightedGraph g_boost_und
1210
+ cdef int N = g.num_verts()
1211
+ cdef vector[vector[double]] result
1212
+
1213
+ if g.is_directed():
1214
+ boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
1215
+ sig_on()
1216
+ result = g_boost_dir.johnson_shortest_paths()
1217
+ sig_off()
1218
+ else:
1219
+ boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
1220
+ sig_on()
1221
+ result = g_boost_und.johnson_shortest_paths()
1222
+ sig_off()
1223
+
1224
+ if not result.size():
1225
+ raise ValueError("the graph contains a negative cycle")
1226
+
1227
+ if weight_function is not None:
1228
+ correct_type = type(weight_function(next(g.edge_iterator())))
1229
+ elif g.weighted():
1230
+ correct_type = type(next(g.edge_iterator())[2])
1231
+ else:
1232
+ correct_type = int
1233
+ # Needed for rational curves.
1234
+ try:
1235
+ from sage.rings.real_mpfr import RealNumber, RR
1236
+ except ImportError:
1237
+ pass
1238
+ else:
1239
+ if correct_type == RealNumber:
1240
+ correct_type = RR
1241
+
1242
+ import sys
1243
+ if distances:
1244
+ dist = {int_to_v[v]: {int_to_v[w]: correct_type(result[v][w])
1245
+ for w in range(N) if result[v][w] != sys.float_info.max}
1246
+ for v in range(N)}
1247
+
1248
+ if predecessors:
1249
+ if g.is_directed():
1250
+ pred = get_predecessors(g_boost_dir, result, int_to_v, True, correct_type)
1251
+ else:
1252
+ pred = get_predecessors(g_boost_und, result, int_to_v, False, correct_type)
1253
+
1254
+ if distances and predecessors:
1255
+ return (dist, pred)
1256
+ if distances:
1257
+ return dist
1258
+ if predecessors:
1259
+ return pred
1260
+
1261
+
1262
+ cpdef floyd_warshall_shortest_paths(g, weight_function=None, distances=True, predecessors=False):
1263
+ r"""
1264
+ Use Floyd-Warshall algorithm to solve the all-pairs-shortest-paths.
1265
+
1266
+ This routine outputs the distance between each pair of vertices and the
1267
+ predecessors matrix (depending on the values of boolean ``distances`` and
1268
+ ``predecessors``) using a dictionary of dictionaries. This method should be
1269
+ preferred only if the graph is dense. If the graph is sparse the much
1270
+ faster johnson_shortest_paths should be used.
1271
+
1272
+ The time-complexity is `O(n^3 + nm)`, where `n` is the number of nodes and
1273
+ `m` the number of edges. The factor `nm` in the complexity is added only
1274
+ when ``predecessors`` is set to ``True``.
1275
+
1276
+ INPUT:
1277
+
1278
+ - ``g`` -- the input Sage graph
1279
+
1280
+ - ``weight_function`` -- function (default: ``None``); a function that
1281
+ associates a weight to each edge. If ``None`` (default), the weights of
1282
+ ``g`` are used, if ``g.weighted()==True``, otherwise all edges have
1283
+ weight 1.
1284
+
1285
+ - ``distances`` -- boolean (default: ``True``); whether to return
1286
+ the dictionary of shortest distances
1287
+
1288
+ - ``predecessors`` -- boolean (default: ``False``); whether to return the
1289
+ predecessors matrix
1290
+
1291
+ OUTPUT:
1292
+
1293
+ Depending on the input, this function return the dictionary of
1294
+ predecessors, the dictionary of distances, or a pair of dictionaries
1295
+ ``(distances, predecessors)`` where ``distance[u][v]`` denotes the distance
1296
+ of a shortest path from `u` to `v` and ``predecessors[u][v]`` indicates the
1297
+ predecessor of `w` on a shortest path from `u` to `v`.
1298
+
1299
+ EXAMPLES:
1300
+
1301
+ Undirected graphs::
1302
+
1303
+ sage: from sage.graphs.base.boost_graph import floyd_warshall_shortest_paths
1304
+ sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
1305
+ sage: floyd_warshall_shortest_paths(g)
1306
+ {0: {0: 0, 1: 1, 2: 3, 3: 4},
1307
+ 1: {0: 1, 1: 0, 2: 2, 3: 3},
1308
+ 2: {0: 3, 1: 2, 2: 0, 3: 1},
1309
+ 3: {0: 4, 1: 3, 2: 1, 3: 0}}
1310
+ sage: expected = {0: {0: None, 1: 0, 2: 1, 3: 2},
1311
+ ....: 1: {0: 1, 1: None, 2: 1, 3: 2},
1312
+ ....: 2: {0: 1, 1: 2, 2: None, 3: 2},
1313
+ ....: 3: {0: 1, 1: 2, 2: 3, 3: None}}
1314
+ sage: floyd_warshall_shortest_paths(g, distances=False, predecessors=True) == expected
1315
+ True
1316
+
1317
+ Directed graphs::
1318
+
1319
+ sage: g = DiGraph([(0,1,1),(1,2,-2),(1,3,4),(2,3,1)], weighted=True)
1320
+ sage: floyd_warshall_shortest_paths(g)
1321
+ {0: {0: 0, 1: 1, 2: -1, 3: 0},
1322
+ 1: {1: 0, 2: -2, 3: -1},
1323
+ 2: {2: 0, 3: 1},
1324
+ 3: {3: 0}}
1325
+ sage: g = DiGraph([(1,2,3),(2,3,2),(1,4,1),(4,2,1)], weighted=True)
1326
+ sage: floyd_warshall_shortest_paths(g, distances=False, predecessors=True)
1327
+ {1: {1: None, 2: 4, 3: 2, 4: 1},
1328
+ 2: {2: None, 3: 2},
1329
+ 3: {3: None},
1330
+ 4: {2: 4, 3: 2, 4: None}}
1331
+
1332
+ TESTS:
1333
+
1334
+ Given an input which is not a graph::
1335
+
1336
+ sage: floyd_warshall_shortest_paths("I am not a graph!")
1337
+ Traceback (most recent call last):
1338
+ ...
1339
+ TypeError: the input must be a Sage graph
1340
+
1341
+ If there is a negative cycle:
1342
+
1343
+ sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True)
1344
+ sage: floyd_warshall_shortest_paths(g)
1345
+ Traceback (most recent call last):
1346
+ ...
1347
+ ValueError: the graph contains a negative cycle
1348
+ """
1349
+ from sage.graphs.generic_graph import GenericGraph
1350
+ cdef dict dist = {}
1351
+ cdef dict pred = {}
1352
+
1353
+ if not isinstance(g, GenericGraph):
1354
+ raise TypeError("the input must be a Sage graph")
1355
+ elif not g.num_edges():
1356
+ dist = {v: {v: 0} for v in g}
1357
+ pred = {v: {v: None} for v in g}
1358
+ if distances and predecessors:
1359
+ return (dist, pred)
1360
+ if distances:
1361
+ return dist
1362
+ if predecessors:
1363
+ return pred
1364
+ # These variables are automatically deleted when the function terminates.
1365
+ cdef v_index i
1366
+ cdef list int_to_v = list(g)
1367
+ cdef dict v_to_int = {v: i for i, v in enumerate(int_to_v)}
1368
+ cdef BoostVecWeightedDiGraphU g_boost_dir
1369
+ cdef BoostVecWeightedGraph g_boost_und
1370
+ cdef int N = g.num_verts()
1371
+ cdef vector[vector[double]] result
1372
+
1373
+ if g.is_directed():
1374
+ boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
1375
+ sig_on()
1376
+ result = g_boost_dir.floyd_warshall_shortest_paths()
1377
+ sig_off()
1378
+ else:
1379
+ boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
1380
+ sig_on()
1381
+ result = g_boost_und.floyd_warshall_shortest_paths()
1382
+ sig_off()
1383
+
1384
+ if not result.size():
1385
+ raise ValueError("the graph contains a negative cycle")
1386
+
1387
+ if weight_function is not None:
1388
+ correct_type = type(weight_function(next(g.edge_iterator())))
1389
+ elif g.weighted():
1390
+ correct_type = type(next(g.edge_iterator())[2])
1391
+ else:
1392
+ correct_type = int
1393
+ # Needed for rational curves.
1394
+ try:
1395
+ from sage.rings.real_mpfr import RealNumber, RR
1396
+ except ImportError:
1397
+ pass
1398
+ else:
1399
+ if correct_type == RealNumber:
1400
+ correct_type = RR
1401
+
1402
+ import sys
1403
+ if distances:
1404
+ dist = {int_to_v[v]: {int_to_v[w]: correct_type(result[v][w])
1405
+ for w in range(N) if result[v][w] != sys.float_info.max}
1406
+ for v in range(N)}
1407
+
1408
+ if predecessors:
1409
+ if g.is_directed():
1410
+ pred = get_predecessors(g_boost_dir, result, int_to_v, True, correct_type)
1411
+ else:
1412
+ pred = get_predecessors(g_boost_und, result, int_to_v, False, correct_type)
1413
+
1414
+ if distances and predecessors:
1415
+ return (dist, pred)
1416
+ if distances:
1417
+ return dist
1418
+ if predecessors:
1419
+ return pred
1420
+
1421
+
1422
+ cpdef johnson_closeness_centrality(g, weight_function=None):
1423
+ r"""
1424
+ Use Johnson algorithm to compute the closeness centrality of all vertices.
1425
+
1426
+ This routine is preferable to :func:`~johnson_shortest_paths` because it
1427
+ does not create a doubly indexed dictionary of distances, saving memory.
1428
+
1429
+ The time-complexity is `O(mn\log n)`, where `n` is the number of nodes and
1430
+ `m` is the number of edges.
1431
+
1432
+ INPUT:
1433
+
1434
+ - ``g`` -- the input Sage graph
1435
+
1436
+ - ``weight_function`` -- function (default: ``None``); a function that
1437
+ associates a weight to each edge. If ``None`` (default), the weights of
1438
+ ``g`` are used, if ``g.weighted()==True``, otherwise all edges have
1439
+ weight 1.
1440
+
1441
+ OUTPUT: a dictionary associating each vertex ``v`` to its closeness centrality
1442
+
1443
+ EXAMPLES:
1444
+
1445
+ Undirected graphs::
1446
+
1447
+ sage: from sage.graphs.base.boost_graph import johnson_closeness_centrality
1448
+ sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
1449
+ sage: johnson_closeness_centrality(g)
1450
+ {0: 0.375, 1: 0.5, 2: 0.5, 3: 0.375}
1451
+
1452
+ Directed graphs::
1453
+
1454
+ sage: from sage.graphs.base.boost_graph import johnson_closeness_centrality
1455
+ sage: g = DiGraph([(0,1,1),(1,2,-2),(1,3,4),(2,3,1)], weighted=True)
1456
+ sage: johnson_closeness_centrality(g)
1457
+ {0: inf, 1: -0.4444444444444444, 2: 0.3333333333333333}
1458
+
1459
+ TESTS:
1460
+
1461
+ Given an input which is not a graph::
1462
+
1463
+ sage: from sage.graphs.base.boost_graph import johnson_closeness_centrality
1464
+ sage: johnson_closeness_centrality("I am not a graph!")
1465
+ Traceback (most recent call last):
1466
+ ...
1467
+ TypeError: the input must be a Sage graph
1468
+
1469
+ If there is a negative cycle::
1470
+
1471
+ sage: from sage.graphs.base.boost_graph import johnson_closeness_centrality
1472
+ sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True)
1473
+ sage: johnson_closeness_centrality(g)
1474
+ Traceback (most recent call last):
1475
+ ...
1476
+ ValueError: the graph contains a negative cycle
1477
+ """
1478
+ from sage.graphs.generic_graph import GenericGraph
1479
+
1480
+ if not isinstance(g, GenericGraph):
1481
+ raise TypeError("the input must be a Sage graph")
1482
+ elif not g.num_edges():
1483
+ return {}
1484
+ # These variables are automatically deleted when the function terminates.
1485
+ cdef BoostVecWeightedDiGraphU g_boost_dir
1486
+ cdef BoostVecWeightedGraph g_boost_und
1487
+ cdef int N = g.num_verts()
1488
+ cdef vector[vector[double]] result
1489
+ cdef vector[double] closeness
1490
+ cdef double farness
1491
+ cdef int i, j, reach
1492
+ cdef list int_to_v = list(g)
1493
+ cdef dict v_to_int = {v: i for i, v in enumerate(int_to_v)}
1494
+
1495
+ if g.is_directed():
1496
+ boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
1497
+ sig_on()
1498
+ result = g_boost_dir.johnson_shortest_paths()
1499
+ sig_off()
1500
+ else:
1501
+ boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
1502
+ sig_on()
1503
+ result = g_boost_und.johnson_shortest_paths()
1504
+ sig_off()
1505
+
1506
+ if not result.size():
1507
+ raise ValueError("the graph contains a negative cycle")
1508
+
1509
+ import sys
1510
+ for i in range(N):
1511
+ farness = 0
1512
+ reach = 0
1513
+ for j in range(N):
1514
+ if result[i][j] != sys.float_info.max:
1515
+ farness += result[i][j]
1516
+ reach += 1
1517
+ if reach > 1:
1518
+ closeness.push_back((<double>reach - 1) * (reach - 1) / ((N - 1) * farness))
1519
+ else:
1520
+ closeness.push_back(sys.float_info.max)
1521
+ sig_check()
1522
+ return {v: closeness[i] for i, v in enumerate(int_to_v) if closeness[i] != sys.float_info.max}
1523
+
1524
+
1525
+ cpdef min_cycle_basis(g_sage, weight_function=None, by_weight=False):
1526
+ r"""
1527
+ Return a minimum weight cycle basis of the input graph ``g_sage``.
1528
+
1529
+ A cycle basis is a list of cycles (list of vertices forming a cycle) of
1530
+ ``g_sage``. Note that the vertices are not necessarily returned in the order
1531
+ in which they appear in the cycle.
1532
+
1533
+ A minimum weight cycle basis is a cycle basis that minimizes the sum of the
1534
+ weights (length for unweighted graphs) of its cycles.
1535
+
1536
+ Not implemented for directed graphs and multigraphs.
1537
+
1538
+ INPUT:
1539
+
1540
+ - ``g_sage`` -- a Sage Graph
1541
+
1542
+ - ``weight_function`` -- function (default: ``None``); a function that takes
1543
+ as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``,
1544
+ ``by_weight`` is automatically set to ``True``. If ``None`` and
1545
+ ``by_weight`` is ``True``, the weights of ``g_sage`` are used, if
1546
+ ``g_sage.weighted()==True``, otherwise all edges have weight 1.
1547
+
1548
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, the edges in
1549
+ the graph are weighted, otherwise all edges have weight 1
1550
+
1551
+ EXAMPLES::
1552
+
1553
+ sage: g = Graph([(1, 2, 3), (2, 3, 5), (3, 4, 8), (4, 1, 13), (1, 3, 250), (5, 6, 9), (6, 7, 17), (7, 5, 20)])
1554
+ sage: sorted(g.minimum_cycle_basis(by_weight=True))
1555
+ [[1, 2, 3], [1, 2, 3, 4], [5, 6, 7]]
1556
+ sage: sorted(g.minimum_cycle_basis())
1557
+ [[1, 2, 3], [1, 3, 4], [5, 6, 7]]
1558
+
1559
+ .. SEEALSO::
1560
+
1561
+ * :wikipedia:`Cycle_basis`
1562
+ """
1563
+ cdef Py_ssize_t u_int, v_int, i, j
1564
+ cdef object u, v
1565
+ cdef Py_ssize_t n = g_sage.num_verts()
1566
+ cdef list int_to_vertex = list(g_sage)
1567
+ cdef dict vertex_to_int = {u: u_int for u_int, u in enumerate(int_to_vertex)}
1568
+ cdef list edgelist
1569
+ if by_weight and weight_function is not None:
1570
+ edgelist = [(vertex_to_int[e[0]], vertex_to_int[e[1]], float(weight_function(e)))
1571
+ for e in g_sage.edge_iterator()]
1572
+ if by_weight:
1573
+ edgelist = [(vertex_to_int[e[0]], vertex_to_int[e[1]], float(e[2]))
1574
+ for e in g_sage.edge_iterator()]
1575
+ else:
1576
+ edgelist = [(vertex_to_int[u], vertex_to_int[v], 1) for u, v in g_sage.edge_iterator(labels=False)]
1577
+
1578
+ # We just need the edges of any spanning tree here not necessarily a
1579
+ # minimum spanning tree.
1580
+
1581
+ cdef list sp_edges = min_spanning_tree(g_sage)
1582
+ cdef cset[pair[int, int]] edges_s
1583
+ for a, b, c in sp_edges:
1584
+ edges_s.insert((a, b))
1585
+ # Edges of self that are not in the spanning tree
1586
+ cdef list edges_c = [e for e in g_sage.edge_iterator(labels=False) if not edges_s.count(e)]
1587
+ cdef list edges_complement = [frozenset((vertex_to_int[u], vertex_to_int[v])) for u, v in edges_c]
1588
+ cdef Py_ssize_t lec = len(edges_complement)
1589
+ cdef list orth_set = [set([e]) for e in edges_complement]
1590
+ cdef list cycle_basis = []
1591
+ cdef set base
1592
+ cdef BoostVecWeightedGraph g_boost_und
1593
+ cdef vector[vector[double]] all_pair_shortest_pathlens
1594
+ cdef result_distances min_path
1595
+ cdef list min_path_nodes
1596
+ cdef dict cross_paths_lens
1597
+ cdef float edge_w
1598
+
1599
+ for i in range(lec):
1600
+ base = orth_set[i]
1601
+ # For each edge in g, add 2 edges to g_boost_und: "cross" edges if edge
1602
+ # is in base, otherwise "in-plane" edges
1603
+ g_boost_und = BoostVecWeightedGraph()
1604
+ for u_int in range(2 * n):
1605
+ g_boost_und.add_vertex()
1606
+ for u_int, v_int, edge_w in edgelist:
1607
+ # mapping the nodes in g from 0 to n-1
1608
+ if frozenset((u_int, v_int)) in base:
1609
+ g_boost_und.add_edge(u_int, n + v_int, edge_w)
1610
+ g_boost_und.add_edge(n + u_int, v_int, edge_w)
1611
+ else:
1612
+ g_boost_und.add_edge(u_int, v_int, edge_w)
1613
+ g_boost_und.add_edge(n + u_int, n + v_int, edge_w)
1614
+
1615
+ sig_on()
1616
+ all_pair_shortest_pathlens = g_boost_und.johnson_shortest_paths()
1617
+ sig_off()
1618
+ cross_paths_lens = {j: all_pair_shortest_pathlens[j][n + j] for j in range(n)}
1619
+ u_int = min(cross_paths_lens, key=cross_paths_lens.get)
1620
+ v_int = n + u_int
1621
+ sig_on()
1622
+ min_path = g_boost_und.dijkstra_shortest_paths(u_int)
1623
+ sig_off()
1624
+ # Mapping the nodes in G to nodes in g
1625
+ min_path_nodes = [v_int if v_int < n else v_int - n]
1626
+ while v_int != u_int:
1627
+ v_int = min_path.predecessors[v_int]
1628
+ min_path_nodes.append(v_int if v_int < n else v_int - n)
1629
+
1630
+ # removal of edges occurring even number of times
1631
+ edges = set()
1632
+ for edge in zip(min_path_nodes[:-1], min_path_nodes[1:]):
1633
+ edges ^= {edge}
1634
+ new_cycle = {frozenset(e) for e in edges}
1635
+ cycle_basis.append([int_to_vertex[u_int] for u_int in set().union(*new_cycle)])
1636
+ # updating orth_set so that i+1, i+2, ...th elements are orthogonal
1637
+ # to the newly found cycle
1638
+ for j in range(i + 1, lec):
1639
+ if len(orth_set[j] & new_cycle) % 2:
1640
+ orth_set[j] = orth_set[j] ^ base
1641
+ return cycle_basis
1642
+
1643
+
1644
+ cpdef eccentricity_DHV(g, vertex_list=None, weight_function=None, check_weight=True):
1645
+ r"""
1646
+ Return the vector of eccentricities using the algorithm of [Dragan2018]_.
1647
+
1648
+ The array returned is of length `n`, and by default its `i`-th component is
1649
+ the eccentricity of the `i`-th vertex in ``g.vertices(sort=True)``,
1650
+ if ``vertex_list is None``, otherwise ``ecc[i]`` is the eccentricity of
1651
+ vertex ``vertex_list[i]``.
1652
+
1653
+ The algorithm proposed in [Dragan2018]_ is based on the observation that for
1654
+ all nodes `v,w\in V`, we have `\max(ecc[v]-d(v,w), d(v,w))\leq ecc[w] \leq
1655
+ ecc[v] + d(v,w)`. Also the algorithm iteratively improves upper and lower
1656
+ bounds on the eccentricity of each vertex until no further improvements can
1657
+ be done.
1658
+
1659
+ INPUT:
1660
+
1661
+ - ``g`` -- the input Sage graph
1662
+
1663
+ - ``vertex_list`` -- list (default: ``None``); a list of `n` vertices
1664
+ specifying a mapping from `(0, \ldots, n-1)` to vertex labels in `g`. When
1665
+ set, ``ecc[i]`` is the eccentricity of vertex ``vertex_list[i]``. When
1666
+ ``vertex_list`` is ``None``, ``ecc[i]`` is the eccentricity of vertex
1667
+ ``g.vertices(sort=True)[i]``.
1668
+
1669
+ - ``weight_function`` -- function (default: ``None``); a function that
1670
+ associates a weight to each edge. If ``None`` (default), the weights of
1671
+ ``g`` are used, if ``g.weighted()==True``, otherwise all edges have
1672
+ weight 1.
1673
+
1674
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
1675
+ that the ``weight_function`` outputs a number for each edge
1676
+
1677
+ EXAMPLES::
1678
+
1679
+ sage: from sage.graphs.base.boost_graph import eccentricity_DHV
1680
+ sage: G = graphs.BullGraph()
1681
+ sage: eccentricity_DHV(G)
1682
+ [2.0, 2.0, 2.0, 3.0, 3.0]
1683
+
1684
+ TESTS:
1685
+
1686
+ sage: G = Graph(2)
1687
+ sage: eccentricity_DHV(G)
1688
+ [+Infinity, +Infinity]
1689
+ sage: G = graphs.RandomGNP(20, 0.7)
1690
+ sage: eccentricity_DHV(G) == G.eccentricity()
1691
+ True
1692
+ sage: G = Graph([(0,1,-1)], weighted=True)
1693
+ sage: eccentricity_DHV(G)
1694
+ Traceback (most recent call last):
1695
+ ...
1696
+ ValueError: graph contains negative edge weights, use Johnson_Boost instead
1697
+ """
1698
+ if g.is_directed():
1699
+ raise TypeError("the 'DHV' algorithm only works on undirected graphs")
1700
+
1701
+ cdef v_index n = g.order()
1702
+ if not n:
1703
+ return []
1704
+ if n == 1:
1705
+ return [0]
1706
+
1707
+ if weight_function and check_weight:
1708
+ g._check_weight_function(weight_function)
1709
+
1710
+ if weight_function is not None:
1711
+ for e in g.edge_iterator():
1712
+ if float(weight_function(e)) < 0:
1713
+ raise ValueError("graph contains negative edge weights, use Johnson_Boost instead")
1714
+ elif g.weighted():
1715
+ for _, _, w in g.edge_iterator():
1716
+ if w and float(w) < 0:
1717
+ raise ValueError("graph contains negative edge weights, use Johnson_Boost instead")
1718
+
1719
+ if vertex_list is None:
1720
+ vertex_list = g.vertices(sort=True)
1721
+ elif not len(vertex_list) == n or not set(vertex_list) == set(g):
1722
+ raise ValueError("parameter vertex_list is incorrect for this graph")
1723
+
1724
+ # These variables are automatically deleted when the function terminates.
1725
+ cdef dict v_to_int = {vv: vi for vi, vv in enumerate(vertex_list)}
1726
+ cdef BoostVecWeightedGraph g_boost
1727
+ boost_weighted_graph_from_sage_graph(&g_boost, g, v_to_int, weight_function)
1728
+
1729
+ import sys
1730
+ cdef v_index u, antipode, v
1731
+ cdef double ecc_u, ecc_antipode, tmp
1732
+ cdef v_index i, idx
1733
+
1734
+ cdef list active = list(range(n))
1735
+ cdef vector[double] ecc_lower_bound
1736
+ cdef vector[double] ecc_upper_bound
1737
+ cdef vector[double] distances
1738
+
1739
+ ecc_lower_bound.assign(n, 0)
1740
+ ecc_upper_bound.assign(n, sys.float_info.max)
1741
+
1742
+ # Algorithm
1743
+ while active:
1744
+ # Select vertex with minimum eccentricity in active and update
1745
+ # eccentricity upper bounds.
1746
+ # For this, we select u with minimum eccentricity lower bound in active
1747
+ # if ecc_u == ecc_lb[u], we are done. Otherwise, we update eccentricity
1748
+ # lower bounds and repeat
1749
+
1750
+ tmp = sys.float_info.max
1751
+ for i, v in enumerate(active):
1752
+ if ecc_lower_bound[v] < tmp:
1753
+ tmp = ecc_lower_bound[v]
1754
+ idx = i
1755
+ active[idx], active[-1] = active[-1], active[idx]
1756
+ u = active.pop()
1757
+
1758
+ # compute distances from u
1759
+ sig_on()
1760
+ distances = g_boost.dijkstra_shortest_paths(u).distances
1761
+ sig_off()
1762
+
1763
+ # Compute eccentricity of u
1764
+ ecc_u = 0
1765
+ for v in range(n):
1766
+ if ecc_u < distances[v]:
1767
+ ecc_u = distances[v]
1768
+ antipode = v
1769
+ ecc_upper_bound[u] = ecc_u
1770
+
1771
+ if ecc_u == sys.float_info.max: # Disconnected graph
1772
+ break
1773
+
1774
+ if ecc_u == ecc_lower_bound[u]:
1775
+ # We found the good vertex.
1776
+ # Update eccentricity upper bounds and remove from active those
1777
+ # vertices for which gap is closed
1778
+ i = 0
1779
+ while i < len(active):
1780
+ v = active[i]
1781
+ ecc_upper_bound[v] = min(ecc_upper_bound[v], distances[v] + ecc_u)
1782
+ if ecc_upper_bound[v] == ecc_lower_bound[v]:
1783
+ active[i] = active[-1]
1784
+ active.pop()
1785
+ else:
1786
+ i += 1
1787
+
1788
+ else:
1789
+ # u was not a good choice.
1790
+ # We use its antipode to update eccentricity lower bounds.
1791
+ # Observe that this antipode might have already been seen.
1792
+ for i, v in enumerate(active):
1793
+ if v == antipode:
1794
+ active[i] = active[-1]
1795
+ active.pop()
1796
+ break
1797
+
1798
+ # Compute distances from antipode
1799
+ sig_on()
1800
+ distances = g_boost.dijkstra_shortest_paths(antipode).distances
1801
+ sig_off()
1802
+
1803
+ # Compute eccentricity of antipode
1804
+ ecc_antipode = 0
1805
+ for v in range(n):
1806
+ ecc_antipode = max(ecc_antipode, distances[v])
1807
+ ecc_upper_bound[antipode] = ecc_antipode
1808
+
1809
+ # Update eccentricity lower bounds and remove from active those
1810
+ # vertices for which the gap is closed
1811
+ i = 0
1812
+ while i < len(active):
1813
+ v = active[i]
1814
+ ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v])
1815
+ if ecc_upper_bound[v] == ecc_lower_bound[v]:
1816
+ active[i] = active[-1]
1817
+ active.pop()
1818
+ else:
1819
+ i += 1
1820
+
1821
+ from sage.rings.infinity import Infinity
1822
+ cdef list eccentricity = []
1823
+ for i in range(n):
1824
+ if ecc_upper_bound[i] != sys.float_info.max:
1825
+ eccentricity.append(ecc_upper_bound[i])
1826
+ else:
1827
+ eccentricity.append(+Infinity)
1828
+
1829
+ return eccentricity
1830
+
1831
+
1832
+ cpdef radius_DHV(g, weight_function=None, check_weight=True):
1833
+ r"""
1834
+ Return the radius of weighted graph `g`.
1835
+
1836
+ This method computes the radius of undirected graph using the algorithm
1837
+ given in [Dragan2018]_.
1838
+
1839
+ This method returns Infinity if graph is not connected.
1840
+
1841
+ INPUT:
1842
+
1843
+ - ``g`` -- the input Sage graph
1844
+
1845
+ - ``weight_function`` -- function (default: ``None``); a function that
1846
+ associates a weight to each edge. If ``None`` (default), the weights of
1847
+ ``g`` are used, if ``g.weighted()==True``, otherwise all edges have
1848
+ weight 1.
1849
+
1850
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
1851
+ that the ``weight_function`` outputs a number for each edge
1852
+
1853
+ EXAMPLES::
1854
+
1855
+ sage: from sage.graphs.base.boost_graph import radius_DHV
1856
+ sage: G = Graph([(0,1,1), (1,2,1), (0,2,3)])
1857
+ sage: radius_DHV(G)
1858
+ 1.0
1859
+ sage: G = graphs.PathGraph(7)
1860
+ sage: radius_DHV(G) == G.radius(algorithm='Dijkstra_Boost')
1861
+ True
1862
+
1863
+ TESTS::
1864
+
1865
+ sage: G = Graph()
1866
+ sage: radius_DHV(G)
1867
+ 0
1868
+ sage: G = Graph(1)
1869
+ sage: radius_DHV(G)
1870
+ 0
1871
+ sage: G = Graph(2)
1872
+ sage: radius_DHV(G)
1873
+ +Infinity
1874
+ sage: G = Graph([(0, 1, 2)],weighted=True)
1875
+ sage: radius_DHV(G)
1876
+ 2.0
1877
+ sage: G = DiGraph(1)
1878
+ sage: radius_DHV(G)
1879
+ Traceback (most recent call last):
1880
+ ...
1881
+ TypeError: this method works for undirected graphs only
1882
+ """
1883
+ if g.is_directed():
1884
+ raise TypeError("this method works for undirected graphs only")
1885
+
1886
+ cdef int n = g.order()
1887
+ if n <= 1:
1888
+ return 0
1889
+
1890
+ if weight_function and check_weight:
1891
+ g._check_weight_function(weight_function)
1892
+
1893
+ if weight_function is not None:
1894
+ for e in g.edge_iterator():
1895
+ if float(weight_function(e)) < 0:
1896
+ raise ValueError("graphs contains negative weights, use Johnson_Boost instead")
1897
+ elif g.weighted():
1898
+ for _, _, w in g.edge_iterator():
1899
+ if w and float(w) < 0:
1900
+ raise ValueError("graphs contains negative weights, use Johnson_Boost instead")
1901
+
1902
+ # These variables are automatically deleted when the function terminates.
1903
+ cdef dict v_to_int = {vv: vi for vi, vv in enumerate(g)}
1904
+ cdef BoostVecWeightedGraph g_boost
1905
+ boost_weighted_graph_from_sage_graph(&g_boost, g, v_to_int, weight_function)
1906
+
1907
+ import sys
1908
+ cdef v_index source = 0
1909
+ cdef v_index antipode
1910
+ cdef v_index v
1911
+ cdef double ecc_source
1912
+ cdef double UB = sys.float_info.max
1913
+ cdef double LB = 0
1914
+ # For storing distances of all nodes from source
1915
+ cdef vector[double] distances
1916
+ # For storing lower bound on eccentricity of nodes
1917
+ cdef vector[double] ecc_lower_bound
1918
+
1919
+ # Initializing
1920
+ for i in range(n):
1921
+ ecc_lower_bound.push_back(0)
1922
+
1923
+ # Algorithm
1924
+ while LB < UB:
1925
+ # 1) pick vertex with minimum eccentricity lower bound
1926
+ # and compute its eccentricity
1927
+ sig_on()
1928
+ distances = g_boost.dijkstra_shortest_paths(source).distances
1929
+ sig_off()
1930
+
1931
+ # Determine the eccentricity of source and its antipode, that is a
1932
+ # vertex at largest distance from source
1933
+ ecc_source = 0
1934
+ for v in range(n):
1935
+ if ecc_source < distances[v]:
1936
+ ecc_source = distances[v]
1937
+ antipode = v
1938
+
1939
+ if ecc_source == sys.float_info.max: # Disconnected graph
1940
+ break
1941
+
1942
+ UB = min(UB, ecc_source) # minimum among exact computed eccentricities
1943
+ if ecc_source == ecc_lower_bound[source]:
1944
+ # we have found minimum eccentricity vertex and hence the radius
1945
+ break
1946
+
1947
+ # 2) Compute distances from antipode
1948
+ sig_on()
1949
+ distances = g_boost.dijkstra_shortest_paths(antipode).distances
1950
+ sig_off()
1951
+
1952
+ # 3) Use distances from antipode to improve eccentricity lower bounds.
1953
+ # We also determine the next source
1954
+ LB = sys.float_info.max
1955
+ for v in range(n):
1956
+ ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v])
1957
+ if LB > ecc_lower_bound[v]:
1958
+ LB = ecc_lower_bound[v]
1959
+ source = v # vertex with minimum eccentricity lower bound
1960
+
1961
+ if UB == sys.float_info.max:
1962
+ from sage.rings.infinity import Infinity
1963
+ return +Infinity
1964
+
1965
+ return UB
1966
+
1967
+
1968
+ cpdef diameter_DHV(g, weight_function=None, check_weight=True):
1969
+ r"""
1970
+ Return the diameter of weighted graph `g`.
1971
+
1972
+ This method computes the diameter of undirected graph using the
1973
+ algorithm proposed in [Dragan2018]_.
1974
+
1975
+ This method returns Infinity if graph is not connected.
1976
+
1977
+ INPUT:
1978
+
1979
+ - ``g`` -- the input Sage graph
1980
+
1981
+ - ``weight_function`` -- function (default: ``None``); a function that
1982
+ associates a weight to each edge. If ``None`` (default), the weights of
1983
+ ``g`` are used, if ``g.weighted()==True``, otherwise all edges have
1984
+ weight 1.
1985
+
1986
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
1987
+ that the ``weight_function`` outputs a number for each edge
1988
+
1989
+ EXAMPLES::
1990
+
1991
+ sage: from sage.graphs.base.boost_graph import diameter_DHV
1992
+ sage: G = graphs.ButterflyGraph()
1993
+ sage: diameter_DHV(G)
1994
+ 2.0
1995
+
1996
+ TESTS::
1997
+
1998
+ sage: G = graphs.RandomBarabasiAlbert(17,6) # needs networkx
1999
+ sage: diameter_DHV(G) == G.diameter(algorithm = 'Dijkstra_Boost') # needs networkx
2000
+ True
2001
+ sage: G = Graph([(0,1,-1)], weighted=True)
2002
+ sage: diameter_DHV(G)
2003
+ Traceback (most recent call last):
2004
+ ...
2005
+ ValueError: graph contains negative edge weights, use Johnson_Boost instead
2006
+ """
2007
+ if g.is_directed():
2008
+ raise TypeError("this method works for undirected graphs only")
2009
+
2010
+ cdef int n = g.order()
2011
+ if n <= 1:
2012
+ return 0
2013
+
2014
+ if weight_function and check_weight:
2015
+ g._check_weight_function(weight_function)
2016
+
2017
+ if weight_function is not None:
2018
+ for e in g.edges(sort=False):
2019
+ if float(weight_function(e)) < 0:
2020
+ raise ValueError("graph contains negative edge weights, use Johnson_Boost instead")
2021
+ elif g.weighted():
2022
+ for _, _, w in g.edges(sort=False):
2023
+ if w and float(w) < 0:
2024
+ raise ValueError("graph contains negative edge weights, use Johnson_Boost instead")
2025
+
2026
+ # These variables are automatically deleted when the function terminates.
2027
+ cdef dict v_to_int = {vv: vi for vi, vv in enumerate(g)}
2028
+ cdef BoostVecWeightedGraph g_boost
2029
+ boost_weighted_graph_from_sage_graph(&g_boost, g, v_to_int, weight_function)
2030
+
2031
+ import sys
2032
+ cdef v_index u, x, antipode
2033
+ cdef double ecc_u, ecc_x, ecc_antipode
2034
+ cdef double LB = 0
2035
+ cdef double UB = sys.float_info.max
2036
+ cdef v_index v
2037
+ cdef double tmp
2038
+ cdef size_t i, idx
2039
+
2040
+ cdef list active = list(range(n))
2041
+ cdef vector[double] ecc_lower_bound, ecc_upper_bound, distances
2042
+
2043
+ for i in range(n):
2044
+ ecc_lower_bound.push_back(0)
2045
+ ecc_upper_bound.push_back(sys.float_info.max)
2046
+
2047
+ # Algorithm
2048
+ while LB < UB and active:
2049
+ # 1. Select vertex u with maximum eccentricity upper bound
2050
+ tmp = 0
2051
+ for i, v in enumerate(active):
2052
+ if ecc_upper_bound[v] > tmp:
2053
+ tmp = ecc_upper_bound[v]
2054
+ idx = i
2055
+ active[idx], active[-1] = active[-1], active[idx]
2056
+ u = active.pop()
2057
+
2058
+ # Compute the distances from u
2059
+ sig_on()
2060
+ distances = g_boost.dijkstra_shortest_paths(u).distances
2061
+ sig_off()
2062
+
2063
+ # compute the eccentricity of u and update eccentricity lower bounds
2064
+ ecc_u = 0
2065
+ for v in range(n):
2066
+ ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v])
2067
+ ecc_u = max(ecc_u, distances[v])
2068
+
2069
+ LB = max(LB, ecc_u)
2070
+
2071
+ if LB == sys.float_info.max: # Disconnected graph
2072
+ break
2073
+
2074
+ # 2. Select x such that dist(u, x) + ecc[x] == ecc[u].
2075
+ # Since we don't know ecc[x], we select x with minimum eccentricity
2076
+ # lower bound. If ecc[x] == ecc_lb[x], we are done. Otherwise, we
2077
+ # update eccentricity lower bounds and repeat
2078
+ while active:
2079
+ # Select v with minimum eccentricity lower bound
2080
+ tmp = sys.float_info.max
2081
+ for i, v in enumerate(active):
2082
+ if ecc_lower_bound[v] < tmp:
2083
+ tmp = ecc_lower_bound[v]
2084
+ idx = i
2085
+ active[idx], active[-1] = active[-1], active[idx]
2086
+ x = active.pop()
2087
+
2088
+ # compute the distances from x
2089
+ sig_on()
2090
+ distances = g_boost.dijkstra_shortest_paths(x).distances
2091
+ sig_off()
2092
+
2093
+ # compute the eccentricity of x and its antipode
2094
+ ecc_x = 0
2095
+ for v in range(n):
2096
+ if distances[v] > ecc_x:
2097
+ ecc_x = distances[v]
2098
+ antipode = v
2099
+ LB = max(LB, ecc_x)
2100
+
2101
+ if ecc_x == ecc_lower_bound[x]:
2102
+ # We found the good vertex x
2103
+ # We update eccentricity upper bounds and break
2104
+ UB = ecc_x
2105
+ for v in active:
2106
+ ecc_upper_bound[v] = min(ecc_upper_bound[v], distances[v] + ecc_x)
2107
+ UB = max(UB, ecc_upper_bound[v])
2108
+ break
2109
+ else:
2110
+ # x was not a good choice
2111
+ # We use its antipode to update eccentricity lower bounds.
2112
+ # Observe that this antipode might have already been seen.
2113
+ for i, v in enumerate(active):
2114
+ if v == antipode:
2115
+ active[i] = active[-1]
2116
+ active.pop()
2117
+ break
2118
+
2119
+ # compute the distances from antipode
2120
+ sig_on()
2121
+ distances = g_boost.dijkstra_shortest_paths(antipode).distances
2122
+ sig_off()
2123
+
2124
+ # compute the eccentricity of antipode and update
2125
+ # eccentricity lower bounds
2126
+ ecc_antipode = 0
2127
+ for v in range(n):
2128
+ ecc_antipode = max(ecc_antipode, distances[v])
2129
+ ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v])
2130
+ LB = max(LB, ecc_antipode)
2131
+
2132
+ if LB == sys.float_info.max:
2133
+ from sage.rings.infinity import Infinity
2134
+ return +Infinity
2135
+
2136
+ return LB
2137
+
2138
+ cdef tuple diameter_lower_bound_2Dsweep(BoostVecWeightedDiGraphU g_boost,
2139
+ BoostVecWeightedDiGraphU rev_g_boost,
2140
+ v_index source,
2141
+ str algorithm):
2142
+ r"""
2143
+ Return a lower bound on the diameter of `G`.
2144
+
2145
+ This method implements the weighted version of the algorithm proposed
2146
+ in [Broder2000]_ to compute a lower bound on the diameter of the
2147
+ weighted digraph `G`.
2148
+
2149
+ If the digraph is not strongly connected, the returned value is infinity.
2150
+
2151
+ Firstly, this method computes forward distances from `source` and selects a
2152
+ vertex `vf` at maximum forward distance from `source` (i.e. an
2153
+ antipode). Then, it computes backward eccentricity of `vf`. Observe that the
2154
+ backward eccentricity of `vf` is at least the forward eccentricity of
2155
+ `source`.
2156
+
2157
+ Secondly, this method computes backward distances from `source` and selects
2158
+ a vertex `vb` at maximum backward distance from `source`. Then, it computes
2159
+ the forward eccentricity of `vb`, which is at least the backward
2160
+ eccentricity of `source`.
2161
+
2162
+ The lower bound on the diameter is the maximum among the backward
2163
+ eccentricity of `vf` and forward eccentricity of `vb`.
2164
+
2165
+ The method returns `(LB, s, m, d)`, where `LB` is best found lower bound on
2166
+ diameter, `s` is a vertex whose forward / backward eccentricity is `LB`, `d`
2167
+ is a vertex at a distance `LB` from / to `s` , and `m` is a vertex at
2168
+ distance `LB/2` from / to both `s` and `d`.
2169
+
2170
+ INPUT:
2171
+
2172
+ - ``g_boost`` -- a boost weighted digraph
2173
+
2174
+ - ``rev_g_boost`` -- a copy of ``g_boost`` with edges reversed
2175
+
2176
+ - ``source`` -- starting node for forward and backward distance computation
2177
+
2178
+ - ``algorithm`` -- string; algorithm for computing single source shortest
2179
+ distances. If ``g_boost`` contains negative edge weights then it will be
2180
+ ``Bellman-Ford``, otherwise it will be ``Dijkstra_Boost``.
2181
+
2182
+ TESTS::
2183
+
2184
+ sage: from sage.graphs.base.boost_graph import diameter
2185
+ sage: G = DiGraph()
2186
+ sage: diameter(DiGraph(), algorithm='2Dsweep')
2187
+ 0
2188
+ sage: diameter(DiGraph(1), algorithm='2Dsweep')
2189
+ 0
2190
+ sage: diameter(DiGraph(2), algorithm='2Dsweep')
2191
+ +Infinity
2192
+ """
2193
+ import sys
2194
+
2195
+ cdef int n = g_boost.num_verts()
2196
+
2197
+ if n <= 1:
2198
+ return (0, 0, 0, 0)
2199
+
2200
+ cdef v_index source_1, source_2, m, s, d, antipode_1, antipode_2, v
2201
+ cdef double LB_1, LB_2, LB, LB_m
2202
+ cdef result_distances result_1, result_2
2203
+ source_1 = source_2 = source
2204
+
2205
+ # Algorithm
2206
+
2207
+ # 1) Compute forward distances from source_1.
2208
+ # Get forward eccentricity and antipode (vertex at maximum forward distance)
2209
+ if algorithm == 'Bellman-Ford':
2210
+ sig_on()
2211
+ result_1 = g_boost.bellman_ford_shortest_paths(source_1)
2212
+ sig_off()
2213
+ else:
2214
+ sig_on()
2215
+ result_1 = g_boost.dijkstra_shortest_paths(source_1)
2216
+ sig_off()
2217
+ if not result_1.distances.size():
2218
+ raise ValueError("the graph contains a negative cycle")
2219
+
2220
+ LB_1 = -sys.float_info.max
2221
+ for v in range(n):
2222
+ if result_1.distances[v] > LB_1:
2223
+ LB_1 = result_1.distances[v]
2224
+ antipode_1 = v
2225
+
2226
+ if LB_1 == sys.float_info.max:
2227
+ return (LB_1, 0, 0, 0)
2228
+
2229
+ # 2) Compute backward distances from antipode_1.
2230
+ source_1, antipode_1 = antipode_1, source_1
2231
+ if algorithm == 'Bellman-Ford':
2232
+ sig_on()
2233
+ result_1 = rev_g_boost.bellman_ford_shortest_paths(source_1)
2234
+ sig_off()
2235
+ else:
2236
+ sig_on()
2237
+ result_1 = rev_g_boost.dijkstra_shortest_paths(source_1)
2238
+ sig_off()
2239
+ if not result_1.distances.size():
2240
+ raise ValueError("the graph contains a negative cycle")
2241
+
2242
+ for v in range(n):
2243
+ if result_1.distances[v] > LB_1:
2244
+ LB_1 = result_1.distances[v]
2245
+ antipode_1 = v
2246
+
2247
+ if LB_1 == sys.float_info.max:
2248
+ return (LB_1, 0, 0, 0)
2249
+
2250
+ # 3) Compute backward distances from source_2.
2251
+ # Get backward eccentricity and antipode.
2252
+ if algorithm == 'Bellman-Ford':
2253
+ sig_on()
2254
+ result_2 = rev_g_boost.bellman_ford_shortest_paths(source_2)
2255
+ sig_off()
2256
+ else:
2257
+ sig_on()
2258
+ result_2 = rev_g_boost.dijkstra_shortest_paths(source_2)
2259
+ sig_off()
2260
+ if not result_2.distances.size():
2261
+ raise ValueError("the graph contains a negative cycle")
2262
+
2263
+ LB_2 = -sys.float_info.max
2264
+ for v in range(n):
2265
+ if result_2.distances[v] > LB_2:
2266
+ LB_2 = result_2.distances[v]
2267
+ antipode_2 = v
2268
+
2269
+ if LB_2 == sys.float_info.max:
2270
+ return (LB_2, 0, 0, 0)
2271
+
2272
+ # 4) Compute forward distances from antipode_2.
2273
+ source_2, antipode_2 = antipode_2, source_2
2274
+ if algorithm == 'Bellman-Ford':
2275
+ sig_on()
2276
+ result_2 = g_boost.bellman_ford_shortest_paths(source_2)
2277
+ sig_off()
2278
+ else:
2279
+ sig_on()
2280
+ result_2 = g_boost.dijkstra_shortest_paths(source_2)
2281
+ sig_off()
2282
+ if not result_2.distances.size():
2283
+ raise ValueError("the graph contains a negative cycle")
2284
+
2285
+ for v in range(n):
2286
+ if result_2.distances[v] > LB_2:
2287
+ LB_2 = result_2.distances[v]
2288
+ antipode_2 = v
2289
+
2290
+ if LB_2 == sys.float_info.max:
2291
+ return (LB_2, 0, 0, 0)
2292
+
2293
+ # 5) Select the best found lower bound as LB with corresponding source s and
2294
+ # antipode d. Then find a vertex m at a distance LB/2 from/to both s and d.
2295
+ if LB_1 < LB_2:
2296
+ LB = LB_2
2297
+ s = source_2
2298
+ d = antipode_2
2299
+ LB_m = LB_2 / 2
2300
+ m = d
2301
+ while result_2.distances[m] > LB_m:
2302
+ m = result_2.predecessors[m]
2303
+ else:
2304
+ LB = LB_1
2305
+ s = source_1
2306
+ d = antipode_1
2307
+ LB_m = LB_1 / 2
2308
+ m = d
2309
+ while result_1.distances[m] > LB_m:
2310
+ m = result_1.predecessors[m]
2311
+
2312
+ return (LB, s, m, d)
2313
+
2314
+
2315
+ cdef double diameter_DiFUB(BoostVecWeightedDiGraphU g_boost,
2316
+ BoostVecWeightedDiGraphU rev_g_boost,
2317
+ v_index source,
2318
+ str algorithm) except? -1:
2319
+ r"""
2320
+ Return the diameter of a weighted directed graph.
2321
+
2322
+ The ``DiFUB`` (Directed iterative Fringe Upper Bound) algorithm calculates
2323
+ the exact value of the diameter of a weighted directed graph [CGLM2012]_.
2324
+
2325
+ This algorithm starts from a vertex found through a 2Dsweep call (a directed
2326
+ version of the 2sweep method). The worst case time complexity of the DiFUB
2327
+ algorithm is `O(nm)`, but it can be very fast in practice. See the code's
2328
+ documentation and [CGLM2012]_ for more details.
2329
+
2330
+ If the digraph is not strongly connected, the returned value is infinity.
2331
+
2332
+ INPUT:
2333
+
2334
+ - ``g_boost`` -- a boost weighted digraph
2335
+
2336
+ - ``rev_g_boost`` -- a copy of ``g_boost`` with edges reversed
2337
+
2338
+ - ``source`` -- starting node for forward and backward distance computation
2339
+
2340
+ - ``algorithm`` -- string; algorithm for computing single source shortest
2341
+ distances. If ``g_boost`` contains negative edge weights then it will be
2342
+ ``Bellman-Ford``, otherwise it will be ``Dijkstra_Boost``.
2343
+
2344
+ TESTS::
2345
+
2346
+ sage: from sage.graphs.base.boost_graph import diameter
2347
+ sage: G = DiGraph()
2348
+ sage: diameter(DiGraph(), algorithm='DiFUB')
2349
+ 0
2350
+ sage: diameter(DiGraph(1), algorithm='DiFUB')
2351
+ 0
2352
+ sage: diameter(DiGraph(2), algorithm='DiFUB')
2353
+ +Infinity
2354
+ """
2355
+ cdef v_index n = g_boost.num_verts()
2356
+ if n <= 1:
2357
+ return 0
2358
+
2359
+ import sys
2360
+ # These variables are automatically deleted when the function terminates.
2361
+ cdef double LB, LB_1, LB_2, UB
2362
+ cdef v_index m, v, tmp
2363
+ cdef v_index i
2364
+ cdef vector[double] distances
2365
+ cdef vector[pair[double, v_index]] order_1, order_2
2366
+
2367
+ # We select a vertex with low eccentricity using 2Dsweep
2368
+ LB, _, m, _ = diameter_lower_bound_2Dsweep(g_boost, rev_g_boost,
2369
+ source, algorithm)
2370
+
2371
+ # If the lower bound is a very large number, it means that the digraph is
2372
+ # not strongly connected and so the diameter is infinite.
2373
+ if LB == sys.float_info.max:
2374
+ return LB
2375
+
2376
+ # Compute Forward distances from `m`.
2377
+ if algorithm == 'Bellman-Ford':
2378
+ sig_on()
2379
+ distances = g_boost.bellman_ford_shortest_paths(m).distances
2380
+ sig_off()
2381
+ else:
2382
+ sig_on()
2383
+ distances = g_boost.dijkstra_shortest_paths(m).distances
2384
+ sig_off()
2385
+ if not distances.size():
2386
+ raise ValueError("the graph contains a negative cycle")
2387
+
2388
+ # Obtain Forward eccentricity of `m` and store pair of
2389
+ # forward distances, vertex in order_1
2390
+ LB_1 = sys.float_info.min
2391
+ for v in range(n):
2392
+ LB_1 = max(LB_1, distances[v])
2393
+ order_1.push_back(pair[double, v_index](distances[v], v))
2394
+ # Compute Backward distances from `m`.
2395
+ if algorithm == 'Bellman-Ford':
2396
+ sig_on()
2397
+ distances = rev_g_boost.bellman_ford_shortest_paths(m).distances
2398
+ sig_off()
2399
+ else:
2400
+ sig_on()
2401
+ distances = rev_g_boost.dijkstra_shortest_paths(m).distances
2402
+ sig_off()
2403
+ if not distances.size():
2404
+ raise ValueError("the graph contains a negative cycle")
2405
+
2406
+ # Obtain Backward eccentricity of `m` and store pair of
2407
+ # backward distances, vertex in order_2.
2408
+ LB_2 = sys.float_info.min
2409
+ for v in range(n):
2410
+ LB_2 = max(LB_2, distances[v])
2411
+ order_2.push_back(pair[double, v_index](distances[v], v))
2412
+
2413
+ # Now sort order_1 / order_2 in decreasing order of forward / backward
2414
+ # distances respectively.
2415
+ # Now order_1 and order_2 will contain order of vertices in which
2416
+ # further distance computations will be done.
2417
+ order_1 = sorted(order_1, reverse=True)
2418
+ order_2 = sorted(order_2, reverse=True)
2419
+
2420
+ LB = max(LB, LB_1, LB_2)
2421
+ if LB == sys.float_info.max:
2422
+ return LB
2423
+
2424
+ # The algorithm:
2425
+ #
2426
+ # The diameter of the digraph is equal to the maximum forward or backward
2427
+ # eccentricity of a vertex. Let `\[db_1, db_2,..., db_i\]` represents the
2428
+ # different backward distances from `m` containing at least one vertex at
2429
+ # that distance. Similarly, let `\[df_1, df_2,..., df_i\]` represents the
2430
+ # different forward distances from `m` containing at least one vertex at
2431
+ # that distance.
2432
+ #
2433
+ # The algorithm is based on the following two observations:
2434
+ #
2435
+ # 1). All the nodes `x` at a backward distance greater than `\[db_i\]` from
2436
+ # `m` having forward eccentricity greater than `\[2db_{i-1}\]` have a
2437
+ # corresponding node `y` whose backward eccentricity is greater than or
2438
+ # equal to the forward eccentricity of `x`, at a forward distance greater
2439
+ # than `\[db_i\]` from `m`.
2440
+ #
2441
+ # 2). All the nodes `x` at a forward distance greater than `\[df_i\]` from
2442
+ # `m` having backward eccentricity greater than `\[2df_{i-1}\]` have a
2443
+ # corresponding node `y` whose forward eccentricity is greater than or equal
2444
+ # to the backward eccentricity of `x`, at a backward distance greater than
2445
+ # `\[df_i\]` from `m`.
2446
+ #
2447
+ # Therefore, we calculate backward / forward eccentricity of all nodes at
2448
+ # forward / backward distance `\[df_i / db_i\]` from `m` respectively. And
2449
+ # their maximum is `LB`. If `LB` is greater than `2(next maximum forward /
2450
+ # backward distance)` then we are done, else we proceed further.
2451
+
2452
+ i = 0
2453
+ UB = max(2 * order_1[i].first, 2 * order_2[i].first)
2454
+
2455
+ while LB < UB:
2456
+ v = order_1[i].second
2457
+ if algorithm == 'Bellman-Ford':
2458
+ sig_on()
2459
+ distances = rev_g_boost.bellman_ford_shortest_paths(v).distances
2460
+ sig_off()
2461
+ else:
2462
+ sig_on()
2463
+ distances = rev_g_boost.dijkstra_shortest_paths(v).distances
2464
+ sig_off()
2465
+ if not distances.size():
2466
+ raise ValueError("the graph contains a negative cycle")
2467
+
2468
+ LB_1 = sys.float_info.min
2469
+ for tmp in range(n):
2470
+ LB_1 = max(LB_1, distances[tmp])
2471
+
2472
+ v = order_2[i].second
2473
+ if algorithm == 'Bellman-Ford':
2474
+ sig_on()
2475
+ distances = g_boost.bellman_ford_shortest_paths(v).distances
2476
+ sig_off()
2477
+ else:
2478
+ sig_on()
2479
+ distances = g_boost.dijkstra_shortest_paths(v).distances
2480
+ sig_off()
2481
+ if not distances.size():
2482
+ raise ValueError("the graph contains a negative cycle")
2483
+
2484
+ LB_2 = sys.float_info.min
2485
+ for tmp in range(n):
2486
+ LB_2 = max(LB_2, distances[tmp])
2487
+
2488
+ # Update the lower bound
2489
+ LB = max(LB, LB_1, LB_2)
2490
+ i += 1
2491
+
2492
+ if LB == sys.float_info.max or i == n:
2493
+ break
2494
+
2495
+ # next maximum forward / backward distance
2496
+ UB = max(2 * order_1[i].first, 2 * order_2[i].first)
2497
+
2498
+ # Finally return the computed diameter
2499
+ return LB
2500
+
2501
+ cpdef diameter(G, algorithm=None, source=None,
2502
+ weight_function=None, check_weight=True):
2503
+ r"""
2504
+ Return the diameter of `G`.
2505
+
2506
+ This method returns Infinity if the digraph is not strongly connected. It
2507
+ can also quickly return a lower bound on the diameter using the ``2Dsweep``
2508
+ scheme.
2509
+
2510
+ INPUT:
2511
+
2512
+ - ``G`` -- the input sage digraph
2513
+
2514
+ - ``algorithm`` -- string (default: ``None``); specifies the algorithm to
2515
+ use among:
2516
+
2517
+ - ``'2Dsweep'`` -- computes lower bound on the diameter of a weighted
2518
+ directed graph using the weighted version of the algorithm proposed in
2519
+ [Broder2000]_. See the code's documentation for more details.
2520
+
2521
+ - ``'DiFUB'`` -- computes the diameter of a weighted directed graph
2522
+ using the weighted version of the algorithm proposed in [CGLM2012]_.
2523
+ See the code's documentation for more details.
2524
+
2525
+ - ``source`` -- (default: ``None``) vertex from which to start the
2526
+ computation. If ``source==None``, an arbitrary vertex of the graph is
2527
+ chosen. Raise an error if the initial vertex is not in `G`.
2528
+
2529
+ - ``weight_function`` -- function (default: ``None``); a function that
2530
+ associates a weight to each edge. If ``None`` (default), the weights of
2531
+ ``G`` are used, if ``G.weighted()==True``, otherwise all edges have
2532
+ weight 1.
2533
+
2534
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
2535
+ that the ``weight_function`` outputs a number for each edge
2536
+
2537
+ EXAMPLES::
2538
+
2539
+ sage: from sage.graphs.base.boost_graph import diameter
2540
+ sage: G = DiGraph([(0, 1, 2), (1, 0, -1)])
2541
+ sage: diameter(G, algorithm='DiFUB')
2542
+ 1.0
2543
+ sage: diameter(G, algorithm='DiFUB', weight_function=lambda e:e[2])
2544
+ 2.0
2545
+ sage: G = DiGraph([(0, 1, -1), (1, 0, 2)])
2546
+ sage: diameter(G, algorithm='DiFUB', weight_function=lambda e:e[2])
2547
+ 2.0
2548
+
2549
+ TESTS:
2550
+
2551
+ Diameter of weakly connected digraph is Infinity::
2552
+
2553
+ sage: G = DiGraph(2)
2554
+ sage: diameter(G, algorithm='DiFUB')
2555
+ +Infinity
2556
+ sage: diameter(G, algorithm='2Dsweep')
2557
+ +Infinity
2558
+
2559
+ DiGraph containing negative cycle::
2560
+
2561
+ sage: G = DiGraph([(0,1,-2), (1,0,1)])
2562
+ sage: diameter(G, algorithm='2Dsweep', weight_function=lambda e:e[2])
2563
+ Traceback (most recent call last):
2564
+ ...
2565
+ ValueError: the graph contains a negative cycle
2566
+ sage: diameter(G, algorithm='DiFUB', weight_function=lambda e:e[2])
2567
+ Traceback (most recent call last):
2568
+ ...
2569
+ ValueError: the graph contains a negative cycle
2570
+ """
2571
+ import sys
2572
+
2573
+ if not G.is_directed():
2574
+ raise TypeError("this method works only for digraphs")
2575
+
2576
+ cdef int n = G.order()
2577
+
2578
+ if n <= 1:
2579
+ return 0
2580
+
2581
+ if weight_function and check_weight:
2582
+ G._check_weight_function(weight_function)
2583
+
2584
+ # Algorithm for single source shortest distance computations.
2585
+ cdef str algo = 'Dijkstra_Boost'
2586
+
2587
+ # If digraph contains negative edge weight then
2588
+ # algo is set to `Bellman-Ford`
2589
+ if weight_function is not None:
2590
+ for e in G.edges(sort=False):
2591
+ if float(weight_function(e)) < 0:
2592
+ algo = 'Bellman-Ford'
2593
+ break
2594
+ elif G.weighted():
2595
+ for _, _, w in G.edges(sort=False):
2596
+ if w and float(w) < 0:
2597
+ algo = 'Bellman-Ford'
2598
+ break
2599
+
2600
+ if algorithm is None: # default algorithm for diameter computation
2601
+ algorithm = 'DiFUB'
2602
+
2603
+ if algorithm not in ['2Dsweep', 'DiFUB']:
2604
+ raise ValueError("unknown algorithm for computing the diameter of directed graph")
2605
+
2606
+ if source is None:
2607
+ source = next(G.vertex_iterator())
2608
+ elif not G.has_vertex(source):
2609
+ raise ValueError("the specified source is not a vertex of the input Graph")
2610
+
2611
+ # These variables are automatically deleted when the function terminates.
2612
+ cdef dict v_to_int = {vv: vi for vi, vv in enumerate(G)}
2613
+
2614
+ # boost copy of G
2615
+ cdef BoostVecWeightedDiGraphU g_boost
2616
+ # boost copy of G with edges reversed
2617
+ cdef BoostVecWeightedDiGraphU rev_g_boost
2618
+
2619
+ # Initializing
2620
+ boost_weighted_graph_from_sage_graph(&g_boost, G, v_to_int, weight_function)
2621
+ boost_weighted_graph_from_sage_graph(&rev_g_boost, G, v_to_int, weight_function, reverse=True)
2622
+
2623
+ cdef v_index isource = 0 if source is None else v_to_int[source]
2624
+ cdef double LB
2625
+
2626
+ if algorithm == '2Dsweep':
2627
+ LB = diameter_lower_bound_2Dsweep(g_boost, rev_g_boost, isource, algo)[0]
2628
+ else:
2629
+ LB = diameter_DiFUB(g_boost, rev_g_boost, isource, algo)
2630
+
2631
+ if LB == sys.float_info.max:
2632
+ from sage.rings.infinity import Infinity
2633
+ return +Infinity
2634
+ else:
2635
+ return LB
2636
+
2637
+ cpdef shortest_paths_from_vertices(g, vertex_list=None, order=None,
2638
+ weight_function=None, algorithm=None):
2639
+ r"""
2640
+ Compute the shortest paths to all vertices from each vertex in
2641
+ ``vertex_list``.
2642
+
2643
+ The input graph can be weighted: if the algorithm is Dijkstra, no negative
2644
+ weights are allowed, while if the algorithm is Bellman-Ford, negative
2645
+ weights are allowed, but there must be no negative cycle (otherwise, the
2646
+ shortest paths might not exist).
2647
+
2648
+ However, Dijkstra algorithm is more efficient: for this reason, we suggest
2649
+ to use Bellman-Ford only if necessary (which is also the default option).
2650
+
2651
+ The running-time for each vertex is `O(n \log n+m)` for Dijkstra algorithm
2652
+ and `O(mn)` for Bellman-Ford algorithm, where `n` is the number of nodes and
2653
+ `m` is the number of edges.
2654
+
2655
+ INPUT:
2656
+
2657
+ - ``g`` -- the input Sage graph
2658
+
2659
+ - ``vertex_list`` -- list (default: ``None``); list of vertices to compute
2660
+ shortest paths from. By default (``None``), compute shortest paths from
2661
+ all vertices.
2662
+
2663
+ - ``order`` -- list (default: ``None``); order of vertices of `g`
2664
+
2665
+ - ``weight_function`` -- function (default: ``None``); a function that
2666
+ associates a weight to each edge. If ``None`` (default), the weights of
2667
+ ``g`` are used, if ``g.weighted()==True``, otherwise all edges have
2668
+ weight 1.
2669
+
2670
+ - ``algorithm`` -- string (default: ``None``); one of the following
2671
+ algorithms:
2672
+
2673
+ - ``'Dijkstra'``, ``'Dijkstra_Boost'`` -- the Dijkstra algorithm
2674
+ implemented in Boost (works only with positive weights)
2675
+
2676
+ - ``'Bellman-Ford'``, ``'Bellman-Ford_Boost'`` -- the Bellman-Ford
2677
+ algorithm implemented in Boost (works also with negative weights,
2678
+ if there is no negative cycle)
2679
+
2680
+ OUTPUT:
2681
+
2682
+ The type of output depends on the input. More precisely -
2683
+
2684
+ - A pair of dictionaries of list ``(distances, predecessors)``, when
2685
+ ``order is not None``, such that for each vertex ``v`` in ``vertex_list``,
2686
+ ``distances[v][i]`` store the shortest distance between ``v`` and
2687
+ ``order[i]`` and ``predecessors[v][i]`` store the last vertex in the
2688
+ shortest path from ``v`` to ``order[i]``.
2689
+
2690
+ - A pair of dictionaries of dictionaries ``(distances, predecessors)`` such
2691
+ that for each vertex ``v`` in ``vertex_list``, ``distances[v]`` store the
2692
+ shortest distances of all the other vertices from ``v``,
2693
+ ``predecessors[v]`` store the last vertices in the shortest path from
2694
+ ``v`` to all the other vertices.
2695
+
2696
+ EXAMPLES:
2697
+
2698
+ Undirected graphs::
2699
+
2700
+ sage: from sage.graphs.base.boost_graph import shortest_paths_from_vertices
2701
+ sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
2702
+ sage: shortest_paths_from_vertices(g,[1,2])
2703
+ ({1: {0: 1.0, 1: 0.0, 2: 2.0, 3: 3.0}, 2: {0: 3.0, 1: 2.0, 2: 0.0, 3: 1.0}},
2704
+ {1: {0: 1, 1: None, 2: 1, 3: 2}, 2: {0: 1, 1: 2, 2: None, 3: 2}})
2705
+
2706
+ Directed graphs::
2707
+
2708
+ sage: g = DiGraph([(0,1,1),(1,2,-1),(2,0,2),(2,3,1)], weighted=True)
2709
+ sage: shortest_paths_from_vertices(g,1)
2710
+ ({1: {0: 1.0, 1: 0.0, 2: -1.0, 3: 0.0}}, {1: {0: 2, 1: None, 2: 1, 3: 2}})
2711
+ sage: shortest_paths_from_vertices(g, 1, [0,1,2,3])
2712
+ ({1: [1.0, 0.0, -1.0, 0.0]}, {1: [2, None, 1, 2]})
2713
+
2714
+ TESTS:
2715
+
2716
+ Given an input which is not a graph::
2717
+
2718
+ sage: shortest_paths_from_vertices("X-AE A-12", 1)
2719
+ Traceback (most recent call last):
2720
+ ...
2721
+ TypeError: the input must be a Sage graph
2722
+
2723
+ If there is a negative cycle::
2724
+
2725
+ sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True)
2726
+ sage: shortest_paths_from_vertices(g, 1)
2727
+ Traceback (most recent call last):
2728
+ ...
2729
+ ValueError: the graph contains a negative cycle
2730
+
2731
+ If the given ordering is not valid::
2732
+
2733
+ sage: g = DiGraph([(0,1,1),(1,2,2),(2,0,0.5),(2,3,1)], weighted=True)
2734
+ sage: shortest_paths_from_vertices(g,1,[0,1])
2735
+ Traceback (most recent call last):
2736
+ ...
2737
+ ValueError: Given ordering is not valid
2738
+
2739
+ If Dijkstra is used with negative weights::
2740
+
2741
+ sage: g = Graph([(0,1,1),(1,2,-2),(1,3,4)], weighted=True)
2742
+ sage: shortest_paths_from_vertices(g, 1, algorithm='Dijkstra')
2743
+ Traceback (most recent call last):
2744
+ ...
2745
+ RuntimeError: Dijkstra algorithm does not work with negative weights, use Bellman-Ford instead
2746
+
2747
+ Wrong starting vertex::
2748
+
2749
+ sage: shortest_paths_from_vertices(g, 55)
2750
+ Traceback (most recent call last):
2751
+ ...
2752
+ ValueError: the starting vertex 55 is not in the graph
2753
+ """
2754
+ import sys
2755
+ from sage.rings.infinity import Infinity
2756
+ from sage.graphs.generic_graph import GenericGraph
2757
+
2758
+ if not isinstance(g, GenericGraph):
2759
+ raise TypeError("the input must be a Sage graph")
2760
+
2761
+ if vertex_list is None:
2762
+ vertex_list = g
2763
+
2764
+ else:
2765
+ if not isinstance(vertex_list, list):
2766
+ vertex_list = [vertex_list]
2767
+
2768
+ for vertex in vertex_list:
2769
+ if vertex not in g:
2770
+ raise ValueError(f"the starting vertex {vertex} is not in the graph")
2771
+
2772
+ if order is not None:
2773
+ if len(g) == len(order):
2774
+ for vertex in order:
2775
+ if vertex not in g:
2776
+ raise ValueError("Given ordering is not valid")
2777
+ else:
2778
+ raise ValueError("Given ordering is not valid")
2779
+
2780
+ cdef bint use_Bellman_Ford = algorithm in ['Bellman-Ford', 'Bellman-Ford_Boost']
2781
+ if not use_Bellman_Ford:
2782
+ # Check if there are edges with negative weights
2783
+ if weight_function is not None:
2784
+ for e in g.edges(sort=False):
2785
+ if float(weight_function(e)) < 0:
2786
+ use_Bellman_Ford = True
2787
+ break
2788
+ elif g.weighted():
2789
+ for _, _, wt in g.edges(sort=False):
2790
+ if float(wt) < 0:
2791
+ use_Bellman_Ford = True
2792
+ break
2793
+
2794
+ if algorithm in ['Dijkstra', 'Dijkstra_Boost']:
2795
+ if use_Bellman_Ford:
2796
+ raise RuntimeError("Dijkstra algorithm does not work with "
2797
+ "negative weights, use Bellman-Ford instead")
2798
+ elif algorithm is not None:
2799
+ raise ValueError(f"unknown algorithm {algorithm!r}")
2800
+
2801
+ # These variables are automatically deleted when the function terminates.
2802
+ cdef v_index vi, v, vert, pred, w
2803
+ cdef list int_to_v = list(g)
2804
+ cdef dict v_to_int = {vv: vi for vi, vv in enumerate(g)}
2805
+ cdef result_distances result
2806
+ cdef BoostVecWeightedDiGraphU g_boost_dir
2807
+ cdef BoostVecWeightedGraph g_boost_und
2808
+ cdef dict dist_v_dict, pred_v_dict
2809
+ cdef list dist_v_list, pred_v_list
2810
+
2811
+ if g.is_directed():
2812
+ boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
2813
+ else:
2814
+ boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
2815
+
2816
+ distances = {}
2817
+ predecessors = {}
2818
+
2819
+ for v in vertex_list:
2820
+ vi = v_to_int[v]
2821
+ if use_Bellman_Ford:
2822
+ if g.is_directed():
2823
+ sig_on()
2824
+ result = g_boost_dir.bellman_ford_shortest_paths(vi)
2825
+ sig_off()
2826
+ else:
2827
+ sig_on()
2828
+ result = g_boost_und.bellman_ford_shortest_paths(vi)
2829
+ sig_off()
2830
+ if not result.distances.size():
2831
+ raise ValueError("the graph contains a negative cycle")
2832
+ else:
2833
+ if g.is_directed():
2834
+ sig_on()
2835
+ result = g_boost_dir.dijkstra_shortest_paths(vi)
2836
+ sig_off()
2837
+ else:
2838
+ sig_on()
2839
+ result = g_boost_und.dijkstra_shortest_paths(vi)
2840
+ sig_off()
2841
+ if not result.distances.size():
2842
+ # This situation should never happen
2843
+ raise RuntimeError("something goes wrong. Please report the "
2844
+ "bug on sage-devel@googlegroups.com")
2845
+
2846
+ if order is None:
2847
+ dist_v_dict = {}
2848
+ pred_v_dict = {}
2849
+
2850
+ for vert in range(g.num_verts()):
2851
+ if result.distances[vert] != sys.float_info.max:
2852
+ w = int_to_v[vert]
2853
+ dist_v_dict[w] = result.distances[vert]
2854
+ pred = result.predecessors[vert]
2855
+ if pred == vert:
2856
+ pred_v_dict[w] = None
2857
+ else:
2858
+ pred_v_dict[w] = int_to_v[pred]
2859
+
2860
+ distances[v] = dist_v_dict
2861
+ predecessors[v] = pred_v_dict
2862
+ else:
2863
+ dist_v_list = []
2864
+ pred_v_list = []
2865
+
2866
+ for w in order:
2867
+ vert = v_to_int[w]
2868
+ if result.distances[vert] != sys.float_info.max:
2869
+ dist_v_list.append(result.distances[vert])
2870
+ pred = result.predecessors[vert]
2871
+ if pred == vert:
2872
+ pred_v_list.append(None)
2873
+ else:
2874
+ pred_v_list.append(int_to_v[pred])
2875
+
2876
+ distances[v] = dist_v_list
2877
+ predecessors[v] = pred_v_list
2878
+
2879
+ return distances, predecessors
2880
+
2881
+ cpdef wiener_index(g, algorithm=None, weight_function=None, check_weight=True):
2882
+ r"""
2883
+ Return the Wiener index of the graph.
2884
+
2885
+ The Wiener index of an undirected graph `G` is defined as
2886
+ `W(G) = \frac{1}{2} \sum_{u,v\in G} d(u,v)` where `d(u,v)` denotes the
2887
+ distance between vertices `u` and `v` (see [KRG1996]_).
2888
+
2889
+ The Wiener index of a directed graph `G` is defined as the sum of the
2890
+ distances between each pairs of vertices, `W(G) = \sum_{u,v\in G} d(u,v)`.
2891
+
2892
+ INPUT:
2893
+
2894
+ - ``g`` -- the input Sage graph
2895
+
2896
+ - ``algorithm`` -- string (default: ``None``); one of the following
2897
+ algorithms:
2898
+
2899
+ - ``'Dijkstra'``, ``'Dijkstra_Boost'``: the Dijkstra algorithm implemented
2900
+ in Boost (works only with positive weights)
2901
+
2902
+ - ``'Bellman-Ford'``, ``'Bellman-Ford_Boost'``: the Bellman-Ford algorithm
2903
+ implemented in Boost (works also with negative weights, if there is no
2904
+ negative cycle)
2905
+
2906
+ - ``weight_function`` -- function (default: ``None``); a function that
2907
+ associates a weight to each edge. If ``None`` (default), the weights of
2908
+ ``g`` are used, if ``g.weighted()==True``, otherwise all edges have
2909
+ weight 1.
2910
+
2911
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
2912
+ that the ``weight_function`` outputs a number for each edge
2913
+
2914
+ EXAMPLES:
2915
+
2916
+ sage: from sage.graphs.base.boost_graph import wiener_index
2917
+ sage: g = Graph([(0,1,9), (1,2,7), (2,3,4), (3,0,3)])
2918
+ sage: wiener_index(g)
2919
+ 8.0
2920
+ sage: g.weighted(True)
2921
+ sage: wiener_index(g)
2922
+ 41.0
2923
+
2924
+ Wiener index of circuit digraphs::
2925
+
2926
+ sage: n = 10
2927
+ sage: g = digraphs.Circuit(n)
2928
+ sage: w = lambda x: (x*x*(x-1))/2
2929
+ sage: wiener_index(g) == w(n)
2930
+ True
2931
+
2932
+ Wiener index of a graph of order 1::
2933
+
2934
+ sage: wiener_index(Graph(1))
2935
+ 0
2936
+
2937
+ The Wiener index is not defined on the empty graph::
2938
+
2939
+ sage: wiener_index(Graph())
2940
+ Traceback (most recent call last):
2941
+ ...
2942
+ ValueError: Wiener index is not defined for the empty graph
2943
+
2944
+ TESTS:
2945
+
2946
+ Using ``'Dijkstra'`` on a graph with negative weights::
2947
+
2948
+ sage: g = Graph([(0, 1, -1), (1, 2, 1)])
2949
+ sage: def weight_of(e):
2950
+ ....: return e[2]
2951
+ sage: wiener_index(g, algorithm='Dijkstra', weight_function=weight_of)
2952
+ Traceback (most recent call last):
2953
+ ...
2954
+ RuntimeError: Dijkstra algorithm does not work with negative weights, use Bellman-Ford instead
2955
+
2956
+ Directed graph with a negative weight cycle::
2957
+
2958
+ sage: g = DiGraph([(0, 1, -1), (1, 2, -1), (2, 0, -1)])
2959
+ sage: def weight_of(e):
2960
+ ....: return e[2]
2961
+ sage: wiener_index(g, algorithm='Bellman-Ford', weight_function=weight_of)
2962
+ Traceback (most recent call last):
2963
+ ...
2964
+ ValueError: the graph contains a negative cycle
2965
+ """
2966
+ if not g:
2967
+ raise ValueError("Wiener index is not defined for the empty graph")
2968
+
2969
+ cdef unsigned int n = g.order()
2970
+ if n == 1:
2971
+ return 0
2972
+
2973
+ import sys
2974
+
2975
+ if weight_function and check_weight:
2976
+ g._check_weight_function(weight_function)
2977
+
2978
+ cdef bint use_Bellman_Ford = algorithm in ['Bellman-Ford', 'Bellman-Ford_Boost']
2979
+ if not use_Bellman_Ford:
2980
+ # Check if there are edges with negative weights
2981
+ if weight_function is not None:
2982
+ for e in g.edges(sort=False):
2983
+ if float(weight_function(e)) < 0:
2984
+ use_Bellman_Ford = True
2985
+ break
2986
+ elif g.weighted():
2987
+ for _, _, w in g.edges(sort=False):
2988
+ if float(w) < 0:
2989
+ use_Bellman_Ford = True
2990
+ break
2991
+
2992
+ if algorithm in ['Dijkstra', 'Dijkstra_Boost']:
2993
+ if use_Bellman_Ford:
2994
+ raise RuntimeError("Dijkstra algorithm does not work with "
2995
+ "negative weights, use Bellman-Ford instead")
2996
+ elif algorithm is not None:
2997
+ raise ValueError(f"unknown algorithm {algorithm!r}")
2998
+
2999
+ # These variables are automatically deleted when the function terminates.
3000
+ cdef v_index vi, u, v
3001
+ cdef dict v_to_int = {vv: vi for vi, vv in enumerate(g)}
3002
+ cdef BoostVecWeightedDiGraphU g_boost_dir
3003
+ cdef BoostVecWeightedGraph g_boost_und
3004
+ cdef vector[double] distances
3005
+ cdef double s = 0
3006
+
3007
+ if g.is_directed():
3008
+ boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
3009
+ else:
3010
+ boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
3011
+
3012
+ for u in range(n):
3013
+ if use_Bellman_Ford:
3014
+ if g.is_directed():
3015
+ sig_on()
3016
+ distances = g_boost_dir.bellman_ford_shortest_paths(u).distances
3017
+ sig_off()
3018
+ else:
3019
+ sig_on()
3020
+ distances = g_boost_und.bellman_ford_shortest_paths(u).distances
3021
+ sig_off()
3022
+ if not distances.size():
3023
+ raise ValueError("the graph contains a negative cycle")
3024
+ else:
3025
+ if g.is_directed():
3026
+ sig_on()
3027
+ distances = g_boost_dir.dijkstra_shortest_paths(u).distances
3028
+ sig_off()
3029
+ else:
3030
+ sig_on()
3031
+ distances = g_boost_und.dijkstra_shortest_paths(u).distances
3032
+ sig_off()
3033
+ if not distances.size():
3034
+ # This situation should never happen
3035
+ raise RuntimeError("something goes wrong. Please report the "
3036
+ "bug on sage-devel@googlegroups.com")
3037
+
3038
+ for v in range(0 if g.is_directed() else (u + 1), n):
3039
+ if distances[v] == sys.float_info.max:
3040
+ from sage.rings.infinity import Infinity
3041
+ return +Infinity
3042
+ else:
3043
+ s += distances[v]
3044
+
3045
+ return s