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
sage/graphs/digraph.py ADDED
@@ -0,0 +1,4754 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ r"""
3
+ Directed graphs
4
+
5
+ This module implements functions and operations involving directed
6
+ graphs. Here is what they can do
7
+
8
+ **Graph basic operations:**
9
+
10
+ .. csv-table::
11
+ :class: contentstable
12
+ :widths: 30, 70
13
+ :delim: |
14
+
15
+ :meth:`~DiGraph.layout_acyclic_dummy` | Compute a (dummy) ranked layout so that all edges point upward.
16
+ :meth:`~DiGraph.layout_acyclic` | Compute a ranked layout so that all edges point upward.
17
+ :meth:`~DiGraph.reverse` | Return a copy of digraph with edges reversed in direction.
18
+ :meth:`~DiGraph.reverse_edge` | Reverse an edge.
19
+ :meth:`~DiGraph.reverse_edges` | Reverse a list of edges.
20
+ :meth:`~DiGraph.out_degree_sequence` | Return the outdegree sequence.
21
+ :meth:`~DiGraph.out_degree_iterator` | Same as degree_iterator, but for out degree.
22
+ :meth:`~DiGraph.out_degree` | Same as degree, but for out degree.
23
+ :meth:`~DiGraph.in_degree_sequence` | Return the indegree sequence of this digraph.
24
+ :meth:`~DiGraph.in_degree_iterator` | Same as degree_iterator, but for in degree.
25
+ :meth:`~DiGraph.in_degree` | Same as degree, but for in-degree.
26
+ :meth:`~DiGraph.neighbors_out` | Return the list of the out-neighbors of a given vertex.
27
+ :meth:`~DiGraph.neighbor_out_iterator` | Return an iterator over the out-neighbors of a given vertex.
28
+ :meth:`~DiGraph.neighbors_in` | Return the list of the in-neighbors of a given vertex.
29
+ :meth:`~DiGraph.neighbor_in_iterator` | Return an iterator over the in-neighbors of vertex.
30
+ :meth:`~DiGraph.outgoing_edges` | Return a list of edges departing from vertices.
31
+ :meth:`~DiGraph.outgoing_edge_iterator` | Return an iterator over all departing edges from vertices
32
+ :meth:`~DiGraph.incoming_edges` | Return a list of edges arriving at vertices.
33
+ :meth:`~DiGraph.incoming_edge_iterator` | Return an iterator over all arriving edges from vertices
34
+ :meth:`~DiGraph.sources` | Return the list of all sources (vertices without incoming edges) of this digraph.
35
+ :meth:`~DiGraph.sinks` | Return the list of all sinks (vertices without outgoing edges) of this digraph.
36
+ :meth:`~DiGraph.to_undirected` | Return an undirected version of the graph.
37
+ :meth:`~DiGraph.to_directed` | Since the graph is already directed, simply returns a copy of itself.
38
+ :meth:`~DiGraph.is_directed` | Since digraph is directed, returns True.
39
+ :meth:`~DiGraph.dig6_string` | Return the ``dig6`` representation of the digraph as an ASCII string.
40
+
41
+ **Distances:**
42
+
43
+ .. csv-table::
44
+ :class: contentstable
45
+ :widths: 30, 70
46
+ :delim: |
47
+
48
+ :meth:`~DiGraph.eccentricity` | Return the eccentricity of vertex (or vertices) ``v``.
49
+ :meth:`~DiGraph.radius` | Return the radius of the DiGraph.
50
+ :meth:`~DiGraph.diameter` | Return the diameter of the DiGraph.
51
+ :meth:`~DiGraph.center` | Return the set of vertices in the center of the DiGraph.
52
+ :meth:`~DiGraph.periphery` | Return the set of vertices in the periphery of the DiGraph.
53
+
54
+ **Paths and cycles:**
55
+
56
+ .. csv-table::
57
+ :class: contentstable
58
+ :widths: 30, 70
59
+ :delim: |
60
+
61
+ :meth:`~DiGraph.all_cycles_iterator` | Return an iterator over all the cycles of ``self`` starting with one of the given vertices.
62
+ :meth:`~DiGraph.all_simple_cycles` | Return a list of all simple cycles of ``self``.
63
+
64
+ **Representation theory:**
65
+
66
+ .. csv-table::
67
+ :class: contentstable
68
+ :widths: 30, 70
69
+ :delim: |
70
+
71
+ :meth:`~DiGraph.path_semigroup` | Return the (partial) semigroup formed by the paths of the digraph.
72
+ :meth:`~DiGraph.auslander_reiten_quiver` | Return the Auslander-Reiten quiver of ``self``.
73
+
74
+ **Connectivity:**
75
+
76
+ .. csv-table::
77
+ :class: contentstable
78
+ :widths: 30, 70
79
+ :delim: |
80
+
81
+ :meth:`~DiGraph.is_strongly_connected` | Check whether the current ``DiGraph`` is strongly connected.
82
+ :meth:`~DiGraph.strongly_connected_components_digraph` | Return the digraph of the strongly connected components
83
+ :meth:`~DiGraph.strongly_connected_components_subgraphs` | Return the strongly connected components as a list of subgraphs.
84
+ :meth:`~DiGraph.strongly_connected_component_containing_vertex` | Return the strongly connected component containing a given vertex
85
+ :meth:`~DiGraph.strongly_connected_components` | Return the list of strongly connected components.
86
+ :meth:`~DiGraph.strong_articulation_points` | Return the strong articulation points of this digraph.
87
+
88
+
89
+ **Acyclicity:**
90
+
91
+ .. csv-table::
92
+ :class: contentstable
93
+ :widths: 30, 70
94
+ :delim: |
95
+
96
+ :meth:`~DiGraph.is_directed_acyclic` | Check whether the digraph is acyclic or not.
97
+ :meth:`~DiGraph.is_transitive` | Check whether the digraph is transitive or not.
98
+ :meth:`~DiGraph.is_aperiodic` | Check whether the digraph is aperiodic or not.
99
+ :meth:`~DiGraph.is_tournament` | Check whether the digraph is a tournament.
100
+ :meth:`~DiGraph.period` | Return the period of the digraph.
101
+ :meth:`~DiGraph.level_sets` | Return the level set decomposition of the digraph.
102
+ :meth:`~DiGraph.topological_sort_generator` | Return a list of all topological sorts of the digraph if it is acyclic
103
+ :meth:`~DiGraph.topological_sort` | Return a topological sort of the digraph if it is acyclic
104
+
105
+ **Hard stuff:**
106
+
107
+ .. csv-table::
108
+ :class: contentstable
109
+ :widths: 30, 70
110
+ :delim: |
111
+
112
+ :meth:`~DiGraph.feedback_edge_set` | Compute the minimum feedback edge (arc) set of a digraph
113
+
114
+ **Miscellaneous:**
115
+
116
+ .. csv-table::
117
+ :class: contentstable
118
+ :widths: 30, 70
119
+ :delim: |
120
+
121
+ :meth:`~DiGraph.flow_polytope` | Compute the flow polytope of a digraph
122
+ :meth:`~DiGraph.degree_polynomial` | Return the generating polynomial of degrees of vertices in ``self``.
123
+ :meth:`~DiGraph.out_branchings` | Return an iterator over the out branchings rooted at given vertex in ``self``.
124
+ :meth:`~DiGraph.in_branchings` | Return an iterator over the in branchings rooted at given vertex in ``self``.
125
+
126
+ Methods
127
+ -------
128
+ """
129
+
130
+ # ****************************************************************************
131
+ # Copyright (C) 2010 Alexandre Blondin Masse <alexandre.blondin.masse at gmail.com>
132
+ # Carl Witty <cwitty@newtonlabs.com>
133
+ # Gregory McWhirter <gmcwhirt@uci.edu>
134
+ # Minh Van Nguyen <nguyenminh2@gmail.com>
135
+ # 2010-2011 Robert L. Miller <rlm@rlmiller.org>
136
+ # 2010-2015 Nathann Cohen <nathann.cohen@gmail.com>
137
+ # Nicolas M. Thiery <nthiery@users.sf.net>
138
+ # 2011 Johannes Klaus Fichte <fichte@kr.tuwien.ac.at>
139
+ # 2012 Javier López Peña <vengoroso@gmail.com>
140
+ # 2012 Jim Stark <jstarx@gmail.com>
141
+ # 2012 Karl-Dieter Crisman <kcrisman@gmail.com>
142
+ # 2012 Keshav Kini <keshav.kini@gmail.com>
143
+ # 2012 Lukas Lansky <lansky@kam.mff.cuni.cz>
144
+ # 2012-2015 Volker Braun <vbraun.name@gmail.com>
145
+ # 2012-2017 Jeroen Demeyer <jdemeyer@cage.ugent.be>
146
+ # 2012-2018 David Coudert <david.coudert@inria.fr>
147
+ # 2013 Emily Gunawan <egunawan@umn.edu>
148
+ # 2013 Gregg Musiker <musiker@math.mit.edu>
149
+ # 2013 Mathieu Guay-Paquet <mathieu.guaypaquet@gmail.com>
150
+ # 2013-2014 Simon King <simon.king@uni-jena.de>
151
+ # 2014 Clemens Heuberger <clemens.heuberger@aau.at>
152
+ # Erik Massop <e.massop@hccnet.nl>
153
+ # R. Andrew Ohana <andrew.ohana@gmail.com>
154
+ # Wilfried Luebbe <wluebbe@gmail.com>
155
+ # 2014-2015 André Apitzsch <andre.apitzsch@etit.tu-chemnitz.de>
156
+ # Darij Grinberg <darijgrinberg@gmail.com>
157
+ # Travis Scrimshaw <tscrim at ucdavis.edu>
158
+ # Vincent Delecroix <20100.delecroix@gmail.com>
159
+ # 2014-2017 Frédéric Chapoton <chapoton@math.univ-lyon1.fr>
160
+ # 2015 Michele Borassi <michele.borassi@imtlucca.it>
161
+ # 2015-2017 John H. Palmieri <palmieri@math.washington.edu>
162
+ # Jori Mäntysalo <jori.mantysalo@uta.fi>
163
+ # 2016 Dima Pasechnik <dimpase@gmail.com>
164
+ # 2018 Meghana M Reddy <mreddymeghana@gmail.com>
165
+ # Julian Rüth <julian.rueth@fsfe.org>
166
+ # 2019 Rajat Mittal <rajat.mttl@gmail.com>
167
+ # This program is free software: you can redistribute it and/or modify
168
+ # it under the terms of the GNU General Public License as published by
169
+ # the Free Software Foundation, either version 2 of the License, or
170
+ # (at your option) any later version.
171
+ # https://www.gnu.org/licenses/
172
+ # ****************************************************************************
173
+
174
+ from copy import copy
175
+ from sage.rings.integer import Integer
176
+ from sage.rings.integer_ring import ZZ
177
+ from itertools import product
178
+ import sage.graphs.generic_graph_pyx as generic_graph_pyx
179
+ from sage.graphs.generic_graph import GenericGraph
180
+ from sage.graphs.dot2tex_utils import have_dot2tex
181
+ from sage.graphs.views import EdgesView
182
+
183
+
184
+ class DiGraph(GenericGraph):
185
+ r"""
186
+ Directed graph.
187
+
188
+ A digraph or directed graph is a set of vertices connected by oriented
189
+ edges. See also the :wikipedia:`Directed_graph`. For a collection of
190
+ pre-defined digraphs, see the :mod:`~sage.graphs.digraph_generators` module.
191
+
192
+ A :class:`DiGraph` object has many methods whose list can be obtained by
193
+ typing ``g.<tab>`` (i.e. hit the :kbd:`Tab` key) or by reading the documentation
194
+ of :mod:`~sage.graphs.digraph`, :mod:`~sage.graphs.generic_graph`, and
195
+ :mod:`~sage.graphs.graph`.
196
+
197
+ INPUT:
198
+
199
+ By default, a :class:`DiGraph` object is simple (i.e. no *loops* nor
200
+ *multiple edges*) and unweighted. This can be easily tuned with the
201
+ appropriate flags (see below).
202
+
203
+ - ``data`` -- can be any of the following (see the ``format`` argument):
204
+
205
+ #. ``DiGraph()`` -- build a digraph on 0 vertices
206
+
207
+ #. ``DiGraph(5)`` -- return an edgeless digraph on the 5 vertices 0,...,4
208
+
209
+ #. ``DiGraph([list_of_vertices, list_of_edges])`` -- return a digraph with
210
+ given vertices/edges
211
+
212
+ To bypass auto-detection, prefer the more explicit
213
+ ``DiGraph([V, E], format='vertices_and_edges')``.
214
+
215
+ #. ``DiGraph(list_of_edges)`` -- return a digraph with a given list of
216
+ edges (see documentation of
217
+ :meth:`~sage.graphs.generic_graph.GenericGraph.add_edges`).
218
+
219
+ To bypass auto-detection, prefer the more explicit
220
+ ``DiGraph(L, format='list_of_edges')``.
221
+
222
+ #. ``DiGraph({1: [2,3,4], 3: [4]})`` -- return a digraph by associating to
223
+ each vertex the list of its out-neighbors.
224
+
225
+ To bypass auto-detection, prefer the more explicit
226
+ ``DiGraph(D, format='dict_of_lists')``.
227
+
228
+ #. ``DiGraph({1: {2: 'a', 3: 'b'}, 3: {2: 'c'}})`` -- return a digraph by
229
+ associating a list of out-neighbors to each vertex and providing its
230
+ edge label.
231
+
232
+ To bypass auto-detection, prefer the more explicit
233
+ ``DiGraph(D, format='dict_of_dicts')``.
234
+
235
+ For digraphs with multiple edges, you can provide a list of labels
236
+ instead, e.g.: ``DiGraph({1: {2: ['a1', 'a2'], 3:['b']},
237
+ 3:{2:['c']}})``.
238
+
239
+ #. ``DiGraph(a_matrix)`` -- return a digraph with given (weighted)
240
+ adjacency matrix (see documentation of
241
+ :meth:`~sage.graphs.generic_graph.GenericGraph.adjacency_matrix`).
242
+
243
+ To bypass auto-detection, prefer the more explicit ``DiGraph(M,
244
+ format='adjacency_matrix')``. To take weights into account, use
245
+ ``format='weighted_adjacency_matrix'`` instead.
246
+
247
+ #. ``DiGraph(a_nonsquare_matrix)`` -- return a digraph with given
248
+ incidence matrix (see documentation of
249
+ :meth:`~sage.graphs.generic_graph.GenericGraph.incidence_matrix`).
250
+
251
+ To bypass auto-detection, prefer the more explicit ``DiGraph(M,
252
+ format='incidence_matrix')``.
253
+
254
+ #. ``DiGraph([V, f])`` -- return a digraph with a vertex set ``V`` and an
255
+ edge `u,v` whenever `f(u, v)` is ``True``. Example: ``DiGraph([
256
+ [1..10], lambda x,y: abs(x - y).is_square()])``
257
+
258
+ #. ``DiGraph('FOC@?OC@_?')`` -- return a digraph from a ``dig6`` string
259
+ (see documentation of :meth:`~dig6_string`).
260
+
261
+ #. ``DiGraph(another_digraph)`` -- return a digraph from a Sage (di)graph,
262
+ `pygraphviz <https://pygraphviz.github.io/>`__ digraph, `NetworkX
263
+ <https://networkx.github.io/>`__ digraph, or `igraph
264
+ <http://igraph.org/python/>`__ digraph.
265
+
266
+ - ``pos`` -- dictionary (default: ``None``); a positioning dictionary. For
267
+ example, the spring layout from NetworkX for the 5-cycle is::
268
+
269
+ {0: [-0.91679746, 0.88169588],
270
+ 1: [ 0.47294849, 1.125 ],
271
+ 2: [ 1.125 ,-0.12867615],
272
+ 3: [ 0.12743933,-1.125 ],
273
+ 4: [-1.125 ,-0.50118505]}
274
+
275
+ - ``name`` -- string (default: ``None``); gives the graph a name (e.g.,
276
+ name='complete')
277
+
278
+ - ``loops`` -- boolean (default: ``None``); whether to allow loops (ignored
279
+ if data is an instance of the DiGraph class)
280
+
281
+ - ``multiedges`` -- boolean (default: ``None``); whether to allow multiple
282
+ edges (ignored if data is an instance of the DiGraph class)
283
+
284
+ - ``weighted`` -- boolean (default: ``None``); whether digraph thinks of
285
+ itself as weighted or not. See ``self.weighted()``
286
+
287
+ - ``format`` -- string (default: ``None``); if set to ``None``,
288
+ :class:`DiGraph` tries to guess input's format. To avoid this possibly
289
+ time-consuming step, one of the following values can be specified (see
290
+ description above): ``'int'``, ``'dig6'``, ``'rule'``,
291
+ ``'list_of_edges'``, ``'dict_of_lists'``, ``'dict_of_dicts'``,
292
+ ``'adjacency_matrix'``, ``'weighted_adjacency_matrix'``,
293
+ ``'incidence_matrix'``, ``"NX"``, ``'igraph'``.
294
+
295
+ - ``sparse`` -- boolean (default: ``True``); ``sparse=True`` is an alias for
296
+ ``data_structure="sparse"``, and ``sparse=False`` is an alias for
297
+ ``data_structure="dense"``
298
+
299
+ - ``data_structure`` -- string (default: ``'sparse'``); one of the following
300
+ (for more information, see :mod:`~sage.graphs.base.overview`):
301
+
302
+ * ``'dense'`` -- selects the :mod:`~sage.graphs.base.dense_graph` backend
303
+
304
+ * ``'sparse'`` -- selects the :mod:`~sage.graphs.base.sparse_graph`
305
+ backend
306
+
307
+ * ``'static_sparse'`` -- selects the
308
+ :mod:`~sage.graphs.base.static_sparse_backend` (this backend is faster
309
+ than the sparse backend and smaller in memory, and it is immutable, so
310
+ that the resulting graphs can be used as dictionary keys).
311
+
312
+ - ``immutable`` -- boolean (default: ``False``); whether to create a
313
+ immutable digraph. Note that ``immutable=True`` is actually a shortcut for
314
+ ``data_structure='static_sparse'``.
315
+
316
+ - ``hash_labels`` -- boolean (default: ``None``); whether to include edge
317
+ labels during hashing. This parameter defaults to ``True`` if the digraph
318
+ is weighted. This parameter is ignored if the digraph is mutable.
319
+ Beware that trying to hash unhashable labels will raise an error.
320
+
321
+ - ``vertex_labels`` -- boolean (default: ``True``); whether to allow any
322
+ object as a vertex (slower), or only the integers `0,...,n-1`, where `n`
323
+ is the number of vertices.
324
+
325
+ - ``convert_empty_dict_labels_to_None`` -- boolean (default: ``None``); this
326
+ arguments sets the default edge labels used by NetworkX (empty
327
+ dictionaries) to be replaced by ``None``, the default Sage edge label. It
328
+ is set to ``True`` iff a NetworkX graph is on the input.
329
+
330
+ EXAMPLES:
331
+
332
+ #. A dictionary of dictionaries::
333
+
334
+ sage: g = DiGraph({0: {1: 'x', 2: 'z', 3: 'a'}, 2: {5: 'out'}}); g
335
+ Digraph on 5 vertices
336
+
337
+ The labels ('x', 'z', 'a', 'out') are labels for edges. For example,
338
+ 'out' is the label for the edge from 2 to 5. Labels can be used as
339
+ weights, if all the labels share some common parent.
340
+
341
+ #. A dictionary of lists (or iterables)::
342
+
343
+ sage: g = DiGraph({0: [1, 2, 3], 2: [4]}); g
344
+ Digraph on 5 vertices
345
+ sage: g = DiGraph({0: (1, 2, 3), 2: (4,)}); g
346
+ Digraph on 5 vertices
347
+
348
+ #. A list of vertices and a function describing adjacencies. Note that the
349
+ list of vertices and the function must be enclosed in a list (i.e.,
350
+ ``[list of vertices, function]``).
351
+
352
+ We construct a graph on the integers 1 through 12 such that there is a
353
+ directed edge from `i` to `j` if and only if `i` divides `j`::
354
+
355
+ sage: g = DiGraph([[1..12], lambda i,j: i != j and i.divides(j)])
356
+ sage: g.vertices(sort=True)
357
+ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
358
+ sage: g.adjacency_matrix() # needs sage.modules
359
+ [0 1 1 1 1 1 1 1 1 1 1 1]
360
+ [0 0 0 1 0 1 0 1 0 1 0 1]
361
+ [0 0 0 0 0 1 0 0 1 0 0 1]
362
+ [0 0 0 0 0 0 0 1 0 0 0 1]
363
+ [0 0 0 0 0 0 0 0 0 1 0 0]
364
+ [0 0 0 0 0 0 0 0 0 0 0 1]
365
+ [0 0 0 0 0 0 0 0 0 0 0 0]
366
+ [0 0 0 0 0 0 0 0 0 0 0 0]
367
+ [0 0 0 0 0 0 0 0 0 0 0 0]
368
+ [0 0 0 0 0 0 0 0 0 0 0 0]
369
+ [0 0 0 0 0 0 0 0 0 0 0 0]
370
+ [0 0 0 0 0 0 0 0 0 0 0 0]
371
+
372
+ #. A Sage matrix: Note: If format is not specified, then Sage assumes a
373
+ square matrix is an adjacency matrix, and a nonsquare matrix is an
374
+ incidence matrix.
375
+
376
+ - an adjacency matrix::
377
+
378
+ sage: M = Matrix([[0, 1, 1, 1, 0], [0, 0, 0, 0, 0], # needs sage.modules
379
+ ....: [0, 0, 0, 0, 1], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]); M
380
+ [0 1 1 1 0]
381
+ [0 0 0 0 0]
382
+ [0 0 0 0 1]
383
+ [0 0 0 0 0]
384
+ [0 0 0 0 0]
385
+ sage: DiGraph(M) # needs sage.modules
386
+ Digraph on 5 vertices
387
+
388
+ sage: M = Matrix([[0,1,-1], [-1,0,-1/2], [1,1/2,0]]); M # needs sage.modules
389
+ [ 0 1 -1]
390
+ [ -1 0 -1/2]
391
+ [ 1 1/2 0]
392
+ sage: G = DiGraph(M, sparse=True, weighted=True); G # needs sage.modules
393
+ Digraph on 3 vertices
394
+ sage: G.weighted() # needs sage.modules
395
+ True
396
+
397
+ - an incidence matrix::
398
+
399
+ sage: M = Matrix(6, [-1,0,0,0,1, 1,-1,0,0,0, 0,1,-1,0,0, # needs sage.modules
400
+ ....: 0,0,1,-1,0, 0,0,0,1,-1, 0,0,0,0,0]); M
401
+ [-1 0 0 0 1]
402
+ [ 1 -1 0 0 0]
403
+ [ 0 1 -1 0 0]
404
+ [ 0 0 1 -1 0]
405
+ [ 0 0 0 1 -1]
406
+ [ 0 0 0 0 0]
407
+ sage: DiGraph(M) # needs sage.modules
408
+ Digraph on 6 vertices
409
+
410
+ #. A ``dig6`` string: Sage automatically recognizes whether a string is in
411
+ ``dig6`` format, which is a directed version of ``graph6``::
412
+
413
+ sage: D = DiGraph('IRAaDCIIOWEOKcPWAo')
414
+ sage: D
415
+ Digraph on 10 vertices
416
+
417
+ sage: D = DiGraph('IRAaDCIIOEOKcPWAo')
418
+ Traceback (most recent call last):
419
+ ...
420
+ RuntimeError: the string (IRAaDCIIOEOKcPWAo) seems corrupt: for n = 10, the string is too short
421
+
422
+ sage: D = DiGraph("IRAaDCI'OWEOKcPWAo")
423
+ Traceback (most recent call last):
424
+ ...
425
+ RuntimeError: the string seems corrupt: valid characters are
426
+ ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
427
+
428
+ #. A NetworkX MultiDiGraph::
429
+
430
+ sage: import networkx # needs networkx
431
+ sage: g = networkx.MultiDiGraph({0: [1, 2, 3], 2: [4]}) # needs networkx
432
+ sage: DiGraph(g) # needs networkx
433
+ Multi-digraph on 5 vertices
434
+
435
+
436
+ #. A NetworkX digraph::
437
+
438
+ sage: import networkx # needs networkx
439
+ sage: g = networkx.DiGraph({0: [1, 2, 3], 2: [4]}) # needs networkx
440
+ sage: DiGraph(g) # needs networkx
441
+ Digraph on 5 vertices
442
+
443
+ #. An igraph directed Graph (see also
444
+ :meth:`~sage.graphs.generic_graph.GenericGraph.igraph_graph`)::
445
+
446
+ sage: import igraph # optional - python_igraph
447
+ sage: g = igraph.Graph([(0,1),(0,2)], directed=True) # optional - python_igraph
448
+ sage: DiGraph(g) # optional - python_igraph
449
+ Digraph on 3 vertices
450
+
451
+ If ``vertex_labels`` is ``True``, the names of the vertices are given by
452
+ the vertex attribute ``'name'``, if available::
453
+
454
+ sage: # optional - python_igraph
455
+ sage: g = igraph.Graph([(0,1),(0,2)], directed=True, vertex_attrs={'name':['a','b','c']})
456
+ sage: DiGraph(g).vertices(sort=True)
457
+ ['a', 'b', 'c']
458
+ sage: g = igraph.Graph([(0,1),(0,2)], directed=True, vertex_attrs={'label':['a','b','c']})
459
+ sage: DiGraph(g).vertices(sort=True)
460
+ [0, 1, 2]
461
+
462
+ If the igraph Graph has edge attributes, they are used as edge labels::
463
+
464
+ sage: g = igraph.Graph([(0, 1), (0, 2)], directed=True, # optional - python_igraph
465
+ ....: edge_attrs={'name':['a', 'b'], 'weight':[1, 3]})
466
+ sage: DiGraph(g).edges(sort=True) # optional - python_igraph
467
+ [(0, 1, {'name': 'a', 'weight': 1}), (0, 2, {'name': 'b', 'weight': 3})]
468
+
469
+
470
+ TESTS::
471
+
472
+ sage: DiGraph({0:[1,2,3], 2:[4]}).edges(sort=True)
473
+ [(0, 1, None), (0, 2, None), (0, 3, None), (2, 4, None)]
474
+ sage: DiGraph({0:(1,2,3), 2:(4,)}).edges(sort=True)
475
+ [(0, 1, None), (0, 2, None), (0, 3, None), (2, 4, None)]
476
+ sage: DiGraph({0:Set([1,2,3]), 2:Set([4])}).edges(sort=True)
477
+ [(0, 1, None), (0, 2, None), (0, 3, None), (2, 4, None)]
478
+
479
+ Demonstrate that digraphs using the static backend are equal to mutable
480
+ graphs but can be used as dictionary keys::
481
+
482
+ sage: # needs networkx
483
+ sage: import networkx
484
+ sage: g = networkx.DiGraph({0:[1,2,3], 2:[4]})
485
+ sage: G = DiGraph(g)
486
+ sage: G_imm = DiGraph(G, data_structure='static_sparse')
487
+ sage: H_imm = DiGraph(G, data_structure='static_sparse')
488
+ sage: H_imm is G_imm
489
+ False
490
+ sage: H_imm == G_imm == G
491
+ True
492
+ sage: {G_imm:1}[H_imm]
493
+ 1
494
+ sage: {G_imm:1}[G]
495
+ Traceback (most recent call last):
496
+ ...
497
+ TypeError: This graph is mutable, and thus not hashable. Create an
498
+ immutable copy by `g.copy(immutable=True)`
499
+
500
+ The error message states that one can also create immutable graphs by
501
+ specifying the ``immutable`` optional argument (not only by
502
+ ``data_structure='static_sparse'`` as above)::
503
+
504
+ sage: J_imm = DiGraph(G, immutable=True) # needs networkx
505
+ sage: J_imm == G_imm # needs networkx
506
+ True
507
+ sage: type(J_imm._backend) == type(G_imm._backend) # needs networkx
508
+ True
509
+
510
+ From a list of vertices and a list of edges::
511
+
512
+ sage: G = DiGraph([[1,2,3],[(1,2)]]); G
513
+ Digraph on 3 vertices
514
+ sage: G.edges(sort=True)
515
+ [(1, 2, None)]
516
+
517
+ Check that :issue:`27505` is fixed::
518
+
519
+ sage: DiGraph(DiGraph().networkx_graph(), weighted=None, format='NX') # needs networkx
520
+ Digraph on 0 vertices
521
+ """
522
+ _directed = True
523
+
524
+ def __init__(self, data=None, pos=None, loops=None, format=None,
525
+ weighted=None, data_structure='sparse',
526
+ vertex_labels=True, name=None,
527
+ multiedges=None, convert_empty_dict_labels_to_None=None,
528
+ sparse=True, immutable=False, hash_labels=None):
529
+ """
530
+ TESTS::
531
+
532
+ sage: D = DiGraph()
533
+ sage: loads(dumps(D)) == D
534
+ True
535
+
536
+ sage: a = matrix(2,2,[1,2,0,1]) # needs sage.modules
537
+ sage: DiGraph(a, sparse=True).adjacency_matrix() == a # needs sage.modules
538
+ True
539
+
540
+ sage: a = matrix(2,2,[3,2,0,1]) # needs sage.modules
541
+ sage: DiGraph(a, sparse=True).adjacency_matrix() == a # needs sage.modules
542
+ True
543
+
544
+ The positions are copied when the DiGraph is built from another DiGraph
545
+ or from a Graph ::
546
+
547
+ sage: g = DiGraph(graphs.PetersenGraph())
548
+ sage: h = DiGraph(g)
549
+ sage: g.get_pos() == h.get_pos()
550
+ True
551
+ sage: g.get_pos() == graphs.PetersenGraph().get_pos()
552
+ True
553
+
554
+ The position dictionary is not the input one (:issue:`22424`)::
555
+
556
+ sage: my_pos = {0:(0,0), 1:(1,1)}
557
+ sage: D = DiGraph([[0,1], [(0,1)]], pos=my_pos)
558
+ sage: my_pos == D._pos
559
+ True
560
+ sage: my_pos is D._pos
561
+ False
562
+
563
+ Detection of multiple edges::
564
+
565
+ sage: DiGraph({1:{2:[0,1]}})
566
+ Multi-digraph on 2 vertices
567
+ sage: DiGraph({1:{2:0}})
568
+ Digraph on 2 vertices
569
+
570
+ An empty list or dictionary defines a simple graph (:issue:`10441` and
571
+ :issue:`12910`)::
572
+
573
+ sage: DiGraph([])
574
+ Digraph on 0 vertices
575
+ sage: DiGraph({})
576
+ Digraph on 0 vertices
577
+ sage: # not "Multi-digraph on 0 vertices"
578
+
579
+ Problem with weighted adjacency matrix (:issue:`13919`)::
580
+
581
+ sage: B = {0:{1:2,2:5,3:4},1:{2:2,4:7},2:{3:1,4:4,5:3},
582
+ ....: 3:{5:4},4:{5:1,6:5},5:{4:1,6:7,5:1}}
583
+ sage: grafo3 = DiGraph(B, weighted=True)
584
+ sage: matad = grafo3.weighted_adjacency_matrix() # needs sage.modules
585
+ sage: grafo4 = DiGraph(matad, format='adjacency_matrix', weighted=True) # needs sage.modules
586
+ sage: grafo4.shortest_path(0, 6, by_weight=True) # needs sage.modules
587
+ [0, 1, 2, 5, 4, 6]
588
+
589
+ Building a DiGraph with ``immutable=False`` returns a mutable graph::
590
+
591
+ sage: g = graphs.PetersenGraph()
592
+ sage: g = DiGraph(g.edges(sort=True), immutable=False)
593
+ sage: g.add_edge("Hey", "Heyyyyyyy")
594
+ sage: {g:1}[g]
595
+ Traceback (most recent call last):
596
+ ...
597
+ TypeError: This graph is mutable, and thus not hashable. Create an immutable copy by `g.copy(immutable=True)`
598
+ sage: copy(g) is g
599
+ False
600
+ sage: {g.copy(immutable=True):1}[g.copy(immutable=True)]
601
+ 1
602
+
603
+ But building it with ``immutable=True`` returns an immutable graph::
604
+
605
+ sage: g = DiGraph(graphs.PetersenGraph(), immutable=True)
606
+ sage: g.add_edge("Hey", "Heyyyyyyy")
607
+ Traceback (most recent call last):
608
+ ...
609
+ ValueError: graph is immutable; please change a copy instead (use function copy())
610
+ sage: {g:1}[g]
611
+ 1
612
+ sage: copy(g) is g # copy is mutable again
613
+ False
614
+
615
+ Unknown input format::
616
+
617
+ sage: DiGraph(4, format='HeyHeyHey')
618
+ Traceback (most recent call last):
619
+ ...
620
+ ValueError: unknown input format 'HeyHeyHey'
621
+
622
+ Sage DiGraph from igraph undirected graph::
623
+
624
+ sage: import igraph # optional - python_igraph
625
+ sage: DiGraph(igraph.Graph()) # optional - python_igraph
626
+ Traceback (most recent call last):
627
+ ...
628
+ ValueError: a *directed* igraph graph was expected. To build an undirected graph, call the Graph constructor
629
+
630
+ Vertex labels are retained in the graph (:issue:`14708`)::
631
+
632
+ sage: g = DiGraph()
633
+ sage: g.add_vertex(0)
634
+ sage: g.set_vertex(0, 'foo')
635
+ sage: g.get_vertices()
636
+ {0: 'foo'}
637
+ sage: DiGraph(g).get_vertices()
638
+ {0: 'foo'}
639
+ """
640
+ msg = ''
641
+ GenericGraph.__init__(self)
642
+ from sage.structure.element import Matrix
643
+
644
+ if sparse is False:
645
+ if data_structure != "sparse":
646
+ raise ValueError("the 'sparse' argument is an alias for "
647
+ "'data_structure', please do not define both")
648
+ data_structure = "dense"
649
+
650
+ if multiedges or weighted:
651
+ if data_structure == "dense":
652
+ raise RuntimeError("multiedge and weighted c_graphs must be sparse")
653
+
654
+ if immutable:
655
+ data_structure = 'static_sparse'
656
+
657
+ # If the data structure is static_sparse, we first build a graph
658
+ # using the sparse data structure, then re-encode the resulting graph
659
+ # as a static sparse graph.
660
+ from sage.graphs.base.sparse_graph import SparseGraphBackend
661
+ from sage.graphs.base.dense_graph import DenseGraphBackend
662
+ if data_structure in ["sparse", "static_sparse"]:
663
+ CGB = SparseGraphBackend
664
+ elif data_structure == "dense":
665
+ CGB = DenseGraphBackend
666
+ else:
667
+ raise ValueError("data_structure must be equal to 'sparse', "
668
+ "'static_sparse' or 'dense'")
669
+ self._backend = CGB(0, directed=True)
670
+
671
+ if format is None and isinstance(data, str):
672
+ format = 'dig6'
673
+ if data[:8] == ">>dig6<<":
674
+ data = data[8:]
675
+ if format is None and isinstance(data, Matrix):
676
+ if data.is_square():
677
+ format = 'adjacency_matrix'
678
+ else:
679
+ format = 'incidence_matrix'
680
+ msg += "Non-symmetric or non-square matrix assumed to be an incidence matrix: "
681
+ if format is None and isinstance(data, DiGraph):
682
+ format = 'DiGraph'
683
+ from sage.graphs.graph import Graph
684
+ if format is None and isinstance(data, Graph):
685
+ data = data.to_directed()
686
+ format = 'DiGraph'
687
+ if format is None and isinstance(data, list) and \
688
+ len(data) >= 2 and callable(data[1]):
689
+ format = 'rule'
690
+
691
+ if (format is None and
692
+ isinstance(data, list) and
693
+ len(data) == 2 and
694
+ isinstance(data[0], list) and # a list of two lists, the second of
695
+ ((isinstance(data[1], list) and # which contains iterables (the edges)
696
+ (not data[1] or callable(getattr(data[1][0], "__iter__", None)))) or
697
+ (isinstance(data[1], EdgesView)))):
698
+ format = "vertices_and_edges"
699
+
700
+ if format is None and isinstance(data, dict):
701
+ if not data:
702
+ format = 'dict_of_dicts'
703
+ else:
704
+ val = next(iter(data.values()))
705
+ if isinstance(val, dict):
706
+ format = 'dict_of_dicts'
707
+ else:
708
+ format = 'dict_of_lists'
709
+ if format is None and hasattr(data, 'adj'):
710
+ # the input is a networkx (Multi)(Di)Graph
711
+ format = 'NX'
712
+
713
+ if (format is None and hasattr(data, 'vcount') and
714
+ hasattr(data, 'get_edgelist')):
715
+ try:
716
+ import igraph
717
+ except ImportError:
718
+ raise ImportError("the data seems to be a igraph object, but "
719
+ "igraph is not installed in Sage. To install "
720
+ "it, run 'sage -i python_igraph'")
721
+ if format is None and isinstance(data, igraph.Graph):
722
+ format = 'igraph'
723
+ if format is None and isinstance(data, (int, Integer)):
724
+ format = 'int'
725
+ if format is None and data is None:
726
+ format = 'int'
727
+ data = 0
728
+
729
+ # Input is a list of edges or an EdgesView
730
+ if format is None and isinstance(data, (list, EdgesView)):
731
+ format = "list_of_edges"
732
+ if weighted is None:
733
+ weighted = False
734
+
735
+ if format == 'weighted_adjacency_matrix':
736
+ if weighted is False:
737
+ raise ValueError("format was weighted_adjacency_matrix but weighted was False")
738
+ if weighted is None:
739
+ weighted = True
740
+ if multiedges is None:
741
+ multiedges = False
742
+ format = 'adjacency_matrix'
743
+
744
+ if format is None:
745
+ raise ValueError("This input cannot be turned into a graph")
746
+
747
+ # At this point, format has been set. We build the graph
748
+
749
+ if format == 'dig6':
750
+ if weighted is None:
751
+ self._weighted = False
752
+ self.allow_loops(bool(loops), check=False)
753
+ self.allow_multiple_edges(bool(multiedges), check=False)
754
+ from .graph_input import from_dig6
755
+ from_dig6(self, data)
756
+
757
+ elif format == 'adjacency_matrix':
758
+ from .graph_input import from_adjacency_matrix
759
+ from_adjacency_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted)
760
+
761
+ elif format == 'incidence_matrix':
762
+ from .graph_input import from_oriented_incidence_matrix
763
+ from_oriented_incidence_matrix(self, data, loops=loops, multiedges=multiedges, weighted=weighted)
764
+
765
+ elif format == 'DiGraph':
766
+ if loops is None:
767
+ loops = data.allows_loops()
768
+ elif not loops and data.has_loops():
769
+ raise ValueError("the digraph was built with loops=False but input data has a loop")
770
+ if multiedges is None:
771
+ multiedges = data.allows_multiple_edges()
772
+ elif not multiedges:
773
+ e = data.edges(labels=False, sort=False)
774
+ if len(e) != len(set(e)):
775
+ raise ValueError("no multiple edges but input digraph"
776
+ " has multiple edges")
777
+ self.allow_multiple_edges(multiedges, check=False)
778
+ self.allow_loops(loops, check=False)
779
+ if weighted is None:
780
+ weighted = data.weighted()
781
+ if data.get_pos() is not None:
782
+ pos = data.get_pos()
783
+ self.set_vertices(data.get_vertices())
784
+ data._backend.subgraph_given_vertices(self._backend, data)
785
+ self.name(data.name())
786
+ elif format == 'rule':
787
+ f = data[1]
788
+ if loops is None:
789
+ loops = any(f(v, v) for v in data[0])
790
+ if weighted is None:
791
+ weighted = False
792
+ self.allow_multiple_edges(bool(multiedges), check=False)
793
+ self.allow_loops(loops, check=False)
794
+ self.add_vertices(data[0])
795
+ self.add_edges((u, v) for u in data[0] for v in data[0] if f(u, v))
796
+
797
+ elif format == "vertices_and_edges":
798
+ self.allow_multiple_edges(bool(multiedges), check=False)
799
+ self.allow_loops(bool(loops), check=False)
800
+ self.add_vertices(data[0])
801
+ self.add_edges(data[1])
802
+
803
+ elif format == 'dict_of_dicts':
804
+ from .graph_input import from_dict_of_dicts
805
+ from_dict_of_dicts(self, data, loops=loops, multiedges=multiedges, weighted=weighted,
806
+ convert_empty_dict_labels_to_None=False if convert_empty_dict_labels_to_None is None else convert_empty_dict_labels_to_None)
807
+
808
+ elif format == 'dict_of_lists':
809
+ from .graph_input import from_dict_of_lists
810
+ from_dict_of_lists(self, data, loops=loops, multiedges=multiedges, weighted=weighted)
811
+
812
+ elif format == 'NX':
813
+ from sage.graphs.graph_input import from_networkx_graph
814
+ from_networkx_graph(self, data,
815
+ weighted=weighted, multiedges=multiedges, loops=loops,
816
+ convert_empty_dict_labels_to_None=convert_empty_dict_labels_to_None)
817
+ if weighted is None:
818
+ weighted = self.allows_multiple_edges()
819
+
820
+ elif format == 'igraph':
821
+ if not data.is_directed():
822
+ raise ValueError("a *directed* igraph graph was expected. To "
823
+ "build an undirected graph, call the Graph "
824
+ "constructor")
825
+
826
+ self.add_vertices(range(data.vcount()))
827
+ self.add_edges((e.source, e.target, e.attributes()) for e in data.es())
828
+
829
+ if vertex_labels and 'name' in data.vertex_attributes():
830
+ vs = data.vs()
831
+ self.relabel({v: vs[v]['name'] for v in self})
832
+
833
+ elif format == 'int':
834
+ if weighted is None:
835
+ weighted = False
836
+ self.allow_loops(bool(loops), check=False)
837
+ self.allow_multiple_edges(bool(multiedges),
838
+ check=False)
839
+ if data < 0:
840
+ raise ValueError("the number of vertices cannot be strictly negative")
841
+ elif data:
842
+ self.add_vertices(range(data))
843
+ elif format == 'list_of_edges':
844
+ self.allow_multiple_edges(bool(multiedges),
845
+ check=False)
846
+ self.allow_loops(bool(loops), check=False)
847
+ self.add_edges(data)
848
+ else:
849
+ raise ValueError("unknown input format '{}'".format(format))
850
+
851
+ # weighted, multiedges, loops, verts and num_verts should now be set
852
+ self._weighted = weighted
853
+
854
+ if hash_labels is None and hasattr(data, '_hash_labels'):
855
+ hash_labels = data._hash_labels
856
+ self._hash_labels = hash_labels
857
+
858
+ self._pos = copy(pos)
859
+
860
+ if format != 'DiGraph' or name is not None:
861
+ self.name(name)
862
+
863
+ if data_structure == "static_sparse":
864
+ from sage.graphs.base.static_sparse_backend import StaticSparseBackend
865
+ ib = StaticSparseBackend(self,
866
+ loops=self.allows_loops(),
867
+ multiedges=self.allows_multiple_edges())
868
+ self._backend = ib
869
+ self._immutable = True
870
+
871
+ # Formats
872
+
873
+ def dig6_string(self):
874
+ r"""
875
+ Return the ``dig6`` representation of the digraph as an ASCII string.
876
+
877
+ This is only valid for simple (no multiple edges) digraphs on at most
878
+ `2^{18} - 1 = 262143` vertices.
879
+
880
+ .. NOTE::
881
+
882
+ As the ``dig6`` format only handles graphs with vertex set `\{0,
883
+ \ldots, n-1\}`, a :meth:`relabelled copy
884
+ <sage.graphs.generic_graph.GenericGraph.relabel>` will be encoded,
885
+ if necessary.
886
+
887
+ .. SEEALSO::
888
+
889
+ * :meth:`~sage.graphs.graph.Graph.graph6_string` -- a similar string
890
+ format for undirected graphs
891
+
892
+ EXAMPLES::
893
+
894
+ sage: D = DiGraph({0: [1, 2], 1: [2], 2: [3], 3: [0]})
895
+ sage: D.dig6_string()
896
+ 'CW`_'
897
+ sage: L = DiGraph({0: [1, 2], 1: [2], 2: [3], 3: [3]})
898
+ sage: L.dig6_string()
899
+ 'CW`C'
900
+
901
+ TESTS::
902
+
903
+ sage: DiGraph().dig6_string()
904
+ '?'
905
+ """
906
+ n = self.order()
907
+ if n > 262143:
908
+ raise ValueError('dig6 format supports graphs on 0 to 262143 vertices only')
909
+ elif self.has_multiple_edges():
910
+ raise ValueError('dig6 format does not support multiple edges')
911
+ return generic_graph_pyx.small_integer_to_graph6(n) + generic_graph_pyx.binary_string_to_graph6(self._bit_vector())
912
+
913
+ # Attributes
914
+
915
+ def is_directed(self):
916
+ """
917
+ Since digraph is directed, return ``True``.
918
+
919
+ EXAMPLES::
920
+
921
+ sage: DiGraph().is_directed()
922
+ True
923
+ """
924
+ return True
925
+
926
+ # Properties
927
+
928
+ def is_directed_acyclic(self, certificate=False):
929
+ r"""
930
+ Check whether the digraph is acyclic or not.
931
+
932
+ A directed graph is acyclic if for any vertex `v`, there is no directed
933
+ path that starts and ends at `v`. Every directed acyclic graph (DAG)
934
+ corresponds to a partial ordering of its vertices, however multiple dags
935
+ may lead to the same partial ordering.
936
+
937
+ INPUT:
938
+
939
+ - ``certificate`` -- boolean (default: ``False``); whether to return a
940
+ certificate
941
+
942
+ OUTPUT:
943
+
944
+ * When ``certificate=False``, returns a boolean value
945
+
946
+ * When ``certificate=True``:
947
+
948
+ * If the graph is acyclic, returns a pair ``(True, ordering)`` where
949
+ ``ordering`` is a list of the vertices such that `u` appears
950
+ before `v` in ``ordering`` if `uv` is an edge.
951
+
952
+ * Else, returns a pair ``(False, cycle)`` where ``cycle`` is a list of
953
+ vertices representing a circuit in the graph.
954
+
955
+ EXAMPLES:
956
+
957
+ At first, the following graph is acyclic::
958
+
959
+ sage: D = DiGraph({0:[1, 2, 3], 4:[2, 5], 1:[8], 2:[7], 3:[7], 5:[6,7], 7:[8], 6:[9], 8:[10], 9:[10]})
960
+ sage: D.plot(layout='circular').show() # needs sage.plot
961
+ sage: D.is_directed_acyclic()
962
+ True
963
+
964
+ Adding an edge from `9` to `7` does not change it::
965
+
966
+ sage: D.add_edge(9, 7)
967
+ sage: D.is_directed_acyclic()
968
+ True
969
+
970
+ We can obtain as a proof an ordering of the vertices such that `u`
971
+ appears before `v` if `uv` is an edge of the graph::
972
+
973
+ sage: D.is_directed_acyclic(certificate=True)
974
+ (True, [4, 5, 6, 9, 0, 1, 2, 3, 7, 8, 10])
975
+
976
+ Adding an edge from 7 to 4, though, makes a difference::
977
+
978
+ sage: D.add_edge(7, 4)
979
+ sage: D.is_directed_acyclic()
980
+ False
981
+
982
+ Indeed, it creates a circuit `7, 4, 5`::
983
+
984
+ sage: D.is_directed_acyclic(certificate=True)
985
+ (False, [7, 4, 5])
986
+
987
+ Checking acyclic graphs are indeed acyclic ::
988
+
989
+ sage: def random_acyclic(n, p):
990
+ ....: g = graphs.RandomGNP(n, p)
991
+ ....: h = DiGraph()
992
+ ....: h.add_edges(((u, v) if u < v else (v, u)) for u, v in g.edge_iterator(labels=False))
993
+ ....: return h
994
+ ...
995
+ sage: all(random_acyclic(100, .2).is_directed_acyclic() # long time
996
+ ....: for i in range(50))
997
+ True
998
+
999
+ TESTS:
1000
+
1001
+ What about loops? ::
1002
+
1003
+ sage: g = digraphs.ButterflyGraph(3)
1004
+ sage: g.allow_loops(True)
1005
+ sage: g.is_directed_acyclic()
1006
+ True
1007
+ sage: g.add_edge(0, 0)
1008
+ sage: g.is_directed_acyclic()
1009
+ False
1010
+ """
1011
+ return self._backend.is_directed_acyclic(certificate=certificate)
1012
+
1013
+ def to_directed(self):
1014
+ """
1015
+ Since the graph is already directed, simply returns a copy of itself.
1016
+
1017
+ EXAMPLES::
1018
+
1019
+ sage: DiGraph({0: [1, 2, 3], 4: [5, 1]}).to_directed()
1020
+ Digraph on 6 vertices
1021
+ """
1022
+ return self.copy()
1023
+
1024
+ def to_undirected(self, data_structure=None, sparse=None):
1025
+ """
1026
+ Return an undirected version of the graph.
1027
+
1028
+ Every directed edge becomes an edge.
1029
+
1030
+ INPUT:
1031
+
1032
+ - ``data_structure`` -- string (default: ``None``); one of
1033
+ ``'sparse'``, ``'static_sparse'``, or ``'dense'``. See the
1034
+ documentation of :class:`Graph` or :class:`DiGraph`.
1035
+
1036
+ - ``sparse`` -- boolean (default: ``None``); ``sparse=True`` is an
1037
+ alias for ``data_structure="sparse"``, and ``sparse=False`` is an
1038
+ alias for ``data_structure="dense"``.
1039
+
1040
+ EXAMPLES::
1041
+
1042
+ sage: D = DiGraph({0: [1, 2], 1: [0]})
1043
+ sage: G = D.to_undirected()
1044
+ sage: D.edges(sort=True, labels=False)
1045
+ [(0, 1), (0, 2), (1, 0)]
1046
+ sage: G.edges(sort=True, labels=False)
1047
+ [(0, 1), (0, 2)]
1048
+
1049
+ TESTS:
1050
+
1051
+ Immutable graphs yield immutable graphs (:issue:`17005`)::
1052
+
1053
+ sage: DiGraph([[1, 2]], immutable=True).to_undirected()._backend
1054
+ <sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...>
1055
+
1056
+ Vertex labels will be retained (:issue:`14708`)::
1057
+
1058
+ sage: D.set_vertex(0, 'foo')
1059
+ sage: G = D.to_undirected()
1060
+ sage: D.get_vertices()
1061
+ {0: 'foo', 1: None, 2: None}
1062
+ sage: G.get_vertices()
1063
+ {0: 'foo', 1: None, 2: None}
1064
+ """
1065
+ if sparse is not None:
1066
+ if data_structure is not None:
1067
+ raise ValueError("the 'sparse' argument is an alias for "
1068
+ "'data_structure'. Please do not define both")
1069
+ data_structure = "sparse" if sparse else "dense"
1070
+
1071
+ if data_structure is None:
1072
+ from sage.graphs.base.dense_graph import DenseGraphBackend
1073
+ from sage.graphs.base.sparse_graph import SparseGraphBackend
1074
+ if isinstance(self._backend, DenseGraphBackend):
1075
+ data_structure = "dense"
1076
+ elif isinstance(self._backend, SparseGraphBackend):
1077
+ data_structure = "sparse"
1078
+ else:
1079
+ data_structure = "static_sparse"
1080
+ from sage.graphs.graph import Graph
1081
+ G = Graph(name=self.name(),
1082
+ pos=self._pos,
1083
+ multiedges=self.allows_multiple_edges(),
1084
+ loops=self.allows_loops(),
1085
+ data_structure=(data_structure if data_structure != "static_sparse"
1086
+ else "sparse")) # we need a mutable copy first
1087
+
1088
+ G.add_vertices(self.vertex_iterator())
1089
+ G.set_vertices(self.get_vertices())
1090
+ G.add_edges(self.edge_iterator())
1091
+ G._copy_attribute_from(self, '_assoc')
1092
+ G._copy_attribute_from(self, '_embedding')
1093
+ G._weighted = self._weighted
1094
+
1095
+ if data_structure == "static_sparse":
1096
+ G = G.copy(data_structure=data_structure)
1097
+
1098
+ return G
1099
+
1100
+ # Edge Handlers
1101
+
1102
+ def incoming_edge_iterator(self, vertices, labels=True):
1103
+ """
1104
+ Return an iterator over all arriving edges from vertices.
1105
+
1106
+ INPUT:
1107
+
1108
+ - ``vertices`` -- a vertex or a list of vertices
1109
+
1110
+ - ``labels`` -- boolean (default: ``True``); whether to return edges as
1111
+ pairs of vertices, or as triples containing the labels
1112
+
1113
+ EXAMPLES::
1114
+
1115
+ sage: D = DiGraph({0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]})
1116
+ sage: for a in D.incoming_edge_iterator([0]):
1117
+ ....: print(a)
1118
+ (1, 0, None)
1119
+ (4, 0, None)
1120
+ """
1121
+ if vertices is None:
1122
+ vertices = self
1123
+ elif vertices in self:
1124
+ vertices = [vertices]
1125
+ else:
1126
+ vertices = [v for v in vertices if v in self]
1127
+ return self._backend.iterator_in_edges(vertices, labels)
1128
+
1129
+ def incoming_edges(self, vertices, labels=True):
1130
+ """
1131
+ Return a list of edges arriving at vertices.
1132
+
1133
+ INPUT:
1134
+
1135
+ - ``vertices`` -- a vertex or a list of vertices
1136
+
1137
+ - ``labels`` -- boolean (default: ``True``); whether to return edges as
1138
+ pairs of vertices, or as triples containing the labels
1139
+
1140
+ EXAMPLES::
1141
+
1142
+ sage: D = DiGraph({0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]})
1143
+ sage: D.incoming_edges([0])
1144
+ [(1, 0, None), (4, 0, None)]
1145
+ """
1146
+ return list(self.incoming_edge_iterator(vertices, labels=labels))
1147
+
1148
+ def outgoing_edge_iterator(self, vertices, labels=True):
1149
+ """
1150
+ Return an iterator over all departing edges from vertices.
1151
+
1152
+ INPUT:
1153
+
1154
+ - ``vertices`` -- a vertex or a list of vertices
1155
+
1156
+ - ``labels`` -- boolean (default: ``True``); whether to return edges as
1157
+ pairs of vertices, or as triples containing the labels
1158
+
1159
+ EXAMPLES::
1160
+
1161
+ sage: D = DiGraph({0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]})
1162
+ sage: for a in D.outgoing_edge_iterator([0]):
1163
+ ....: print(a)
1164
+ (0, 1, None)
1165
+ (0, 2, None)
1166
+ (0, 3, None)
1167
+ """
1168
+ if vertices is None:
1169
+ vertices = self
1170
+ elif vertices in self:
1171
+ vertices = [vertices]
1172
+ else:
1173
+ vertices = [v for v in vertices if v in self]
1174
+ return self._backend.iterator_out_edges(vertices, labels)
1175
+
1176
+ def outgoing_edges(self, vertices, labels=True):
1177
+ """
1178
+ Return a list of edges departing from vertices.
1179
+
1180
+ INPUT:
1181
+
1182
+ - ``vertices`` -- a vertex or a list of vertices
1183
+
1184
+ - ``labels`` -- boolean (default: ``True``); whether to return edges as
1185
+ pairs of vertices, or as triples containing the labels
1186
+
1187
+ EXAMPLES::
1188
+
1189
+ sage: D = DiGraph({0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]})
1190
+ sage: D.outgoing_edges([0])
1191
+ [(0, 1, None), (0, 2, None), (0, 3, None)]
1192
+ """
1193
+ return list(self.outgoing_edge_iterator(vertices, labels=labels))
1194
+
1195
+ def neighbor_in_iterator(self, vertex):
1196
+ """
1197
+ Return an iterator over the in-neighbors of ``vertex``.
1198
+
1199
+ A vertex `u` is an in-neighbor of a vertex `v` if `uv` in an edge.
1200
+
1201
+ EXAMPLES::
1202
+
1203
+ sage: D = DiGraph({0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]})
1204
+ sage: for a in D.neighbor_in_iterator(0):
1205
+ ....: print(a)
1206
+ 1
1207
+ 4
1208
+
1209
+ TESTS:
1210
+
1211
+ With multiple edges, check that the neighbors are listed only once::
1212
+
1213
+ sage: D = DiGraph([[0, 1, 2], [(0, 1), (0, 1), (1, 2) ]],multiedges=True)
1214
+ sage: list(D.neighbor_in_iterator(0))
1215
+ []
1216
+ sage: list(D.neighbor_in_iterator(1))
1217
+ [0]
1218
+ sage: list(D.neighbor_in_iterator(2))
1219
+ [1]
1220
+
1221
+ Check that the iterator lists ``vertex`` in the presence of loop(s):
1222
+
1223
+ sage: D = DiGraph([[0, 1, 2], [(0, 0), (0, 0), (1, 1)]],multiedges=True, loops=True)
1224
+ sage: list(D.neighbor_in_iterator(0))
1225
+ [0]
1226
+ sage: list(D.neighbor_in_iterator(1))
1227
+ [1]
1228
+ sage: list(D.neighbor_in_iterator(2))
1229
+ []
1230
+
1231
+ """
1232
+ yield from self._backend.iterator_in_nbrs(vertex)
1233
+
1234
+ def neighbors_in(self, vertex):
1235
+ """
1236
+ Return the list of the in-neighbors of a given vertex.
1237
+
1238
+ A vertex `u` is an in-neighbor of a vertex `v` if `uv` in an edge.
1239
+
1240
+ EXAMPLES::
1241
+
1242
+ sage: D = DiGraph({0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]})
1243
+ sage: D.neighbors_in(0)
1244
+ [1, 4]
1245
+ """
1246
+ return list(self.neighbor_in_iterator(vertex))
1247
+
1248
+ def neighbor_out_iterator(self, vertex):
1249
+ """
1250
+ Return an iterator over the out-neighbors of a given vertex.
1251
+
1252
+ A vertex `u` is an out-neighbor of a vertex `v` if `vu` in an edge.
1253
+
1254
+ EXAMPLES::
1255
+
1256
+ sage: D = DiGraph({0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]})
1257
+ sage: for a in D.neighbor_out_iterator(0):
1258
+ ....: print(a)
1259
+ 1
1260
+ 2
1261
+ 3
1262
+
1263
+ TESTS:
1264
+
1265
+ With multiple edges, check that the neighbors are listed only once::
1266
+
1267
+ sage: D = DiGraph([[0, 1, 2], [(0, 1), (0, 1), (1, 2) ]],multiedges=True)
1268
+ sage: list(D.neighbor_out_iterator(0))
1269
+ [1]
1270
+ sage: list(D.neighbor_out_iterator(1))
1271
+ [2]
1272
+ sage: list(D.neighbor_out_iterator(2))
1273
+ []
1274
+
1275
+ Check that the iterator lists ``vertex`` in the presence of loop(s):
1276
+
1277
+ sage: D = DiGraph([[0, 1, 2], [(0, 0), (0, 0), (1, 1)]],multiedges=True, loops=True)
1278
+ sage: list(D.neighbor_out_iterator(0))
1279
+ [0]
1280
+ sage: list(D.neighbor_out_iterator(1))
1281
+ [1]
1282
+ sage: list(D.neighbor_out_iterator(2))
1283
+ []
1284
+
1285
+ """
1286
+ yield from self._backend.iterator_out_nbrs(vertex)
1287
+
1288
+ def neighbors_out(self, vertex):
1289
+ """
1290
+ Return the list of the out-neighbors of a given vertex.
1291
+
1292
+ A vertex `u` is an out-neighbor of a vertex `v` if `vu` in an edge.
1293
+
1294
+ EXAMPLES::
1295
+
1296
+ sage: D = DiGraph({0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]})
1297
+ sage: D.neighbors_out(0)
1298
+ [1, 2, 3]
1299
+ """
1300
+ return list(self.neighbor_out_iterator(vertex))
1301
+
1302
+ # Degree functions
1303
+
1304
+ def in_degree(self, vertices=None, labels=False):
1305
+ """
1306
+ Same as degree, but for in degree.
1307
+
1308
+ EXAMPLES::
1309
+
1310
+ sage: D = DiGraph({0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]})
1311
+ sage: D.in_degree(vertices=[0, 1, 2], labels=True)
1312
+ {0: 2, 1: 2, 2: 2}
1313
+ sage: D.in_degree()
1314
+ [2, 2, 2, 2, 1, 1]
1315
+ sage: G = graphs.PetersenGraph().to_directed()
1316
+ sage: G.in_degree(0)
1317
+ 3
1318
+ """
1319
+ if vertices in self:
1320
+ return self._backend.in_degree(vertices)
1321
+ elif labels:
1322
+ return dict(self.in_degree_iterator(vertices, labels=labels))
1323
+ return list(self.in_degree_iterator(vertices, labels=labels))
1324
+
1325
+ def in_degree_iterator(self, vertices=None, labels=False):
1326
+ """
1327
+ Same as degree_iterator, but for in degree.
1328
+
1329
+ EXAMPLES::
1330
+
1331
+ sage: D = graphs.Grid2dGraph(2,4).to_directed()
1332
+ sage: sorted(D.in_degree_iterator())
1333
+ [2, 2, 2, 2, 3, 3, 3, 3]
1334
+ sage: sorted(D.in_degree_iterator(labels=True))
1335
+ [((0, 0), 2),
1336
+ ((0, 1), 3),
1337
+ ((0, 2), 3),
1338
+ ((0, 3), 2),
1339
+ ((1, 0), 2),
1340
+ ((1, 1), 3),
1341
+ ((1, 2), 3),
1342
+ ((1, 3), 2)]
1343
+ """
1344
+ if vertices is None:
1345
+ vertices = self.vertex_iterator()
1346
+ if labels:
1347
+ for v in vertices:
1348
+ yield (v, self.in_degree(v))
1349
+ else:
1350
+ for v in vertices:
1351
+ yield self.in_degree(v)
1352
+
1353
+ def in_degree_sequence(self):
1354
+ r"""
1355
+ Return the in-degree sequence.
1356
+
1357
+ EXAMPLES:
1358
+
1359
+ The in-degree sequences of two digraphs::
1360
+
1361
+ sage: g = DiGraph({1: [2, 5, 6], 2: [3, 6], 3: [4, 6], 4: [6], 5: [4, 6]})
1362
+ sage: g.in_degree_sequence()
1363
+ [5, 2, 1, 1, 1, 0]
1364
+
1365
+ ::
1366
+
1367
+ sage: V = [2, 3, 5, 7, 8, 9, 10, 11]
1368
+ sage: E = [[], [8, 10], [11], [8, 11], [9], [], [], [2, 9, 10]]
1369
+ sage: g = DiGraph(dict(zip(V, E)))
1370
+ sage: g.in_degree_sequence()
1371
+ [2, 2, 2, 2, 1, 0, 0, 0]
1372
+ """
1373
+ return sorted(self.in_degree_iterator(), reverse=True)
1374
+
1375
+ def out_degree(self, vertices=None, labels=False):
1376
+ """
1377
+ Same as degree, but for out degree.
1378
+
1379
+ EXAMPLES::
1380
+
1381
+ sage: D = DiGraph({0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]})
1382
+ sage: D.out_degree(vertices=[0, 1 ,2], labels=True)
1383
+ {0: 3, 1: 2, 2: 1}
1384
+ sage: D.out_degree()
1385
+ [3, 2, 1, 1, 2, 1]
1386
+ sage: D.out_degree(2)
1387
+ 1
1388
+ """
1389
+ if vertices in self:
1390
+ return self._backend.out_degree(vertices)
1391
+ elif labels:
1392
+ return dict(self.out_degree_iterator(vertices, labels=labels))
1393
+ return list(self.out_degree_iterator(vertices, labels=labels))
1394
+
1395
+ def out_degree_iterator(self, vertices=None, labels=False):
1396
+ """
1397
+ Same as degree_iterator, but for out degree.
1398
+
1399
+ EXAMPLES::
1400
+
1401
+ sage: D = graphs.Grid2dGraph(2,4).to_directed()
1402
+ sage: sorted(D.out_degree_iterator())
1403
+ [2, 2, 2, 2, 3, 3, 3, 3]
1404
+ sage: sorted(D.out_degree_iterator(labels=True))
1405
+ [((0, 0), 2),
1406
+ ((0, 1), 3),
1407
+ ((0, 2), 3),
1408
+ ((0, 3), 2),
1409
+ ((1, 0), 2),
1410
+ ((1, 1), 3),
1411
+ ((1, 2), 3),
1412
+ ((1, 3), 2)]
1413
+ """
1414
+ if vertices is None:
1415
+ vertices = self.vertex_iterator()
1416
+ if labels:
1417
+ for v in vertices:
1418
+ yield (v, self.out_degree(v))
1419
+ else:
1420
+ for v in vertices:
1421
+ yield self.out_degree(v)
1422
+
1423
+ def out_degree_sequence(self):
1424
+ r"""
1425
+ Return the outdegree sequence of this digraph.
1426
+
1427
+ EXAMPLES:
1428
+
1429
+ The outdegree sequences of two digraphs::
1430
+
1431
+ sage: g = DiGraph({1: [2, 5, 6], 2: [3, 6], 3: [4, 6], 4: [6], 5: [4, 6]})
1432
+ sage: g.out_degree_sequence()
1433
+ [3, 2, 2, 2, 1, 0]
1434
+
1435
+ ::
1436
+
1437
+ sage: V = [2, 3, 5, 7, 8, 9, 10, 11]
1438
+ sage: E = [[], [8, 10], [11], [8, 11], [9], [], [], [2, 9, 10]]
1439
+ sage: g = DiGraph(dict(zip(V, E)))
1440
+ sage: g.out_degree_sequence()
1441
+ [3, 2, 2, 1, 1, 0, 0, 0]
1442
+ """
1443
+ return sorted(self.out_degree_iterator(), reverse=True)
1444
+
1445
+ def sources(self):
1446
+ r"""
1447
+ Return a list of sources of the digraph.
1448
+
1449
+ OUTPUT: list of the vertices of the digraph that have no edges going into them
1450
+
1451
+ EXAMPLES::
1452
+
1453
+ sage: G = DiGraph({1: {3: ['a']}, 2: {3: ['b']}})
1454
+ sage: G.sources()
1455
+ [1, 2]
1456
+ sage: T = DiGraph({1: {}})
1457
+ sage: T.sources()
1458
+ [1]
1459
+ """
1460
+ return [x for x in self if not self.in_degree(x)]
1461
+
1462
+ def sinks(self):
1463
+ """
1464
+ Return a list of sinks of the digraph.
1465
+
1466
+ OUTPUT: list of the vertices of the digraph that have no edges beginning at them
1467
+
1468
+ EXAMPLES::
1469
+
1470
+ sage: G = DiGraph({1: {3: ['a']}, 2: {3: ['b']}})
1471
+ sage: G.sinks()
1472
+ [3]
1473
+ sage: T = DiGraph({1: {}})
1474
+ sage: T.sinks()
1475
+ [1]
1476
+ """
1477
+ return [x for x in self if not self.out_degree(x)]
1478
+
1479
+ def degree_polynomial(self):
1480
+ r"""
1481
+ Return the generating polynomial of degrees of vertices in ``self``.
1482
+
1483
+ This is the sum
1484
+
1485
+ .. MATH::
1486
+
1487
+ \sum_{v \in G} x^{\operatorname{in}(v)} y^{\operatorname{out}(v)},
1488
+
1489
+ where ``in(v)`` and ``out(v)`` are the number of incoming and outgoing
1490
+ edges at vertex `v` in the digraph `G`.
1491
+
1492
+ Because this polynomial is multiplicative for Cartesian product of
1493
+ digraphs, it is useful to help see if the digraph can be isomorphic to a
1494
+ Cartesian product.
1495
+
1496
+ .. SEEALSO::
1497
+
1498
+ :meth:`num_verts` for the value at `(x, y) = (1, 1)`
1499
+
1500
+ EXAMPLES::
1501
+
1502
+ sage: G = posets.PentagonPoset().hasse_diagram() # needs sage.modules
1503
+ sage: G.degree_polynomial() # needs sage.modules
1504
+ x^2 + 3*x*y + y^2
1505
+
1506
+ sage: G = posets.BooleanLattice(4).hasse_diagram()
1507
+ sage: G.degree_polynomial().factor() # needs sage.libs.pari
1508
+ (x + y)^4
1509
+ """
1510
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
1511
+ R = PolynomialRing(ZZ, 'x,y')
1512
+ x, y = R.gens()
1513
+ return R.sum(x ** self.in_degree(v) * y ** self.out_degree(v) for v in self)
1514
+
1515
+ def feedback_edge_set(self, constraint_generation=True, value_only=False,
1516
+ solver=None, verbose=0, *, integrality_tolerance=1e-3):
1517
+ r"""
1518
+ Compute the minimum feedback edge set of a digraph (also called
1519
+ feedback arc set).
1520
+
1521
+ The minimum feedback edge set of a digraph is a set of edges that
1522
+ intersect all the circuits of the digraph. Equivalently, a minimum
1523
+ feedback arc set of a DiGraph is a set `S` of arcs such that the digraph
1524
+ `G - S` is acyclic. For more information, see the
1525
+ :wikipedia:`Feedback_arc_set`.
1526
+
1527
+ INPUT:
1528
+
1529
+ - ``value_only`` -- boolean (default: ``False``)
1530
+
1531
+ - When set to ``True``, only the minimum cardinal of a minimum edge
1532
+ set is returned.
1533
+
1534
+ - When set to ``False``, the ``Set`` of edges of a minimal edge set is
1535
+ returned.
1536
+
1537
+ - ``constraint_generation`` -- boolean (default: ``True``); whether to
1538
+ use constraint generation when solving the Mixed Integer Linear
1539
+ Program.
1540
+
1541
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer
1542
+ Linear Programming (MILP) solver to be used. If set to ``None``, the
1543
+ default one is used. For more information on MILP solvers and which
1544
+ default solver is used, see the method :meth:`solve
1545
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1546
+ :class:`MixedIntegerLinearProgram
1547
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1548
+
1549
+ - ``verbose`` -- integer (default: 0); sets the level of
1550
+ verbosity. Set to 0 by default, which means quiet.
1551
+
1552
+ - ``integrality_tolerance`` -- float; parameter for use with MILP
1553
+ solvers over an inexact base ring; see
1554
+ :meth:`MixedIntegerLinearProgram.get_values`.
1555
+
1556
+ ALGORITHM:
1557
+
1558
+ This problem is solved using Linear Programming, in two different
1559
+ ways. The first one is to solve the following formulation:
1560
+
1561
+ .. MATH::
1562
+
1563
+ \mbox{Minimize : }&\sum_{(u,v)\in G} b_{(u,v)}\\
1564
+ \mbox{Such that : }&\\
1565
+ &\forall (u,v)\in G, d_u-d_v+ n \cdot b_{(u,v)}\geq 0\\
1566
+ &\forall u\in G, 0\leq d_u\leq |G|\\
1567
+
1568
+ An explanation:
1569
+
1570
+ An acyclic digraph can be seen as a poset, and every poset has a linear
1571
+ extension. This means that in any acyclic digraph the vertices can be
1572
+ ordered with a total order `<` in such a way that if `(u,v) \in G`, then
1573
+ `u < v`.
1574
+
1575
+ Thus, this linear program is built in order to assign to each vertex `v`
1576
+ a number `d_v \in [0,\dots,n-1]` such that if there exists an edge
1577
+ `(u, v) \in G` such that `d_v < d_u`, then the edge `(u,v)` is removed.
1578
+
1579
+ The number of edges removed is then minimized, which is the objective.
1580
+
1581
+ (Constraint Generation)
1582
+
1583
+ If the parameter ``constraint_generation`` is enabled, a more efficient
1584
+ formulation is used :
1585
+
1586
+ .. MATH::
1587
+
1588
+ \mbox{Minimize : }&\sum_{(u,v)\in G} b_{(u,v)}\\
1589
+ \mbox{Such that : }&\\
1590
+ &\forall C\text{ circuits }\subseteq G, \sum_{uv\in C}b_{(u,v)}\geq 1\\
1591
+
1592
+ As the number of circuits contained in a graph is exponential, this LP
1593
+ is solved through constraint generation. This means that the solver is
1594
+ sequentially asked to solved the problem, knowing only a portion of the
1595
+ circuits contained in `G`, each time adding to the list of its
1596
+ constraints the circuit which its last answer had left intact.
1597
+
1598
+ EXAMPLES:
1599
+
1600
+ If the digraph is created from a graph, and hence is symmetric (if `uv`
1601
+ is an edge, then `vu` is an edge too), then obviously the cardinality of
1602
+ its feedback arc set is the number of edges in the first graph::
1603
+
1604
+ sage: cycle = graphs.CycleGraph(5)
1605
+ sage: dcycle = DiGraph(cycle)
1606
+ sage: cycle.size()
1607
+ 5
1608
+ sage: dcycle.feedback_edge_set(value_only=True) # needs sage.numerical.mip
1609
+ 5
1610
+
1611
+ And in this situation, for any edge `uv` of the first graph, `uv` of
1612
+ `vu` is in the returned feedback arc set::
1613
+
1614
+ sage: g = graphs.RandomGNP(5,.3)
1615
+ sage: while not g.num_edges():
1616
+ ....: g = graphs.RandomGNP(5,.3)
1617
+ sage: dg = DiGraph(g)
1618
+ sage: feedback = dg.feedback_edge_set() # needs sage.numerical.mip
1619
+ sage: u,v,l = next(g.edge_iterator())
1620
+ sage: (u,v) in feedback or (v,u) in feedback # needs sage.numerical.mip
1621
+ True
1622
+
1623
+ TESTS:
1624
+
1625
+ Comparing with/without constraint generation. Also double-checks issue
1626
+ :issue:`12833`::
1627
+
1628
+ sage: for i in range(20): # needs sage.numerical.mip
1629
+ ....: g = digraphs.RandomDirectedGNP(10, .3)
1630
+ ....: x = g.feedback_edge_set(value_only=True)
1631
+ ....: y = g.feedback_edge_set(value_only=True,
1632
+ ....: constraint_generation=False)
1633
+ ....: if x != y:
1634
+ ....: print("Oh my, oh my !")
1635
+ ....: break
1636
+
1637
+ Loops are part of the feedback edge set (:issue:`23989`)::
1638
+
1639
+ sage: # needs sage.combinat
1640
+ sage: D = digraphs.DeBruijn(2, 2)
1641
+ sage: sorted(D.loops(labels=None))
1642
+ [('00', '00'), ('11', '11')]
1643
+ sage: FAS = D.feedback_edge_set(value_only=False) # needs sage.numerical.mip
1644
+ sage: all(l in FAS for l in D.loops(labels=None)) # needs sage.numerical.mip
1645
+ True
1646
+ sage: FAS2 = D.feedback_edge_set(value_only=False, # needs sage.numerical.mip
1647
+ ....: constraint_generation=False)
1648
+ sage: len(FAS) == len(FAS2) # needs sage.numerical.mip
1649
+ True
1650
+
1651
+ Check that multi-edges are properly taken into account::
1652
+
1653
+ sage: cycle = graphs.CycleGraph(5)
1654
+ sage: dcycle = DiGraph(cycle)
1655
+ sage: dcycle.feedback_edge_set(value_only=True) # needs sage.numerical.mip
1656
+ 5
1657
+ sage: dcycle.allow_multiple_edges(True)
1658
+ sage: dcycle.add_edges(dcycle.edges(sort=True))
1659
+ sage: dcycle.feedback_edge_set(value_only=True) # needs sage.numerical.mip
1660
+ 10
1661
+ sage: dcycle.feedback_edge_set(value_only=True, # needs sage.numerical.mip
1662
+ ....: constraint_generation=False)
1663
+ 10
1664
+
1665
+ Strongly connected components are well handled (:issue:`23989`)::
1666
+
1667
+ sage: g = digraphs.Circuit(3) * 2
1668
+ sage: g.add_edge(0, 3)
1669
+ sage: g.feedback_edge_set(value_only=True) # needs sage.numerical.mip
1670
+ 2
1671
+ """
1672
+ # It would be a pity to start a LP if the digraph is already acyclic
1673
+ if self.is_directed_acyclic():
1674
+ return 0 if value_only else []
1675
+
1676
+ if self.has_loops():
1677
+ # We solve the problem on a copy without loops of the digraph
1678
+ D = DiGraph(self.edges(sort=False), multiedges=self.allows_multiple_edges(), loops=True)
1679
+ loops = D.loops(labels=None)
1680
+ D.delete_edges(loops)
1681
+ D.allow_loops(False, check=False)
1682
+ FAS = D.feedback_edge_set(constraint_generation=constraint_generation,
1683
+ value_only=value_only, solver=solver, verbose=verbose,
1684
+ integrality_tolerance=integrality_tolerance)
1685
+ if value_only:
1686
+ return FAS + len(loops)
1687
+ return FAS + loops
1688
+
1689
+ if not self.is_strongly_connected():
1690
+ # If the digraph is not strongly connected, we solve the problem on
1691
+ # each of its strongly connected components
1692
+
1693
+ FAS = 0 if value_only else []
1694
+
1695
+ for h in self.strongly_connected_components_subgraphs():
1696
+ if not h.size():
1697
+ continue
1698
+ if value_only:
1699
+ FAS += h.feedback_edge_set(constraint_generation=constraint_generation,
1700
+ value_only=True, solver=solver, verbose=verbose,
1701
+ integrality_tolerance=integrality_tolerance)
1702
+ else:
1703
+ FAS.extend(h.feedback_edge_set(constraint_generation=constraint_generation,
1704
+ value_only=False, solver=solver, verbose=verbose,
1705
+ integrality_tolerance=integrality_tolerance))
1706
+ return FAS
1707
+
1708
+ from sage.numerical.mip import MixedIntegerLinearProgram
1709
+
1710
+ ########################################
1711
+ # Constraint Generation Implementation #
1712
+ ########################################
1713
+ if constraint_generation:
1714
+
1715
+ p = MixedIntegerLinearProgram(constraint_generation=True,
1716
+ maximization=False, solver=solver)
1717
+
1718
+ # A variable for each edge
1719
+ b = p.new_variable(binary=True)
1720
+
1721
+ # Variables are binary, and their coefficient in the objective is
1722
+ # the number of occurrences of the corresponding edge, so 1 if the
1723
+ # graph is simple
1724
+ p.set_objective(p.sum(b[e] for e in self.edge_iterator(labels=False)))
1725
+
1726
+ # For as long as we do not break because the digraph is acyclic....
1727
+ while True:
1728
+
1729
+ # Building the graph without the edges removed by the MILP
1730
+ p.solve(log=verbose)
1731
+ val = p.get_values(b, convert=bool, tolerance=integrality_tolerance)
1732
+ h = DiGraph([e for e in self.edge_iterator(labels=False) if not val[e]],
1733
+ format='list_of_edges')
1734
+
1735
+ # Is the digraph acyclic ?
1736
+ isok, certificate = h.is_directed_acyclic(certificate=True)
1737
+
1738
+ # If so, we are done !
1739
+ if isok:
1740
+ if value_only:
1741
+ return sum(1 for e in self.edge_iterator(labels=False) if val[e])
1742
+ # listing the edges contained in the MFAS
1743
+ return [e for e in self.edge_iterator(labels=False) if val[e]]
1744
+
1745
+ # There is a circuit left. Let's add the corresponding
1746
+ # constraint !
1747
+ while not isok:
1748
+
1749
+ if verbose:
1750
+ print("Adding a constraint on circuit : {}".format(certificate))
1751
+
1752
+ edges = zip(certificate, certificate[1:] + [certificate[0]])
1753
+ p.add_constraint(p.sum(b[e] for e in edges), min=1)
1754
+
1755
+ # Is there another edge disjoint circuit ?
1756
+ # for python3, we need to recreate the zip iterator
1757
+ edges = zip(certificate, certificate[1:] + [certificate[0]])
1758
+ h.delete_edges(edges)
1759
+ isok, certificate = h.is_directed_acyclic(certificate=True)
1760
+
1761
+ ######################################
1762
+ # Ordering-based MILP Implementation #
1763
+ ######################################
1764
+ else:
1765
+ p = MixedIntegerLinearProgram(maximization=False, solver=solver)
1766
+
1767
+ b = p.new_variable(binary=True)
1768
+ d = p.new_variable(integer=True, nonnegative=True)
1769
+
1770
+ n = self.order()
1771
+
1772
+ for u, v in self.edge_iterator(labels=None):
1773
+ p.add_constraint(d[u] - d[v] + n * b[u, v], min=1)
1774
+
1775
+ for v in self:
1776
+ p.add_constraint(d[v] <= n)
1777
+
1778
+ p.set_objective(p.sum(b[e] for e in self.edge_iterator(labels=False)))
1779
+
1780
+ p.solve(log=verbose)
1781
+
1782
+ b_sol = p.get_values(b, convert=bool, tolerance=integrality_tolerance)
1783
+
1784
+ if value_only:
1785
+ return sum(1 for e in self.edge_iterator(labels=False) if b_sol[e])
1786
+ return [e for e in self.edge_iterator(labels=False) if b_sol[e]]
1787
+
1788
+ # Construction
1789
+
1790
+ def reverse(self, immutable=None):
1791
+ """
1792
+ Return a copy of digraph with edges reversed in direction.
1793
+
1794
+ INPUT:
1795
+
1796
+ - ``immutable`` -- boolean (default: ``None``); whether to return an
1797
+ immutable digraph or not. By default (``None``), the returned digraph
1798
+ has the same setting than ``self``. That is, if ``self`` is immutable,
1799
+ the returned digraph also is.
1800
+
1801
+ EXAMPLES::
1802
+
1803
+ sage: adj = {0: [1,2,3], 1: [0,2], 2: [3], 3: [4], 4: [0,5], 5: [1]}
1804
+ sage: D = DiGraph(adj)
1805
+ sage: R = D.reverse(); R
1806
+ Reverse of (): Digraph on 6 vertices
1807
+ sage: H = R.reverse()
1808
+ sage: adj == H.to_dictionary()
1809
+ True
1810
+
1811
+ TESTS::
1812
+
1813
+ sage: adj = {0: [1, 1], 1: [1]}
1814
+ sage: D = DiGraph(adj, immutable=True, multiedges=True, loops=True)
1815
+ sage: R = D.reverse()
1816
+ sage: R.is_immutable() and R.allows_loops() and R.allows_multiple_edges()
1817
+ True
1818
+ sage: adj == R.reverse().to_dictionary(multiple_edges=True)
1819
+ True
1820
+
1821
+ Check the behavior of parameter ``immutable``::
1822
+
1823
+ sage: D = DiGraph([(0, 1)], immutable=False)
1824
+ sage: R = D.reverse()
1825
+ sage: R.is_immutable()
1826
+ False
1827
+ sage: R = D.reverse(immutable=True)
1828
+ sage: R.is_immutable()
1829
+ True
1830
+ sage: H = R.reverse()
1831
+ sage: H.is_immutable()
1832
+ True
1833
+ sage: H = R.reverse(immutable=False)
1834
+ sage: H.is_immutable()
1835
+ False
1836
+ """
1837
+ from sage.graphs.base.dense_graph import DenseGraphBackend
1838
+ if isinstance(self._backend, DenseGraphBackend):
1839
+ data_structure = "dense"
1840
+ else:
1841
+ data_structure = "sparse"
1842
+
1843
+ H = DiGraph(data_structure=data_structure,
1844
+ multiedges=self.allows_multiple_edges(), loops=self.allows_loops(),
1845
+ pos=copy(self._pos), weighted=self.weighted(),
1846
+ hash_labels=self._hash_labels)
1847
+ H.add_vertices(self)
1848
+ H.add_edges((v, u, d) for u, v, d in self.edge_iterator())
1849
+ name = self.name()
1850
+ if name is None:
1851
+ name = ''
1852
+ H.name("Reverse of (%s)" % name)
1853
+
1854
+ # Copy attributes '_assoc' and '_embedding' if set
1855
+ H._copy_attribute_from(self, '_assoc')
1856
+ H._copy_attribute_from(self, '_embedding')
1857
+
1858
+ if immutable or (immutable is None and self.is_immutable()):
1859
+ return H.copy(immutable=True)
1860
+
1861
+ return H
1862
+
1863
+ def reverse_edge(self, u, v=None, label=None, inplace=True, multiedges=None):
1864
+ """
1865
+ Reverse the edge from `u` to `v`.
1866
+
1867
+ INPUT:
1868
+
1869
+ - ``inplace`` -- boolean (default: ``True``); if ``False``, a new
1870
+ digraph is created and returned as output, otherwise ``self`` is
1871
+ modified.
1872
+
1873
+ - ``multiedges`` -- boolean (default: ``None``); how to decide what
1874
+ should be done in case of doubt (for instance when edge `(1,2)` is to
1875
+ be reversed in a graph while `(2,1)` already exists):
1876
+
1877
+ - If set to ``True``, input graph will be forced to allow parallel
1878
+ edges if necessary and edge `(1,2)` will appear twice in the graph.
1879
+
1880
+ - If set to ``False``, only one edge `(1,2)` will remain in the graph
1881
+ after `(2,1)` is reversed. Besides, the label of edge `(1,2)` will
1882
+ be overwritten with the label of edge `(2,1)`.
1883
+
1884
+ The default behaviour (``multiedges = None``) will raise an exception
1885
+ each time a subjective decision (setting ``multiedges`` to ``True`` or
1886
+ ``False``) is necessary to perform the operation.
1887
+
1888
+ The following forms are all accepted:
1889
+
1890
+ - D.reverse_edge( 1, 2 )
1891
+ - D.reverse_edge( (1, 2) )
1892
+ - D.reverse_edge( [1, 2] )
1893
+ - D.reverse_edge( 1, 2, 'label' )
1894
+ - D.reverse_edge( ( 1, 2, 'label') )
1895
+ - D.reverse_edge( [1, 2, 'label'] )
1896
+ - D.reverse_edge( ( 1, 2), label='label' )
1897
+
1898
+ EXAMPLES:
1899
+
1900
+ If ``inplace`` is ``True`` (default), ``self`` is modified::
1901
+
1902
+ sage: D = DiGraph([(0, 1 ,2)])
1903
+ sage: D.reverse_edge(0, 1)
1904
+ sage: D.edges(sort=True)
1905
+ [(1, 0, 2)]
1906
+
1907
+ If ``inplace`` is ``False``, ``self`` is not modified and a new digraph
1908
+ is returned::
1909
+
1910
+ sage: D = DiGraph([(0, 1, 2)])
1911
+ sage: re = D.reverse_edge(0, 1, inplace=False)
1912
+ sage: re.edges(sort=True)
1913
+ [(1, 0, 2)]
1914
+ sage: D.edges(sort=True)
1915
+ [(0, 1, 2)]
1916
+
1917
+ If ``multiedges`` is ``True``, ``self`` will be forced to allow parallel
1918
+ edges when and only when it is necessary::
1919
+
1920
+ sage: D = DiGraph([(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)])
1921
+ sage: D.reverse_edge(1, 2, multiedges=True)
1922
+ sage: D.edges(sort=True)
1923
+ [(2, 1, 'A'), (2, 1, 'A'), (2, 3, None)]
1924
+ sage: D.allows_multiple_edges()
1925
+ True
1926
+
1927
+ Even if ``multiedges`` is ``True``, ``self`` will not be forced to allow
1928
+ parallel edges when it is not necessary::
1929
+
1930
+ sage: D = DiGraph( [(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)] )
1931
+ sage: D.reverse_edge(2, 3, multiedges=True)
1932
+ sage: D.edges(sort=True)
1933
+ [(1, 2, 'A'), (2, 1, 'A'), (3, 2, None)]
1934
+ sage: D.allows_multiple_edges()
1935
+ False
1936
+
1937
+ If user specifies ``multiedges = False``, ``self`` will not be forced to
1938
+ allow parallel edges and a parallel edge will get deleted::
1939
+
1940
+ sage: D = DiGraph( [(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)] )
1941
+ sage: D.edges(sort=True)
1942
+ [(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)]
1943
+ sage: D.reverse_edge(1, 2, multiedges=False)
1944
+ sage: D.edges(sort=True)
1945
+ [(2, 1, 'A'), (2, 3, None)]
1946
+
1947
+ Note that in the following graph, specifying ``multiedges = False`` will
1948
+ result in overwriting the label of `(1, 2)` with the label of `(2, 1)`::
1949
+
1950
+ sage: D = DiGraph( [(1, 2, 'B'), (2, 1, 'A'), (2, 3, None)] )
1951
+ sage: D.edges(sort=True)
1952
+ [(1, 2, 'B'), (2, 1, 'A'), (2, 3, None)]
1953
+ sage: D.reverse_edge(2, 1, multiedges=False)
1954
+ sage: D.edges(sort=True)
1955
+ [(1, 2, 'A'), (2, 3, None)]
1956
+
1957
+ If input edge in digraph has weight/label, then the weight/label should
1958
+ be preserved in the output digraph. User does not need to specify the
1959
+ weight/label when calling function::
1960
+
1961
+ sage: D = DiGraph([[0, 1, 2], [1, 2, 1]], weighted=True)
1962
+ sage: D.reverse_edge(0, 1)
1963
+ sage: D.edges(sort=True)
1964
+ [(1, 0, 2), (1, 2, 1)]
1965
+ sage: re = D.reverse_edge([1, 2], inplace=False)
1966
+ sage: re.edges(sort=True)
1967
+ [(1, 0, 2), (2, 1, 1)]
1968
+
1969
+ If ``self`` has multiple copies (parallel edges) of the input edge, only
1970
+ 1 of the parallel edges is reversed::
1971
+
1972
+ sage: D = DiGraph([(0, 1, '01'), (0, 1, '01'), (0, 1, 'cat'), (1, 2, '12')], weighted=True, multiedges=True)
1973
+ sage: re = D.reverse_edge([0, 1, '01'], inplace=False)
1974
+ sage: re.edges(sort=True)
1975
+ [(0, 1, '01'), (0, 1, 'cat'), (1, 0, '01'), (1, 2, '12')]
1976
+
1977
+ If ``self`` has multiple copies (parallel edges) of the input edge but
1978
+ with distinct labels and no input label is specified, only 1 of the
1979
+ parallel edges is reversed (the edge that is labeled by the first label
1980
+ on the list returned by :meth:`.edge_label`)::
1981
+
1982
+ sage: D = DiGraph([(0, 1, 'A'), (0, 1, 'B'), (0, 1, 'mouse'), (0, 1, 'cat')], multiedges=true)
1983
+ sage: D.edge_label(0, 1)
1984
+ ['cat', 'mouse', 'B', 'A']
1985
+ sage: D.reverse_edge(0, 1)
1986
+ sage: D.edges(sort=True)
1987
+ [(0, 1, 'A'), (0, 1, 'B'), (0, 1, 'mouse'), (1, 0, 'cat')]
1988
+
1989
+ Finally, an exception is raised when Sage does not know how to choose
1990
+ between allowing multiple edges and losing some data::
1991
+
1992
+ sage: D = DiGraph([(0, 1, 'A'), (1, 0, 'B')])
1993
+ sage: D.reverse_edge(0, 1)
1994
+ Traceback (most recent call last):
1995
+ ...
1996
+ ValueError: reversing the given edge is about to create two parallel
1997
+ edges but input digraph doesn't allow them - User needs to specify
1998
+ multiedges is True or False.
1999
+
2000
+ The following syntax is supported, but note that you must use the
2001
+ ``label`` keyword::
2002
+
2003
+ sage: D = DiGraph()
2004
+ sage: D.add_edge((1, 2), label='label')
2005
+ sage: D.edges(sort=True)
2006
+ [(1, 2, 'label')]
2007
+ sage: D.reverse_edge((1, 2), label='label')
2008
+ sage: D.edges(sort=True)
2009
+ [(2, 1, 'label')]
2010
+ sage: D.add_edge((1, 2), 'label')
2011
+ sage: D.edges(sort=False)
2012
+ [((1, 2), 'label', None), (2, 1, 'label')]
2013
+ sage: D.reverse_edge((1, 2), 'label')
2014
+ sage: D.edges(sort=False)
2015
+ [('label', (1, 2), None), (2, 1, 'label')]
2016
+
2017
+ TESTS::
2018
+
2019
+ sage: D = DiGraph([(0, 1, None)])
2020
+ sage: D.reverse_edge(0, 1, 'mylabel')
2021
+ Traceback (most recent call last):
2022
+ ...
2023
+ ValueError: input edge must exist in the digraph
2024
+ """
2025
+ # Assigns the expected values to u,v, and label depending on the input.
2026
+ if label is None:
2027
+ if v is None:
2028
+ try:
2029
+ u, v, label = u
2030
+ except Exception:
2031
+ try:
2032
+ u, v = u
2033
+ except Exception:
2034
+ pass
2035
+ else:
2036
+ if v is None:
2037
+ try:
2038
+ u, v = u
2039
+ except Exception:
2040
+ pass
2041
+
2042
+ if not self.has_edge(u, v, label):
2043
+ raise ValueError("input edge must exist in the digraph")
2044
+
2045
+ tempG = self if inplace else copy(self)
2046
+
2047
+ if label is None:
2048
+ if not tempG.allows_multiple_edges():
2049
+ label = tempG.edge_label(u, v)
2050
+ else:
2051
+ # If digraph has parallel edges for input edge, pick the first
2052
+ # from the labels on the list
2053
+ label = tempG.edge_label(u, v)[0]
2054
+
2055
+ if ((not tempG.allows_multiple_edges()) and (tempG.has_edge(v, u))):
2056
+ # If user wants to force digraph to allow parallel edges
2057
+ if multiedges:
2058
+ tempG.allow_multiple_edges(True)
2059
+ tempG.delete_edge(u, v, label)
2060
+ tempG.add_edge(v, u, label)
2061
+
2062
+ # If user does not want to force digraph to allow parallel edges,
2063
+ # we delete edge u to v and overwrite v,u with the label of u,v
2064
+ elif multiedges is False:
2065
+ tempG.delete_edge(u, v, label)
2066
+ tempG.set_edge_label(v, u, label)
2067
+
2068
+ # User is supposed to specify multiedges True or False
2069
+ else:
2070
+ raise ValueError("reversing the given edge is about to "
2071
+ "create two parallel edges but input digraph "
2072
+ "doesn't allow them - User needs to specify "
2073
+ "multiedges is True or False.")
2074
+ else:
2075
+ tempG.delete_edge(u, v, label)
2076
+ tempG.add_edge(v, u, label)
2077
+
2078
+ if not inplace:
2079
+ return tempG
2080
+
2081
+ def reverse_edges(self, edges, inplace=True, multiedges=None):
2082
+ """
2083
+ Reverse a list of edges.
2084
+
2085
+ INPUT:
2086
+
2087
+ - ``edges`` -- list of edges in the DiGraph
2088
+
2089
+ - ``inplace`` -- boolean (default: ``True``); if ``False``, a new
2090
+ digraph is created and returned as output, otherwise ``self`` is
2091
+ modified
2092
+
2093
+ - ``multiedges`` -- boolean (default: ``None``); if ``True``, input
2094
+ graph will be forced to allow parallel edges when necessary (for more
2095
+ information see the documentation of :meth:`~DiGraph.reverse_edge`)
2096
+
2097
+ .. SEEALSO::
2098
+
2099
+ :meth:`~DiGraph.reverse_edge` -- reverses a single edge
2100
+
2101
+ EXAMPLES:
2102
+
2103
+ If ``inplace`` is ``True`` (default), ``self`` is modified::
2104
+
2105
+ sage: D = DiGraph({ 0: [1, 1, 3], 2: [3, 3], 4: [1, 5]}, multiedges=true)
2106
+ sage: D.reverse_edges([[0, 1], [0, 3]])
2107
+ sage: D.reverse_edges([(2, 3), (4, 5)])
2108
+ sage: D.edges(sort=True)
2109
+ [(0, 1, None), (1, 0, None), (2, 3, None), (3, 0, None),
2110
+ (3, 2, None), (4, 1, None), (5, 4, None)]
2111
+
2112
+ If ``inplace`` is ``False``, ``self`` is not modified and a new digraph
2113
+ is returned::
2114
+
2115
+ sage: D = DiGraph([(0, 1, 'A'), (1, 0, 'B'), (1, 2, 'C')])
2116
+ sage: re = D.reverse_edges([(0, 1), (1, 2)],
2117
+ ....: inplace=False,
2118
+ ....: multiedges=True)
2119
+ sage: re.edges(sort=True)
2120
+ [(1, 0, 'A'), (1, 0, 'B'), (2, 1, 'C')]
2121
+ sage: D.edges(sort=True)
2122
+ [(0, 1, 'A'), (1, 0, 'B'), (1, 2, 'C')]
2123
+ sage: D.allows_multiple_edges()
2124
+ False
2125
+ sage: re.allows_multiple_edges()
2126
+ True
2127
+
2128
+ If ``multiedges`` is ``True``, ``self`` will be forced to allow parallel
2129
+ edges when and only when it is necessary::
2130
+
2131
+ sage: D = DiGraph([(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)])
2132
+ sage: D.reverse_edges([(1, 2), (2, 3)], multiedges=True)
2133
+ sage: D.edges(sort=True)
2134
+ [(2, 1, 'A'), (2, 1, 'A'), (3, 2, None)]
2135
+ sage: D.allows_multiple_edges()
2136
+ True
2137
+
2138
+ Even if ``multiedges`` is ``True``, ``self`` will not be forced to allow
2139
+ parallel edges when it is not necessary::
2140
+
2141
+ sage: D = DiGraph([(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)])
2142
+ sage: D.reverse_edges([(2, 3)], multiedges=True)
2143
+ sage: D.edges(sort=True)
2144
+ [(1, 2, 'A'), (2, 1, 'A'), (3, 2, None)]
2145
+ sage: D.allows_multiple_edges()
2146
+ False
2147
+
2148
+ If ``multiedges`` is ``False``, ``self`` will not be forced to allow
2149
+ parallel edges and an edge will get deleted::
2150
+
2151
+ sage: D = DiGraph([(1, 2), (2, 1)])
2152
+ sage: D.edges(sort=True)
2153
+ [(1, 2, None), (2, 1, None)]
2154
+ sage: D.reverse_edges([(1, 2)], multiedges=False)
2155
+ sage: D.edges(sort=True)
2156
+ [(2, 1, None)]
2157
+
2158
+ If input edge in digraph has weight/label, then the weight/label should
2159
+ be preserved in the output digraph. User does not need to specify the
2160
+ weight/label when calling function::
2161
+
2162
+ sage: D = DiGraph([(0, 1, '01'), (1, 2, 1), (2, 3, '23')], weighted=True)
2163
+ sage: D.reverse_edges([(0, 1, '01'), (1, 2), (2, 3)])
2164
+ sage: D.edges(sort=True)
2165
+ [(1, 0, '01'), (2, 1, 1), (3, 2, '23')]
2166
+
2167
+ TESTS::
2168
+
2169
+ sage: D = digraphs.Circuit(6)
2170
+ sage: D.reverse_edges(D.edges(sort=True), inplace=False).edges(sort=True)
2171
+ [(0, 5, None), (1, 0, None), (2, 1, None),
2172
+ (3, 2, None), (4, 3, None), (5, 4, None)]
2173
+
2174
+ sage: D = digraphs.Kautz(2, 3) # needs sage.combinat
2175
+ sage: Dr = D.reverse_edges(D.edges(sort=True), inplace=False, # needs sage.combinat
2176
+ ....: multiedges=True)
2177
+ sage: Dr.edges(sort=True) == D.reverse().edges(sort=True) # needs sage.combinat
2178
+ True
2179
+ """
2180
+ tempG = self if inplace else copy(self)
2181
+ for e in edges:
2182
+ tempG.reverse_edge(e, inplace=True, multiedges=multiedges)
2183
+ if not inplace:
2184
+ return tempG
2185
+
2186
+ # Distances
2187
+
2188
+ def eccentricity(self, v=None, by_weight=False, algorithm=None,
2189
+ weight_function=None, check_weight=True, dist_dict=None,
2190
+ with_labels=False):
2191
+ """
2192
+ Return the eccentricity of vertex (or vertices) ``v``.
2193
+
2194
+ The eccentricity of a vertex is the maximum distance to any other
2195
+ vertex.
2196
+
2197
+ For more information and examples on how to use input variables, see
2198
+ :meth:`~GenericGraph.shortest_path_all_pairs`,
2199
+ :meth:`~GenericGraph.shortest_path_lengths` and
2200
+ :meth:`~GenericGraph.shortest_paths`
2201
+
2202
+ INPUT:
2203
+
2204
+ - ``v`` -- either a single vertex or a list of vertices. If it is not
2205
+ specified, then it is taken to be all vertices
2206
+
2207
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, edge
2208
+ weights are taken into account; if ``False``, all edges have weight 1
2209
+
2210
+ - ``algorithm`` -- string (default: ``None``); one of the following
2211
+ algorithms:
2212
+
2213
+ - ``'BFS'`` -- the computation is done through a BFS centered on each
2214
+ vertex successively. Works only if ``by_weight==False``
2215
+
2216
+ - ``'Floyd-Warshall-Cython'`` -- a Cython implementation of the
2217
+ Floyd-Warshall algorithm. Works only if ``by_weight==False`` and
2218
+ ``v is None`` or ``v`` should contain all vertices of ``self``.
2219
+
2220
+ - ``'Floyd-Warshall-Python'`` -- a Python implementation of the
2221
+ Floyd-Warshall algorithm. Works also with weighted graphs, even with
2222
+ negative weights (but no negative cycle is allowed). However, ``v``
2223
+ must be ``None`` or ``v`` should contain all vertices of ``self``.
2224
+
2225
+ - ``'Dijkstra_NetworkX'`` -- the Dijkstra algorithm, implemented in
2226
+ NetworkX. It works with weighted graphs, but no negative weight is
2227
+ allowed.
2228
+
2229
+ - ``'Dijkstra_Boost'`` -- the Dijkstra algorithm, implemented in Boost
2230
+ (works only with positive weights)
2231
+
2232
+ - ``'Johnson_Boost'`` -- the Johnson algorithm, implemented in
2233
+ Boost (works also with negative weights, if there is no negative
2234
+ cycle). Works only if ``v is None`` or ``v`` should contain all
2235
+ vertices of ``self``.
2236
+
2237
+ - ``'From_Dictionary'`` -- uses the (already computed) distances, that
2238
+ are provided by input variable ``dist_dict``
2239
+
2240
+ - ``None`` (default): Sage chooses the best algorithm:
2241
+ ``'From_Dictionary'`` if ``dist_dict`` is not None, ``'BFS'`` for
2242
+ unweighted graphs, ``'Dijkstra_Boost'`` if all weights are
2243
+ positive, ``'Johnson_Boost'`` otherwise.
2244
+
2245
+ - ``weight_function`` -- function (default: ``None``); a function that
2246
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
2247
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
2248
+ and ``by_weight`` is ``True``, we use the edge label ``l``, if ``l``
2249
+ is not ``None``, else ``1`` as a weight.
2250
+
2251
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
2252
+ that the ``weight_function`` outputs a number for each edge
2253
+
2254
+ - ``dist_dict`` -- dictionary (default: ``None``); a dict of dicts of
2255
+ distances (used only if ``algorithm=='From_Dictionary'``)
2256
+
2257
+ - ``with_labels`` -- boolean (default: ``False``); whether to return a
2258
+ list or a dictionary keyed by vertices
2259
+
2260
+ EXAMPLES::
2261
+
2262
+ sage: G = graphs.KrackhardtKiteGraph().to_directed()
2263
+ sage: G.eccentricity()
2264
+ [4, 4, 4, 4, 4, 3, 3, 2, 3, 4]
2265
+ sage: G.vertices(sort=True)
2266
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2267
+ sage: G.eccentricity(7)
2268
+ 2
2269
+ sage: G.eccentricity([7,8,9])
2270
+ [2, 3, 4]
2271
+ sage: G.eccentricity([7,8,9], with_labels=True) == {8: 3, 9: 4, 7: 2}
2272
+ True
2273
+ sage: G = DiGraph(3)
2274
+ sage: G.eccentricity(with_labels=True)
2275
+ {0: +Infinity, 1: +Infinity, 2: +Infinity}
2276
+ sage: G = DiGraph({0:[]})
2277
+ sage: G.eccentricity(with_labels=True)
2278
+ {0: 0}
2279
+ sage: G = DiGraph([(0,1,2), (1,2,3), (2,0,2)])
2280
+ sage: G.eccentricity(algorithm='BFS')
2281
+ [2, 2, 2]
2282
+ sage: G.eccentricity(algorithm='Floyd-Warshall-Cython')
2283
+ [2, 2, 2]
2284
+ sage: G.eccentricity(by_weight=True, algorithm='Dijkstra_NetworkX') # needs networkx
2285
+ [5, 5, 4]
2286
+ sage: G.eccentricity(by_weight=True, algorithm='Dijkstra_Boost')
2287
+ [5, 5, 4]
2288
+ sage: G.eccentricity(by_weight=True, algorithm='Johnson_Boost')
2289
+ [5, 5, 4]
2290
+ sage: G.eccentricity(by_weight=True, algorithm='Floyd-Warshall-Python')
2291
+ [5, 5, 4]
2292
+ sage: G.eccentricity(dist_dict=G.shortest_path_all_pairs(by_weight=True)[0])
2293
+ [5, 5, 4]
2294
+
2295
+ TESTS:
2296
+
2297
+ A non-implemented algorithm::
2298
+
2299
+ sage: G.eccentricity(algorithm='boh')
2300
+ Traceback (most recent call last):
2301
+ ...
2302
+ ValueError: unknown algorithm "boh"
2303
+
2304
+ An algorithm that does not work with edge weights::
2305
+
2306
+ sage: G.eccentricity(by_weight=True, algorithm='BFS')
2307
+ Traceback (most recent call last):
2308
+ ...
2309
+ ValueError: algorithm 'BFS' does not work with weights
2310
+ sage: G.eccentricity(by_weight=True, algorithm='Floyd-Warshall-Cython')
2311
+ Traceback (most recent call last):
2312
+ ...
2313
+ ValueError: algorithm 'Floyd-Warshall-Cython' does not work with weights
2314
+
2315
+ An algorithm that computes the all-pair-shortest-paths when not all
2316
+ vertices are needed::
2317
+
2318
+ sage: G.eccentricity(0, algorithm='Floyd-Warshall-Cython')
2319
+ Traceback (most recent call last):
2320
+ ...
2321
+ ValueError: algorithm 'Floyd-Warshall-Cython' works only if all eccentricities are needed
2322
+ sage: G.eccentricity(0, algorithm='Floyd-Warshall-Python')
2323
+ Traceback (most recent call last):
2324
+ ...
2325
+ ValueError: algorithm 'Floyd-Warshall-Python' works only if all eccentricities are needed
2326
+ sage: G.eccentricity(0, algorithm='Johnson_Boost')
2327
+ Traceback (most recent call last):
2328
+ ...
2329
+ ValueError: algorithm 'Johnson_Boost' works only if all eccentricities are needed
2330
+ """
2331
+ by_weight, weight_function = self._get_weight_function(by_weight=by_weight,
2332
+ weight_function=weight_function,
2333
+ check_weight=check_weight)
2334
+
2335
+ if not by_weight:
2336
+ # We don't want the default weight function
2337
+ weight_function = None
2338
+ elif algorithm in ['BFS', 'Floyd-Warshall-Cython']:
2339
+ raise ValueError("algorithm '{}' does not work with weights".format(algorithm))
2340
+ if algorithm is None:
2341
+ if dist_dict is not None:
2342
+ algorithm = 'From_Dictionary'
2343
+ elif not by_weight:
2344
+ algorithm = 'BFS'
2345
+ elif any(float(weight_function(e)) < 0 for e in self.edge_iterator()):
2346
+ algorithm = 'Johnson_Boost'
2347
+ if algorithm is None:
2348
+ algorithm = 'Dijkstra_Boost'
2349
+
2350
+ if v is not None:
2351
+ if not isinstance(v, list):
2352
+ v = [v]
2353
+ v_set = set(v)
2354
+
2355
+ if v is None or all(u in v_set for u in self):
2356
+ if v is None:
2357
+ v = list(self)
2358
+
2359
+ # If we want to use BFS, we use the Cython routine
2360
+ if algorithm == 'BFS':
2361
+ from sage.graphs.distances_all_pairs import eccentricity
2362
+ algo = 'standard'
2363
+ if with_labels:
2364
+ return dict(zip(v, eccentricity(self, algorithm=algo, vertex_list=v)))
2365
+ else:
2366
+ return eccentricity(self, algorithm=algo, vertex_list=v)
2367
+
2368
+ if algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost']:
2369
+ dist_dict = self.shortest_path_all_pairs(by_weight=by_weight, algorithm=algorithm,
2370
+ weight_function=weight_function,
2371
+ check_weight=False)[0]
2372
+ algorithm = 'From_Dictionary'
2373
+
2374
+ elif algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost']:
2375
+ raise ValueError("algorithm '" + algorithm + "' works only if all" +
2376
+ " eccentricities are needed")
2377
+
2378
+ ecc = {}
2379
+
2380
+ from sage.rings.infinity import Infinity
2381
+
2382
+ for u in v:
2383
+ if algorithm == 'From_Dictionary':
2384
+ length = dist_dict[u]
2385
+ else:
2386
+ # If algorithm is wrong, the error is raised by the
2387
+ # shortest_path_lengths function
2388
+ length = self.shortest_path_lengths(u, by_weight=by_weight,
2389
+ algorithm=algorithm,
2390
+ weight_function=weight_function,
2391
+ check_weight=False)
2392
+
2393
+ if len(length) != self.num_verts():
2394
+ ecc[u] = Infinity
2395
+ else:
2396
+ ecc[u] = max(length.values())
2397
+
2398
+ if with_labels:
2399
+ return ecc
2400
+ if len(ecc) == 1:
2401
+ # return single value
2402
+ v, = ecc.values()
2403
+ return v
2404
+ return [ecc[u] for u in v]
2405
+
2406
+ def radius(self, by_weight=False, algorithm=None, weight_function=None,
2407
+ check_weight=True):
2408
+ r"""
2409
+ Return the radius of the DiGraph.
2410
+
2411
+ The radius is defined to be the minimum eccentricity of any vertex,
2412
+ where the eccentricity is the maximum distance to any other
2413
+ vertex. For more information and examples on how to use input variables,
2414
+ see :meth:`~GenericGraph.shortest_paths` and
2415
+ :meth:`~DiGraph.eccentricity`
2416
+
2417
+ INPUT:
2418
+
2419
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, edge
2420
+ weights are taken into account; if ``False``, all edges have weight 1
2421
+
2422
+ - ``algorithm`` -- string (default: ``None``); see method
2423
+ :meth:`eccentricity` for the list of available algorithms
2424
+
2425
+ - ``weight_function`` -- function (default: ``None``); a function that
2426
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
2427
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
2428
+ and ``by_weight`` is ``True``, we use the edge label ``l``, if ``l``
2429
+ is not ``None``, else ``1`` as a weight.
2430
+
2431
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
2432
+ that the ``weight_function`` outputs a number for each edge
2433
+
2434
+ EXAMPLES:
2435
+
2436
+ The more symmetric a DiGraph is, the smaller (diameter - radius) is::
2437
+
2438
+ sage: G = graphs.BarbellGraph(9, 3).to_directed()
2439
+ sage: G.radius()
2440
+ 3
2441
+ sage: G.diameter()
2442
+ 6
2443
+
2444
+ ::
2445
+
2446
+ sage: G = digraphs.Circuit(9)
2447
+ sage: G.radius()
2448
+ 8
2449
+ sage: G.diameter()
2450
+ 8
2451
+
2452
+ TESTS::
2453
+
2454
+ sage: G = DiGraph()
2455
+ sage: G.radius()
2456
+ Traceback (most recent call last):
2457
+ ...
2458
+ ValueError: radius is not defined for the empty DiGraph
2459
+
2460
+ Check that :issue:`35300` is fixed::
2461
+
2462
+ sage: H = DiGraph([[42, 'John'], [(42, 'John')]])
2463
+ sage: H.radius()
2464
+ 1
2465
+ """
2466
+ if not self.order():
2467
+ raise ValueError("radius is not defined for the empty DiGraph")
2468
+
2469
+ return min(self.eccentricity(v=list(self), by_weight=by_weight,
2470
+ weight_function=weight_function,
2471
+ check_weight=check_weight,
2472
+ algorithm=algorithm))
2473
+
2474
+ def diameter(self, by_weight=False, algorithm=None, weight_function=None,
2475
+ check_weight=True):
2476
+ r"""
2477
+ Return the diameter of the DiGraph.
2478
+
2479
+ The diameter is defined to be the maximum distance between two vertices.
2480
+ It is infinite if the DiGraph is not strongly connected.
2481
+
2482
+ For more information and examples on how to use input variables, see
2483
+ :meth:`~GenericGraph.shortest_paths` and
2484
+ :meth:`~DiGraph.eccentricity`
2485
+
2486
+ INPUT:
2487
+
2488
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, edge
2489
+ weights are taken into account; if ``False``, all edges have weight 1
2490
+
2491
+ - ``algorithm`` -- string (default: ``None``); one of the following
2492
+ algorithms:
2493
+
2494
+ - ``'BFS'``: the computation is done through a BFS centered on each
2495
+ vertex successively. Works only if ``by_weight==False``. It computes
2496
+ all the eccentricities and return the maximum value.
2497
+
2498
+ - ``'Floyd-Warshall-Cython'``: a Cython implementation of the
2499
+ Floyd-Warshall algorithm. Works only if ``by_weight==False``. It
2500
+ computes all the eccentricities and return the maximum value.
2501
+
2502
+ - ``'Floyd-Warshall-Python'``: a Python implementation of the
2503
+ Floyd-Warshall algorithm. Works also with weighted graphs, even with
2504
+ negative weights (but no negative cycle is allowed). It computes all
2505
+ the eccentricities and return the maximum value.
2506
+
2507
+ - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in
2508
+ NetworkX. It works with weighted graphs, but no negative weight is
2509
+ allowed. It computes all the eccentricities and return the maximum
2510
+ value.
2511
+
2512
+ - ``'DiFUB'``, ``'2Dsweep'``: these algorithms are
2513
+ implemented in :func:`sage.graphs.distances_all_pairs.diameter` and
2514
+ :func:`sage.graphs.base.boost_graph.diameter`. ``'2Dsweep'`` returns
2515
+ lower bound on the diameter, while ``'DiFUB'`` returns the exact
2516
+ computed diameter. They also work with negative weight, if there is
2517
+ no negative cycle. See the functions documentation for more
2518
+ information.
2519
+
2520
+ - ``'standard'`` : the standard algorithm is implemented in
2521
+ :func:`sage.graphs.distances_all_pairs.diameter`. It works only
2522
+ if ``by_weight==False``. See the function documentation for more
2523
+ information. It computes all the eccentricities and return the
2524
+ maximum value.
2525
+
2526
+ - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost
2527
+ (works only with positive weights). It computes all the
2528
+ eccentricities and return the maximum value.
2529
+
2530
+ - ``'Johnson_Boost'``: the Johnson algorithm, implemented in
2531
+ Boost (works also with negative weights, if there is no negative
2532
+ cycle). It computes all the eccentricities and return the maximum
2533
+ value.
2534
+
2535
+ - ``None`` (default): Sage chooses the best algorithm: ``'DiFUB'``.
2536
+
2537
+ - ``weight_function`` -- function (default: ``None``); a function that
2538
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
2539
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
2540
+ and ``by_weight`` is ``True``, we use the edge label ``l``, if ``l``
2541
+ is not ``None``, else ``1`` as weight.
2542
+
2543
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
2544
+ that the ``weight_function`` outputs a number for each edge
2545
+
2546
+ EXAMPLES::
2547
+
2548
+ sage: # needs sage.combinat
2549
+ sage: G = digraphs.DeBruijn(5,4)
2550
+ sage: G.diameter()
2551
+ 4
2552
+ sage: G = digraphs.GeneralizedDeBruijn(9, 3)
2553
+ sage: G.diameter()
2554
+ 2
2555
+
2556
+ TESTS::
2557
+
2558
+ sage: G = graphs.RandomGNP(40, 0.4).to_directed()
2559
+ sage: d1 = G.diameter(algorithm='DiFUB', by_weight=True)
2560
+ sage: d2 = max(G.eccentricity(algorithm='Dijkstra_Boost', by_weight=True))
2561
+ sage: d1 == d2
2562
+ True
2563
+ sage: G.diameter(algorithm='BFS', by_weight=True)
2564
+ Traceback (most recent call last):
2565
+ ...
2566
+ ValueError: algorithm 'BFS' does not work with weights
2567
+ sage: G.diameter(algorithm='Floyd-Warshall-Cython', by_weight=True)
2568
+ Traceback (most recent call last):
2569
+ ...
2570
+ ValueError: algorithm 'Floyd-Warshall-Cython' does not work with weights
2571
+ sage: G = digraphs.Path(5)
2572
+ sage: G.diameter(algorithm = 'DiFUB')
2573
+ +Infinity
2574
+ sage: G = DiGraph([(1,2,4), (2,1,7)])
2575
+ sage: G.diameter(algorithm='2Dsweep', by_weight=True)
2576
+ 7.0
2577
+ sage: G.delete_edge(2,1,7)
2578
+ sage: G.add_edge(2,1,-5)
2579
+ sage: G.diameter(algorithm='2Dsweep', by_weight=True)
2580
+ Traceback (most recent call last):
2581
+ ...
2582
+ ValueError: the graph contains a negative cycle
2583
+ sage: G = DiGraph()
2584
+ sage: G.diameter()
2585
+ Traceback (most recent call last):
2586
+ ...
2587
+ ValueError: diameter is not defined for the empty DiGraph
2588
+
2589
+ :issue:`32095` is fixed::
2590
+
2591
+ sage: g6 = 'guQOUOQCW[IaDBCVP_IE\\RfxV@WMSaeHgheEIA@tfOJkB~@EpGLCrs'
2592
+ sage: g6 += 'aPIpwgQI_`Abs_x?VWxNJAo@w\\hffCDAW]bYGMIZGC_PYOrIw[Gp['
2593
+ sage: g6 += '@FTgc_O}E?fXAnGCB{gSaUcD'
2594
+ sage: G = Graph(g6).to_directed()
2595
+ sage: G.diameter(algorithm='DiFUB', by_weight=False)
2596
+ 3
2597
+ sage: G.diameter(algorithm='DiFUB', by_weight=True)
2598
+ 3.0
2599
+
2600
+ Check that :issue:`35300` is fixed::
2601
+
2602
+ sage: H = DiGraph([[42, 'John'], [(42, 'John')]])
2603
+ sage: H.diameter()
2604
+ +Infinity
2605
+ sage: H.add_edge('John', 42)
2606
+ sage: H.diameter()
2607
+ 1
2608
+ """
2609
+ if not self.order():
2610
+ raise ValueError("diameter is not defined for the empty DiGraph")
2611
+
2612
+ by_weight, weight_function = self._get_weight_function(by_weight=by_weight,
2613
+ weight_function=weight_function,
2614
+ check_weight=check_weight)
2615
+
2616
+ if not by_weight:
2617
+ # We don't want the default weight function
2618
+ weight_function = None
2619
+ elif algorithm in ['BFS', 'Floyd-Warshall-Cython']:
2620
+ raise ValueError("algorithm '{}' does not work with weights".format(algorithm))
2621
+
2622
+ if algorithm is None:
2623
+ algorithm = 'DiFUB'
2624
+
2625
+ if algorithm in ['2Dsweep', 'DiFUB']:
2626
+ if not by_weight:
2627
+ from sage.graphs.distances_all_pairs import diameter
2628
+ return diameter(self, algorithm=algorithm)
2629
+ else:
2630
+ from sage.graphs.base.boost_graph import diameter
2631
+ return diameter(self, algorithm=algorithm,
2632
+ weight_function=weight_function,
2633
+ check_weight=False)
2634
+
2635
+ if algorithm == 'BFS':
2636
+ from sage.graphs.distances_all_pairs import diameter
2637
+ return diameter(self, algorithm='standard')
2638
+
2639
+ return max(self.eccentricity(v=list(self), by_weight=by_weight,
2640
+ weight_function=weight_function,
2641
+ check_weight=False,
2642
+ algorithm=algorithm))
2643
+
2644
+ def center(self, by_weight=False, algorithm=None, weight_function=None,
2645
+ check_weight=True):
2646
+ r"""
2647
+ Return the set of vertices in the center of the DiGraph.
2648
+
2649
+ The center is the set of vertices whose eccentricity is equal to the
2650
+ radius of the DiGraph, i.e., achieving the minimum eccentricity.
2651
+
2652
+ For more information and examples on how to use input variables,
2653
+ see :meth:`~GenericGraph.shortest_paths` and
2654
+ :meth:`~DiGraph.eccentricity`
2655
+
2656
+ INPUT:
2657
+
2658
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, edge
2659
+ weights are taken into account; if ``False``, all edges have weight 1
2660
+
2661
+ - ``algorithm`` -- string (default: ``None``); see method
2662
+ :meth:`eccentricity` for the list of available algorithms
2663
+
2664
+ - ``weight_function`` -- function (default: ``None``); a function that
2665
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
2666
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
2667
+ and ``by_weight`` is ``True``, we use the edge label ``l`` as a
2668
+ weight, if ``l`` is not ``None``, else ``1`` as a weight.
2669
+
2670
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
2671
+ that the ``weight_function`` outputs a number for each edge
2672
+
2673
+ EXAMPLES:
2674
+
2675
+ Every vertex is a center in a Circuit-DiGraph::
2676
+
2677
+ sage: G = digraphs.Circuit(9)
2678
+ sage: G.center()
2679
+ [0, 1, 2, 3, 4, 5, 6, 7, 8]
2680
+
2681
+ Center can be the whole graph::
2682
+
2683
+ sage: G.subgraph(G.center()) == G
2684
+ True
2685
+
2686
+ Some other graphs::
2687
+
2688
+ sage: G = digraphs.Path(5)
2689
+ sage: G.center()
2690
+ [0]
2691
+ sage: G = DiGraph([(0,1,2), (1,2,3), (2,0,2)])
2692
+ sage: G.center(by_weight=True)
2693
+ [2]
2694
+
2695
+ TESTS::
2696
+
2697
+ sage: G = DiGraph()
2698
+ sage: G.center()
2699
+ []
2700
+ sage: G = DiGraph(3)
2701
+ sage: G.center()
2702
+ [0, 1, 2]
2703
+ """
2704
+ ecc = self.eccentricity(v=list(self), by_weight=by_weight,
2705
+ weight_function=weight_function,
2706
+ algorithm=algorithm,
2707
+ check_weight=check_weight,
2708
+ with_labels=True)
2709
+ try:
2710
+ r = min(ecc.values())
2711
+ except Exception:
2712
+ return []
2713
+ return [v for v in self if ecc[v] == r]
2714
+
2715
+ def periphery(self, by_weight=False, algorithm=None, weight_function=None,
2716
+ check_weight=True):
2717
+ r"""
2718
+ Return the set of vertices in the periphery of the DiGraph.
2719
+
2720
+ The periphery is the set of vertices whose eccentricity is equal to the
2721
+ diameter of the DiGraph, i.e., achieving the maximum eccentricity.
2722
+
2723
+ For more information and examples on how to use input variables,
2724
+ see :meth:`~GenericGraph.shortest_paths` and
2725
+ :meth:`~DiGraph.eccentricity`
2726
+
2727
+ INPUT:
2728
+
2729
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, edge
2730
+ weights are taken into account; if ``False``, all edges have weight 1
2731
+
2732
+ - ``algorithm`` -- string (default: ``None``); see method
2733
+ :meth:`eccentricity` for the list of available algorithms
2734
+
2735
+ - ``weight_function`` -- function (default: ``None``); a function that
2736
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
2737
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
2738
+ and ``by_weight`` is ``True``, we use the edge label ``l`` as a
2739
+ weight, if ``l`` is not ``None``, else ``1`` as a weight.
2740
+
2741
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
2742
+ that the ``weight_function`` outputs a number for each edge
2743
+
2744
+ EXAMPLES::
2745
+
2746
+ sage: G = graphs.DiamondGraph().to_directed()
2747
+ sage: G.periphery()
2748
+ [0, 3]
2749
+ sage: P = digraphs.Path(5)
2750
+ sage: P.periphery()
2751
+ [1, 2, 3, 4]
2752
+ sage: G = digraphs.Complete(5)
2753
+ sage: G.subgraph(G.periphery()) == G
2754
+ True
2755
+
2756
+ TESTS::
2757
+
2758
+ sage: G = DiGraph()
2759
+ sage: G.periphery()
2760
+ []
2761
+ sage: G.add_vertex()
2762
+ 0
2763
+ sage: G.periphery()
2764
+ [0]
2765
+ """
2766
+ ecc = self.eccentricity(v=list(self), by_weight=by_weight,
2767
+ weight_function=weight_function,
2768
+ algorithm=algorithm,
2769
+ check_weight=check_weight,
2770
+ with_labels=True)
2771
+ try:
2772
+ d = max(ecc.values())
2773
+ except Exception:
2774
+ return []
2775
+ return [v for v in self if ecc[v] == d]
2776
+
2777
+ # Paths and cycles iterators
2778
+
2779
+ def _all_cycles_iterator_vertex(self, vertex, starting_vertices=None, simple=False,
2780
+ rooted=False, max_length=None, trivial=False,
2781
+ remove_acyclic_edges=True,
2782
+ weight_function=None, by_weight=False,
2783
+ check_weight=True, report_weight=False):
2784
+ r"""
2785
+ Return an iterator over the cycles of ``self`` starting with the given
2786
+ vertex in increasing length order. Each edge must have a positive weight.
2787
+
2788
+ INPUT:
2789
+
2790
+ - ``vertex`` -- the starting vertex of the cycle
2791
+
2792
+ - ``starting_vertices`` -- iterable (default: ``None``); vertices from
2793
+ which the cycles must start. If ``None``, then all vertices of the
2794
+ graph can be starting points. This argument is necessary if ``rooted``
2795
+ is set to ``True``.
2796
+
2797
+ - ``simple`` -- boolean (default: ``False``); if set to ``True``, then
2798
+ only simple cycles are considered. A cycle is simple if the only
2799
+ vertex occurring twice in it is the starting and ending one.
2800
+
2801
+ - ``rooted`` -- boolean (default: ``False``); if set to False, then
2802
+ cycles differing only by their starting vertex are considered the same
2803
+ (e.g. ``['a', 'b', 'c', 'a']`` and ``['b', 'c', 'a',
2804
+ 'b']``). Otherwise, all cycles are enumerated.
2805
+
2806
+ - ``max_length`` -- nonnegative integer (default: ``None``); the
2807
+ maximum length of the enumerated paths. If set to ``None``, then all
2808
+ lengths are allowed.
2809
+
2810
+ - ``trivial`` -- boolean (default: ``False``); if set to ``True``, then
2811
+ the empty paths are also enumerated
2812
+
2813
+ - ``remove_acyclic_edges`` -- boolean (default: ``True``); whether
2814
+ acyclic edges must be removed from the graph. Used to avoid
2815
+ recomputing it for each vertex
2816
+
2817
+ - ``weight_function`` -- function (default: ``None``); a function that
2818
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
2819
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
2820
+ and ``by_weight`` is ``True``, we use the edge label ``l`` as a
2821
+ weight.
2822
+
2823
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, the edges
2824
+ in the graph are weighted, otherwise all edges have weight 1
2825
+
2826
+ - ``check_weight`` -- boolean (default: ``True``); whether to check that
2827
+ the ``weight_function`` outputs a number for each edge
2828
+
2829
+ - ``report_weight`` -- boolean (default: ``False``); if ``False``, just
2830
+ a cycle is returned. Otherwise a tuple of cycle length and cycle is
2831
+ returned.
2832
+
2833
+ OUTPUT: iterator
2834
+
2835
+ EXAMPLES::
2836
+
2837
+ sage: g = DiGraph({'a': ['a', 'b'], 'b': ['c'], 'c': ['d'], 'd': ['c']}, loops=True)
2838
+ sage: it = g._all_cycles_iterator_vertex('a', simple=False, max_length=None)
2839
+ sage: for i in range(5): print(next(it))
2840
+ ['a', 'a']
2841
+ ['a', 'a', 'a']
2842
+ ['a', 'a', 'a', 'a']
2843
+ ['a', 'a', 'a', 'a', 'a']
2844
+ ['a', 'a', 'a', 'a', 'a', 'a']
2845
+ sage: it = g._all_cycles_iterator_vertex('c', simple=False, max_length=None)
2846
+ sage: for i in range(5): print(next(it))
2847
+ ['c', 'd', 'c']
2848
+ ['c', 'd', 'c', 'd', 'c']
2849
+ ['c', 'd', 'c', 'd', 'c', 'd', 'c']
2850
+ ['c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c']
2851
+ ['c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c']
2852
+
2853
+ sage: it = g._all_cycles_iterator_vertex('d', simple=False, max_length=None)
2854
+ sage: for i in range(5): print(next(it))
2855
+ ['d', 'c', 'd']
2856
+ ['d', 'c', 'd', 'c', 'd']
2857
+ ['d', 'c', 'd', 'c', 'd', 'c', 'd']
2858
+ ['d', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd']
2859
+ ['d', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd']
2860
+
2861
+ It is possible to set a maximum length so that the number of cycles is
2862
+ finite::
2863
+
2864
+ sage: it = g._all_cycles_iterator_vertex('d', simple=False, max_length=6)
2865
+ sage: list(it)
2866
+ [['d', 'c', 'd'], ['d', 'c', 'd', 'c', 'd'], ['d', 'c', 'd', 'c', 'd', 'c', 'd']]
2867
+
2868
+ When ``simple`` is set to True, the number of cycles is finite since no vertex
2869
+ but the first one can occur more than once::
2870
+
2871
+ sage: it = g._all_cycles_iterator_vertex('d', simple=True, max_length=None)
2872
+ sage: list(it)
2873
+ [['d', 'c', 'd']]
2874
+
2875
+ By default, the empty cycle is not enumerated::
2876
+
2877
+ sage: it = g._all_cycles_iterator_vertex('d', simple=True, trivial=True)
2878
+ sage: list(it)
2879
+ [['d'], ['d', 'c', 'd']]
2880
+
2881
+ A cycle is enumerated in increasing length order for a weighted graph::
2882
+
2883
+ sage: g = DiGraph()
2884
+ sage: g.add_edges([('a', 'b', 2), ('a', 'e', 2), ('b', 'a', 1), ('b', 'c', 1),
2885
+ ....: ('c', 'd', 2), ('d', 'b', 1), ('e', 'a', 2)])
2886
+ sage: it = g._all_cycles_iterator_vertex('a', simple=False, max_length=None,
2887
+ ....: by_weight=True, report_weight=True)
2888
+ sage: for i in range(7): print(next(it))
2889
+ (3, ['a', 'b', 'a'])
2890
+ (4, ['a', 'e', 'a'])
2891
+ (6, ['a', 'b', 'a', 'b', 'a'])
2892
+ (7, ['a', 'b', 'a', 'e', 'a'])
2893
+ (7, ['a', 'b', 'c', 'd', 'b', 'a'])
2894
+ (7, ['a', 'e', 'a', 'b', 'a'])
2895
+ (8, ['a', 'e', 'a', 'e', 'a'])
2896
+
2897
+ Each edge must have a positive weight::
2898
+
2899
+ sage: g = DiGraph()
2900
+ sage: g.add_edges([('a', 'b', -2), ('b', 'a', 1)])
2901
+ sage: next(g._all_cycles_iterator_vertex('a', simple=False, max_length=None,
2902
+ ....: by_weight=True, report_weight=True))
2903
+ Traceback (most recent call last):
2904
+ ...
2905
+ ValueError: negative weight is not allowed
2906
+ """
2907
+ if starting_vertices is None:
2908
+ starting_vertices = [vertex]
2909
+ # First enumerate the empty cycle
2910
+ if trivial:
2911
+ if report_weight:
2912
+ yield (0, [vertex])
2913
+ else:
2914
+ yield [vertex]
2915
+ # First we remove vertices and edges that are not part of any cycle
2916
+ if remove_acyclic_edges:
2917
+ sccs = self.strongly_connected_components()
2918
+ d = {}
2919
+ for id, component in enumerate(sccs):
2920
+ for v in component:
2921
+ d[v] = id
2922
+ h = copy(self)
2923
+ h.delete_edges((u, v) for u, v in h.edge_iterator(labels=False) if d[u] != d[v])
2924
+ else:
2925
+ h = self
2926
+
2927
+ by_weight, weight_function = self._get_weight_function(by_weight=by_weight,
2928
+ weight_function=weight_function,
2929
+ check_weight=check_weight)
2930
+ if by_weight:
2931
+ for e in h.edge_iterator():
2932
+ if weight_function(e) < 0:
2933
+ raise ValueError("negative weight is not allowed")
2934
+
2935
+ from heapq import heappop, heappush
2936
+ heap_queue = [(0, [vertex])]
2937
+ if max_length is None:
2938
+ from sage.rings.infinity import Infinity
2939
+ max_length = Infinity
2940
+ while heap_queue:
2941
+ length, path = heappop(heap_queue)
2942
+ # Checks if a cycle has been found
2943
+ if len(path) > 1 and path[0] == path[-1]:
2944
+ if report_weight:
2945
+ yield (length, path)
2946
+ else:
2947
+ yield path
2948
+ # If simple is set to True, only simple cycles are
2949
+ # allowed, Then it discards the current path
2950
+ if (not simple or path.count(path[-1]) == 1):
2951
+ for e in h.outgoing_edge_iterator(path[-1]):
2952
+ neighbor = e[1]
2953
+ # Makes sure that the current cycle is not too long.
2954
+ # If cycles are not rooted, makes sure to keep only the
2955
+ # minimum cycle according to the lexicographic order
2956
+ if length + weight_function(e) <= max_length and \
2957
+ (rooted or neighbor not in starting_vertices or path[0] <= neighbor):
2958
+ heappush(heap_queue, (length + weight_function(e), path + [neighbor]))
2959
+
2960
+ def _all_simple_cycles_iterator_edge(self, edge, max_length=None,
2961
+ remove_acyclic_edges=True,
2962
+ weight_function=None, by_weight=False,
2963
+ check_weight=True, report_weight=False):
2964
+ r"""
2965
+ Return an iterator over the **simple** cycles of ``self`` starting with the
2966
+ given edge in increasing length order. Each edge must have a positive weight.
2967
+
2968
+ INPUT:
2969
+
2970
+ - ``edge`` -- the starting edge of the cycle.
2971
+
2972
+ - ``max_length`` -- nonnegative integer (default: ``None``); the
2973
+ maximum length of the enumerated paths. If set to ``None``, then all
2974
+ lengths are allowed.
2975
+
2976
+ - ``remove_acyclic_edges`` -- boolean (default: ``True``); whether
2977
+ acyclic edges must be removed from the graph. Used to avoid
2978
+ recomputing it for each edge
2979
+
2980
+ - ``weight_function`` -- function (default: ``None``); a function that
2981
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
2982
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
2983
+ and ``by_weight`` is ``True``, we use the edge label ``l`` as a
2984
+ weight.
2985
+
2986
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, the edges
2987
+ in the graph are weighted, otherwise all edges have weight 1
2988
+
2989
+ - ``check_weight`` -- boolean (default: ``True``); whether to check that
2990
+ the ``weight_function`` outputs a number for each edge
2991
+
2992
+ - ``report_weight`` -- boolean (default: ``False``); if ``False``, just
2993
+ a cycle is returned. Otherwise a tuple of cycle length and cycle is
2994
+ returned.
2995
+
2996
+ OUTPUT: iterator
2997
+
2998
+ ALGORITHM:
2999
+
3000
+ Given an edge `uv`, this algorithm extracts k-shortest `vu`-path in `G-uv`
3001
+ in increasing length order by using k-shortest path algorithm. Thus, it
3002
+ extracts only simple cycles. See
3003
+ :math:`~sage.graphs.path_enumeration.shortest_simple_paths` for more
3004
+ information.
3005
+
3006
+ EXAMPLES:
3007
+
3008
+ sage: g = graphs.Grid2dGraph(2, 5).to_directed()
3009
+ sage: it = g._all_simple_cycles_iterator_edge(((0, 0), (0, 1), None), report_weight=True)
3010
+ sage: for i in range(5): print(next(it))
3011
+ (2, [(0, 0), (0, 1), (0, 0)])
3012
+ (4, [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
3013
+ (6, [(0, 0), (0, 1), (0, 2), (1, 2), (1, 1), (1, 0), (0, 0)])
3014
+ (8, [(0, 0), (0, 1), (0, 2), (0, 3), (1, 3), (1, 2), (1, 1), (1, 0), (0, 0)])
3015
+ (10, [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 4), (1, 3), (1, 2), (1, 1), (1, 0), (0, 0)])
3016
+
3017
+ Each edge must have a positive weight::
3018
+
3019
+ sage: g = DiGraph()
3020
+ sage: g.add_edges([('a', 'b', -2), ('b', 'a', 1)])
3021
+ sage: next(g._all_simple_cycles_iterator_edge(('a', 'b', -2), max_length=None, by_weight=True))
3022
+ Traceback (most recent call last):
3023
+ ...
3024
+ ValueError: negative weight is not allowed
3025
+
3026
+ TESTS:
3027
+
3028
+ A graph with a loop::
3029
+
3030
+ sage: g = DiGraph(loops=True)
3031
+ sage: g.add_edges([('a', 'b'), ('b', 'c'), ('c', 'a'), ('c', 'c')])
3032
+ sage: it = g._all_simple_cycles_iterator_edge(('a', 'b'), remove_acyclic_edges=True)
3033
+ sage: next(it)
3034
+ ['a', 'b', 'c', 'a']
3035
+ sage: g = DiGraph(loops=True)
3036
+ sage: g.add_edges([('a', 'b'), ('b', 'c'), ('c', 'c')])
3037
+ sage: it = g._all_simple_cycles_iterator_edge(('c', 'c'), remove_acyclic_edges=True)
3038
+ sage: next(it)
3039
+ ['c', 'c']
3040
+ """
3041
+ # First we remove vertices and edges that are not part of any cycle
3042
+ if remove_acyclic_edges:
3043
+ h = None
3044
+ for component in self.strongly_connected_components_subgraphs():
3045
+ if component.has_edge(edge):
3046
+ h = component
3047
+ if h is None:
3048
+ # edge connects two strongly connected components, so
3049
+ # no simple cycle starting with edge exists.
3050
+ return
3051
+ else:
3052
+ h = copy(self)
3053
+ # delete edge
3054
+ h.delete_edge(edge)
3055
+
3056
+ by_weight, weight_function = self._get_weight_function(by_weight=by_weight,
3057
+ weight_function=weight_function,
3058
+ check_weight=check_weight)
3059
+
3060
+ if by_weight:
3061
+ for e in self.edge_iterator():
3062
+ if weight_function(e) < 0:
3063
+ raise ValueError("negative weight is not allowed")
3064
+
3065
+ it = h.shortest_simple_paths(source=edge[1], target=edge[0],
3066
+ weight_function=weight_function,
3067
+ by_weight=by_weight,
3068
+ check_weight=check_weight,
3069
+ algorithm='Feng',
3070
+ report_edges=False,
3071
+ report_weight=True)
3072
+
3073
+ edge_weight = weight_function(edge)
3074
+
3075
+ if max_length is None:
3076
+ from sage.rings.infinity import Infinity
3077
+ max_length = Infinity
3078
+ for length, path in it:
3079
+ if length + edge_weight > max_length:
3080
+ break
3081
+
3082
+ if report_weight:
3083
+ yield length + edge_weight, [edge[0]] + path
3084
+ else:
3085
+ yield [edge[0]] + path
3086
+
3087
+ def all_cycles_iterator(self, starting_vertices=None, simple=False,
3088
+ rooted=False, max_length=None, trivial=False,
3089
+ weight_function=None, by_weight=False,
3090
+ check_weight=True, report_weight=False,
3091
+ algorithm='A'):
3092
+ r"""
3093
+ Return an iterator over all the cycles of ``self`` starting with one of
3094
+ the given vertices. Each edge must have a positive weight.
3095
+
3096
+ The cycles are enumerated in increasing length order.
3097
+
3098
+ INPUT:
3099
+
3100
+ - ``starting_vertices`` -- iterable (default: ``None``); vertices from
3101
+ which the cycles must start. If ``None``, then all vertices of the
3102
+ graph can be starting points. This argument is necessary if ``rooted``
3103
+ is set to ``True``.
3104
+
3105
+ - ``simple`` -- boolean (default: ``False``); if set to ``True``, then
3106
+ only simple cycles are considered. A cycle is simple if the only
3107
+ vertex occurring twice in it is the starting and ending one.
3108
+
3109
+ - ``rooted`` -- boolean (default: ``False``); if set to False, then
3110
+ cycles differing only by their starting vertex are considered the same
3111
+ (e.g. ``['a', 'b', 'c', 'a']`` and ``['b', 'c', 'a',
3112
+ 'b']``). Otherwise, all cycles are enumerated.
3113
+
3114
+ - ``max_length`` -- nonnegative integer (default: ``None``); the
3115
+ maximum length of the enumerated paths. If set to ``None``, then all
3116
+ lengths are allowed.
3117
+
3118
+ - ``trivial`` -- boolean (default: ``False``); if set to ``True``, then
3119
+ the empty paths are also enumerated
3120
+
3121
+ - ``weight_function`` -- function (default: ``None``); a function that
3122
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
3123
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
3124
+ and ``by_weight`` is ``True``, we use the edge label ``l`` as a
3125
+ weight.
3126
+
3127
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, the edges
3128
+ in the graph are weighted, otherwise all edges have weight 1
3129
+
3130
+ - ``check_weight`` -- boolean (default: ``True``); whether to check that
3131
+ the ``weight_function`` outputs a number for each edge
3132
+
3133
+ - ``report_weight`` -- boolean (default: ``False``); if ``False``, just
3134
+ a cycle is returned. Otherwise a tuple of cycle length and cycle is
3135
+ returned.
3136
+
3137
+ - ``algorithm`` -- string (default: ``'A'``); the algorithm used to
3138
+ enumerate the cycles.
3139
+
3140
+ - The algorithm ``'A'`` holds cycle iterators starting with each vertex,
3141
+ and output them in increasing length order.
3142
+
3143
+ - The algorithm ``'B'`` holds cycle iterators starting with each edge,
3144
+ and output them in increasing length order. It depends on the k-shortest
3145
+ simple paths algorithm. Thus, it is not available if ``simple=False``.
3146
+
3147
+ OUTPUT: iterator
3148
+
3149
+ .. SEEALSO::
3150
+
3151
+ - :meth:`all_simple_cycles`
3152
+ - :meth:`~sage.graphs.path_enumeration.shortest_simple_paths`
3153
+
3154
+ AUTHOR:
3155
+
3156
+ Alexandre Blondin Masse
3157
+
3158
+ EXAMPLES::
3159
+
3160
+ sage: g = DiGraph({'a': ['a', 'b'], 'b': ['c'], 'c': ['d'], 'd': ['c']}, loops=True)
3161
+ sage: it = g.all_cycles_iterator()
3162
+ sage: for _ in range(7): print(next(it))
3163
+ ['a', 'a']
3164
+ ['a', 'a', 'a']
3165
+ ['c', 'd', 'c']
3166
+ ['a', 'a', 'a', 'a']
3167
+ ['a', 'a', 'a', 'a', 'a']
3168
+ ['c', 'd', 'c', 'd', 'c']
3169
+ ['a', 'a', 'a', 'a', 'a', 'a']
3170
+
3171
+ There are no cycles in the empty graph and in acyclic graphs::
3172
+
3173
+ sage: g = DiGraph()
3174
+ sage: it = g.all_cycles_iterator()
3175
+ sage: list(it)
3176
+ []
3177
+ sage: g = DiGraph({0:[1]})
3178
+ sage: it = g.all_cycles_iterator()
3179
+ sage: list(it)
3180
+ []
3181
+
3182
+ It is possible to restrict the starting vertices of the cycles::
3183
+
3184
+ sage: g = DiGraph({'a': ['a', 'b'], 'b': ['c'], 'c': ['d'], 'd': ['c']}, loops=True)
3185
+ sage: it = g.all_cycles_iterator(starting_vertices=['b', 'c'])
3186
+ sage: for _ in range(3): print(next(it))
3187
+ ['c', 'd', 'c']
3188
+ ['c', 'd', 'c', 'd', 'c']
3189
+ ['c', 'd', 'c', 'd', 'c', 'd', 'c']
3190
+
3191
+ Also, one can bound the length of the cycles::
3192
+
3193
+ sage: it = g.all_cycles_iterator(max_length=3)
3194
+ sage: list(it)
3195
+ [['a', 'a'], ['a', 'a', 'a'], ['c', 'd', 'c'],
3196
+ ['a', 'a', 'a', 'a']]
3197
+
3198
+ By default, cycles differing only by their starting point are not all
3199
+ enumerated, but this may be parametrized::
3200
+
3201
+ sage: it = g.all_cycles_iterator(max_length=3, rooted=False)
3202
+ sage: list(it)
3203
+ [['a', 'a'], ['a', 'a', 'a'], ['c', 'd', 'c'],
3204
+ ['a', 'a', 'a', 'a']]
3205
+ sage: it = g.all_cycles_iterator(max_length=3, rooted=True)
3206
+ sage: list(it)
3207
+ [['a', 'a'], ['a', 'a', 'a'], ['c', 'd', 'c'], ['d', 'c', 'd'],
3208
+ ['a', 'a', 'a', 'a']]
3209
+
3210
+ One may prefer to enumerate simple cycles, i.e. cycles such that the only
3211
+ vertex occurring twice in it is the starting and ending one (see also
3212
+ :meth:`all_simple_cycles`)::
3213
+
3214
+ sage: it = g.all_cycles_iterator(simple=True)
3215
+ sage: list(it)
3216
+ [['a', 'a'], ['c', 'd', 'c']]
3217
+ sage: g = digraphs.Circuit(4)
3218
+ sage: list(g.all_cycles_iterator(simple=True))
3219
+ [[0, 1, 2, 3, 0]]
3220
+
3221
+ A cycle is enumerated in increasing length order for a weighted graph::
3222
+
3223
+ sage: g = DiGraph()
3224
+ sage: g.add_edges([('a', 'b', 2), ('a', 'e', 2), ('b', 'a', 1), ('b', 'c', 1),
3225
+ ....: ('c', 'd', 2), ('d', 'b', 1), ('e', 'a', 2)])
3226
+ sage: it = g.all_cycles_iterator(simple=False, max_length=None,
3227
+ ....: by_weight=True, report_weight=True)
3228
+ sage: for i in range(9): print(next(it))
3229
+ (3, ['a', 'b', 'a'])
3230
+ (4, ['a', 'e', 'a'])
3231
+ (4, ['b', 'c', 'd', 'b'])
3232
+ (6, ['a', 'b', 'a', 'b', 'a'])
3233
+ (7, ['a', 'b', 'a', 'e', 'a'])
3234
+ (7, ['a', 'b', 'c', 'd', 'b', 'a'])
3235
+ (7, ['a', 'e', 'a', 'b', 'a'])
3236
+ (8, ['a', 'e', 'a', 'e', 'a'])
3237
+ (8, ['b', 'c', 'd', 'b', 'c', 'd', 'b'])
3238
+
3239
+ Each edge must have a positive weight::
3240
+
3241
+ sage: g = DiGraph()
3242
+ sage: g.add_edges([('a', 'b', -2), ('b', 'a', 1)])
3243
+ sage: next(g.all_cycles_iterator(simple=False, max_length=None,
3244
+ ....: by_weight=True, report_weight=True))
3245
+ Traceback (most recent call last):
3246
+ ...
3247
+ ValueError: negative weight is not allowed
3248
+
3249
+ The algorithm ``'B'`` is available only when `simple=True`::
3250
+
3251
+ sage: g = DiGraph()
3252
+ sage: g.add_edges([('a', 'b', 1), ('b', 'a', 1)])
3253
+ sage: next(g.all_cycles_iterator(algorithm='B', simple=False))
3254
+ ....:
3255
+ Traceback (most recent call last):
3256
+ ...
3257
+ ValueError: The algorithm 'B' is available only when simple=True.
3258
+ """
3259
+ if starting_vertices is None:
3260
+ starting_vertices = self
3261
+ # Since a cycle is always included in a given strongly connected
3262
+ # component, we may remove edges from the graph
3263
+ sccs = self.strongly_connected_components()
3264
+ d = {}
3265
+ for id, component in enumerate(sccs):
3266
+ for v in component:
3267
+ d[v] = id
3268
+ h = copy(self)
3269
+ h.delete_edges((u, v) for u, v in h.edge_iterator(labels=False) if d[u] != d[v])
3270
+
3271
+ by_weight, weight_function = self._get_weight_function(by_weight=by_weight,
3272
+ weight_function=weight_function,
3273
+ check_weight=check_weight)
3274
+
3275
+ if by_weight:
3276
+ for e in h.edge_iterator():
3277
+ if weight_function(e) < 0:
3278
+ raise ValueError("negative weight is not allowed")
3279
+
3280
+ if algorithm == 'A':
3281
+ # We create one cycles iterator per vertex. This is necessary if we
3282
+ # want to iterate over cycles with increasing length.
3283
+ def cycle_iter(v):
3284
+ return h._all_cycles_iterator_vertex(v,
3285
+ starting_vertices=starting_vertices,
3286
+ simple=simple,
3287
+ rooted=rooted,
3288
+ max_length=max_length,
3289
+ trivial=trivial,
3290
+ remove_acyclic_edges=False,
3291
+ weight_function=weight_function,
3292
+ by_weight=by_weight,
3293
+ check_weight=check_weight,
3294
+ report_weight=True)
3295
+
3296
+ iterators = {v: cycle_iter(v) for v in starting_vertices}
3297
+ elif algorithm == 'B':
3298
+ if not simple:
3299
+ raise ValueError("The algorithm 'B' is available only when simple=True.")
3300
+
3301
+ def simple_cycle_iter(hh, e):
3302
+ return hh._all_simple_cycles_iterator_edge(e,
3303
+ max_length=max_length,
3304
+ remove_acyclic_edges=False,
3305
+ weight_function=weight_function,
3306
+ by_weight=by_weight,
3307
+ check_weight=check_weight,
3308
+ report_weight=True)
3309
+
3310
+ SCCS = h.strongly_connected_components_subgraphs()
3311
+ iterators = dict()
3312
+ while SCCS:
3313
+ hh = SCCS.pop()
3314
+ if not hh.size():
3315
+ continue
3316
+ e = next(hh.edge_iterator())
3317
+ iterators[e] = simple_cycle_iter(hh, e)
3318
+ hh.delete_edge(e)
3319
+ SCCS.extend(hh.strongly_connected_components_subgraphs())
3320
+ else:
3321
+ raise ValueError(f"The algorithm {algorithm} is not valid. \
3322
+ Use the algorithm 'A' or 'B'.")
3323
+
3324
+ cycles = []
3325
+ for key, it in iterators.items():
3326
+ try:
3327
+ length, cycle = next(it)
3328
+ cycles.append((length, cycle, key))
3329
+ except StopIteration:
3330
+ pass
3331
+ # Since we always extract a shortest path, using a heap
3332
+ # can speed up the algorithm
3333
+ from heapq import heapify, heappop, heappush
3334
+ heapify(cycles)
3335
+ while cycles:
3336
+ # We choose the shortest available cycle
3337
+ length, shortest_cycle, key = heappop(cycles)
3338
+ if report_weight:
3339
+ yield (length, shortest_cycle)
3340
+ else:
3341
+ yield shortest_cycle
3342
+ # We update the cycle iterator to its next available cycle if it
3343
+ # exists
3344
+ try:
3345
+ length, cycle = next(iterators[key])
3346
+ heappush(cycles, (length, cycle, key))
3347
+ except StopIteration:
3348
+ pass
3349
+
3350
+ def all_simple_cycles(self, starting_vertices=None, rooted=False,
3351
+ max_length=None, trivial=False,
3352
+ weight_function=None, by_weight=False,
3353
+ check_weight=True, report_weight=False,
3354
+ algorithm='A'):
3355
+ r"""
3356
+ Return a list of all simple cycles of ``self``. The cycles are
3357
+ enumerated in increasing length order. Each edge must have a
3358
+ positive weight.
3359
+
3360
+ INPUT:
3361
+
3362
+ - ``starting_vertices`` -- iterable (default: ``None``); vertices from
3363
+ which the cycles must start. If ``None``, then all vertices of the
3364
+ graph can be starting points. This argument is necessary if ``rooted``
3365
+ is set to ``True``.
3366
+
3367
+ - ``rooted`` -- boolean (default: ``False``); if set to False, then
3368
+ cycles differing only by their starting vertex are considered the same
3369
+ (e.g. ``['a', 'b', 'c', 'a']`` and ``['b', 'c', 'a',
3370
+ 'b']``). Otherwise, all cycles are enumerated.
3371
+
3372
+ - ``max_length`` -- nonnegative integer (default: ``None``); the
3373
+ maximum length of the enumerated paths. If set to ``None``, then all
3374
+ lengths are allowed.
3375
+
3376
+ - ``trivial`` -- boolean (default: ``False``); if set to ``True``, then
3377
+ the empty paths are also enumerated
3378
+
3379
+ - ``weight_function`` -- function (default: ``None``); a function that
3380
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
3381
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
3382
+ and ``by_weight`` is ``True``, we use the edge label ``l`` as a
3383
+ weight.
3384
+
3385
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, the edges
3386
+ in the graph are weighted, otherwise all edges have weight 1
3387
+
3388
+ - ``check_weight`` -- boolean (default: ``True``); whether to check that
3389
+ the ``weight_function`` outputs a number for each edge
3390
+
3391
+ - ``report_weight`` -- boolean (default: ``False``); if ``False``, just
3392
+ a cycle is returned. Otherwise a tuple of cycle length and cycle is
3393
+ returned.
3394
+
3395
+ - ``algorithm`` -- string (default: ``'A'``); the algorithm used to
3396
+ enumerate the cycles.
3397
+
3398
+ - The algorithm ``'A'`` holds cycle iterators starting with each vertex,
3399
+ and output them in increasing length order.
3400
+
3401
+ - The algorithm ``'B'`` holds cycle iterators starting with each edge,
3402
+ and output them in increasing length order.
3403
+
3404
+ OUTPUT: list
3405
+
3406
+ .. NOTE::
3407
+
3408
+ Although the number of simple cycles of a finite graph is always
3409
+ finite, computing all its cycles may take a very long time.
3410
+
3411
+ EXAMPLES::
3412
+
3413
+ sage: g = DiGraph({'a': ['a', 'b'], 'b': ['c'], 'c': ['d'], 'd': ['c']}, loops=True)
3414
+ sage: g.all_simple_cycles()
3415
+ [['a', 'a'], ['c', 'd', 'c']]
3416
+
3417
+ The directed version of the Petersen graph::
3418
+
3419
+ sage: g = graphs.PetersenGraph().to_directed()
3420
+ sage: g.all_simple_cycles(max_length=4)
3421
+ [[0, 1, 0], [0, 4, 0], [0, 5, 0], [1, 2, 1], [1, 6, 1], [2, 3, 2],
3422
+ [2, 7, 2], [3, 4, 3], [3, 8, 3], [4, 9, 4], [5, 7, 5], [5, 8, 5],
3423
+ [6, 8, 6], [6, 9, 6], [7, 9, 7]]
3424
+ sage: g.all_simple_cycles(max_length=6)
3425
+ [[0, 1, 0], [0, 4, 0], [0, 5, 0], [1, 2, 1], [1, 6, 1], [2, 3, 2],
3426
+ [2, 7, 2], [3, 4, 3], [3, 8, 3], [4, 9, 4], [5, 7, 5], [5, 8, 5],
3427
+ [6, 8, 6], [6, 9, 6], [7, 9, 7], [0, 1, 2, 3, 4, 0],
3428
+ [0, 1, 2, 7, 5, 0], [0, 1, 6, 8, 5, 0], [0, 1, 6, 9, 4, 0],
3429
+ [0, 4, 3, 2, 1, 0], [0, 4, 3, 8, 5, 0], [0, 4, 9, 6, 1, 0],
3430
+ [0, 4, 9, 7, 5, 0], [0, 5, 7, 2, 1, 0], [0, 5, 7, 9, 4, 0],
3431
+ [0, 5, 8, 3, 4, 0], [0, 5, 8, 6, 1, 0], [1, 2, 3, 8, 6, 1],
3432
+ [1, 2, 7, 9, 6, 1], [1, 6, 8, 3, 2, 1], [1, 6, 9, 7, 2, 1],
3433
+ [2, 3, 4, 9, 7, 2], [2, 3, 8, 5, 7, 2], [2, 7, 5, 8, 3, 2],
3434
+ [2, 7, 9, 4, 3, 2], [3, 4, 9, 6, 8, 3], [3, 8, 6, 9, 4, 3],
3435
+ [5, 7, 9, 6, 8, 5], [5, 8, 6, 9, 7, 5], [0, 1, 2, 3, 8, 5, 0],
3436
+ [0, 1, 2, 7, 9, 4, 0], [0, 1, 6, 8, 3, 4, 0],
3437
+ [0, 1, 6, 9, 7, 5, 0], [0, 4, 3, 2, 7, 5, 0],
3438
+ [0, 4, 3, 8, 6, 1, 0], [0, 4, 9, 6, 8, 5, 0],
3439
+ [0, 4, 9, 7, 2, 1, 0], [0, 5, 7, 2, 3, 4, 0],
3440
+ [0, 5, 7, 9, 6, 1, 0], [0, 5, 8, 3, 2, 1, 0],
3441
+ [0, 5, 8, 6, 9, 4, 0], [1, 2, 3, 4, 9, 6, 1],
3442
+ [1, 2, 7, 5, 8, 6, 1], [1, 6, 8, 5, 7, 2, 1],
3443
+ [1, 6, 9, 4, 3, 2, 1], [2, 3, 8, 6, 9, 7, 2],
3444
+ [2, 7, 9, 6, 8, 3, 2], [3, 4, 9, 7, 5, 8, 3],
3445
+ [3, 8, 5, 7, 9, 4, 3]]
3446
+
3447
+ The complete graph (without loops) on `4` vertices::
3448
+
3449
+ sage: g = graphs.CompleteGraph(4).to_directed()
3450
+ sage: g.all_simple_cycles()
3451
+ [[0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 2, 1], [1, 3, 1], [2, 3, 2],
3452
+ [0, 1, 2, 0], [0, 1, 3, 0], [0, 2, 1, 0], [0, 2, 3, 0],
3453
+ [0, 3, 1, 0], [0, 3, 2, 0], [1, 2, 3, 1], [1, 3, 2, 1],
3454
+ [0, 1, 2, 3, 0], [0, 1, 3, 2, 0], [0, 2, 1, 3, 0],
3455
+ [0, 2, 3, 1, 0], [0, 3, 1, 2, 0], [0, 3, 2, 1, 0]]
3456
+
3457
+ If the graph contains a large number of cycles, one can bound the length
3458
+ of the cycles, or simply restrict the possible starting vertices of the
3459
+ cycles::
3460
+
3461
+ sage: g = graphs.CompleteGraph(20).to_directed()
3462
+ sage: g.all_simple_cycles(max_length=2)
3463
+ [[0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0],
3464
+ [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 0], [0, 12, 0], [0, 13, 0],
3465
+ [0, 14, 0], [0, 15, 0], [0, 16, 0], [0, 17, 0], [0, 18, 0], [0, 19, 0],
3466
+ [1, 2, 1], [1, 3, 1], [1, 4, 1], [1, 5, 1], [1, 6, 1], [1, 7, 1], [1, 8, 1],
3467
+ [1, 9, 1], [1, 10, 1], [1, 11, 1], [1, 12, 1], [1, 13, 1], [1, 14, 1],
3468
+ [1, 15, 1], [1, 16, 1], [1, 17, 1], [1, 18, 1], [1, 19, 1], [2, 3, 2],
3469
+ [2, 4, 2], [2, 5, 2], [2, 6, 2], [2, 7, 2], [2, 8, 2], [2, 9, 2], [2, 10, 2],
3470
+ [2, 11, 2], [2, 12, 2], [2, 13, 2], [2, 14, 2], [2, 15, 2], [2, 16, 2],
3471
+ [2, 17, 2], [2, 18, 2], [2, 19, 2], [3, 4, 3], [3, 5, 3], [3, 6, 3],
3472
+ [3, 7, 3], [3, 8, 3], [3, 9, 3], [3, 10, 3], [3, 11, 3], [3, 12, 3],
3473
+ [3, 13, 3], [3, 14, 3], [3, 15, 3], [3, 16, 3], [3, 17, 3], [3, 18, 3],
3474
+ [3, 19, 3], [4, 5, 4], [4, 6, 4], [4, 7, 4], [4, 8, 4], [4, 9, 4], [4, 10, 4],
3475
+ [4, 11, 4], [4, 12, 4], [4, 13, 4], [4, 14, 4], [4, 15, 4], [4, 16, 4],
3476
+ [4, 17, 4], [4, 18, 4], [4, 19, 4], [5, 6, 5], [5, 7, 5], [5, 8, 5],
3477
+ [5, 9, 5], [5, 10, 5], [5, 11, 5], [5, 12, 5], [5, 13, 5], [5, 14, 5],
3478
+ [5, 15, 5], [5, 16, 5], [5, 17, 5], [5, 18, 5], [5, 19, 5], [6, 7, 6],
3479
+ [6, 8, 6], [6, 9, 6], [6, 10, 6], [6, 11, 6], [6, 12, 6], [6, 13, 6],
3480
+ [6, 14, 6], [6, 15, 6], [6, 16, 6], [6, 17, 6], [6, 18, 6], [6, 19, 6],
3481
+ [7, 8, 7], [7, 9, 7], [7, 10, 7], [7, 11, 7], [7, 12, 7], [7, 13, 7],
3482
+ [7, 14, 7], [7, 15, 7], [7, 16, 7], [7, 17, 7], [7, 18, 7], [7, 19, 7],
3483
+ [8, 9, 8], [8, 10, 8], [8, 11, 8], [8, 12, 8], [8, 13, 8], [8, 14, 8],
3484
+ [8, 15, 8], [8, 16, 8], [8, 17, 8], [8, 18, 8], [8, 19, 8], [9, 10, 9],
3485
+ [9, 11, 9], [9, 12, 9], [9, 13, 9], [9, 14, 9], [9, 15, 9], [9, 16, 9],
3486
+ [9, 17, 9], [9, 18, 9], [9, 19, 9], [10, 11, 10], [10, 12, 10], [10, 13, 10],
3487
+ [10, 14, 10], [10, 15, 10], [10, 16, 10], [10, 17, 10], [10, 18, 10],
3488
+ [10, 19, 10], [11, 12, 11], [11, 13, 11], [11, 14, 11], [11, 15, 11],
3489
+ [11, 16, 11], [11, 17, 11], [11, 18, 11], [11, 19, 11], [12, 13, 12],
3490
+ [12, 14, 12], [12, 15, 12], [12, 16, 12], [12, 17, 12], [12, 18, 12],
3491
+ [12, 19, 12], [13, 14, 13], [13, 15, 13], [13, 16, 13], [13, 17, 13],
3492
+ [13, 18, 13], [13, 19, 13], [14, 15, 14], [14, 16, 14], [14, 17, 14],
3493
+ [14, 18, 14], [14, 19, 14], [15, 16, 15], [15, 17, 15], [15, 18, 15],
3494
+ [15, 19, 15], [16, 17, 16], [16, 18, 16], [16, 19, 16], [17, 18, 17],
3495
+ [17, 19, 17], [18, 19, 18]]
3496
+
3497
+ sage: g = graphs.CompleteGraph(20).to_directed()
3498
+ sage: g.all_simple_cycles(max_length=2, starting_vertices=[0])
3499
+ [[0, 1, 0], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0],
3500
+ [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0],
3501
+ [0, 11, 0], [0, 12, 0], [0, 13, 0], [0, 14, 0], [0, 15, 0],
3502
+ [0, 16, 0], [0, 17, 0], [0, 18, 0], [0, 19, 0]]
3503
+
3504
+ One may prefer to distinguish equivalent cycles having distinct starting
3505
+ vertices (compare the following examples)::
3506
+
3507
+ sage: g = graphs.CompleteGraph(4).to_directed()
3508
+ sage: g.all_simple_cycles(max_length=2, rooted=False)
3509
+ [[0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 2, 1], [1, 3, 1], [2, 3, 2]]
3510
+ sage: g.all_simple_cycles(max_length=2, rooted=True)
3511
+ [[0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 0, 1], [1, 2, 1], [1, 3, 1],
3512
+ [2, 0, 2], [2, 1, 2], [2, 3, 2], [3, 0, 3], [3, 1, 3], [3, 2, 3]]
3513
+
3514
+ A cycle is enumerated in increasing length order for a weighted graph::
3515
+
3516
+ sage: cycles = g.all_simple_cycles(weight_function=lambda e:e[0]+e[1],
3517
+ ....: by_weight=True, report_weight=True)
3518
+ sage: cycles
3519
+ [(2, [0, 1, 0]), (4, [0, 2, 0]), (6, [0, 1, 2, 0]), (6, [0, 2, 1, 0]),
3520
+ (6, [0, 3, 0]), (6, [1, 2, 1]), (8, [0, 1, 3, 0]), (8, [0, 3, 1, 0]),
3521
+ (8, [1, 3, 1]), (10, [0, 2, 3, 0]), (10, [0, 3, 2, 0]), (10, [2, 3, 2]),
3522
+ (12, [0, 1, 2, 3, 0]), (12, [0, 1, 3, 2, 0]), (12, [0, 2, 1, 3, 0]),
3523
+ (12, [0, 2, 3, 1, 0]), (12, [0, 3, 1, 2, 0]), (12, [0, 3, 2, 1, 0]),
3524
+ (12, [1, 2, 3, 1]), (12, [1, 3, 2, 1])]
3525
+
3526
+ The algorithm ``'B'`` can be used::
3527
+
3528
+ sage: cycles_B = g.all_simple_cycles(weight_function=lambda e:e[0]+e[1], by_weight=True,
3529
+ ....: report_weight=True, algorithm='B')
3530
+ sage: cycles_B
3531
+ [(2, [0, 1, 0]), (4, [0, 2, 0]), (6, [0, 1, 2, 0]), (6, [0, 2, 1, 0]),
3532
+ (6, [0, 3, 0]), (6, [1, 2, 1]), (8, [0, 1, 3, 0]), (8, [0, 3, 1, 0]),
3533
+ (8, [1, 3, 1]), (10, [0, 2, 3, 0]), (10, [0, 3, 2, 0]), (10, [2, 3, 2]),
3534
+ (12, [0, 1, 3, 2, 0]), (12, [0, 1, 2, 3, 0]), (12, [0, 2, 3, 1, 0]),
3535
+ (12, [0, 2, 1, 3, 0]), (12, [0, 3, 2, 1, 0]), (12, [0, 3, 1, 2, 0]),
3536
+ (12, [1, 2, 3, 1]), (12, [1, 3, 2, 1])]
3537
+ sage: cycles.sort() == cycles_B.sort()
3538
+ True
3539
+ """
3540
+ return list(self.all_cycles_iterator(starting_vertices=starting_vertices,
3541
+ simple=True, rooted=rooted,
3542
+ max_length=max_length, trivial=trivial,
3543
+ weight_function=weight_function,
3544
+ by_weight=by_weight,
3545
+ check_weight=check_weight,
3546
+ report_weight=report_weight,
3547
+ algorithm=algorithm))
3548
+
3549
+ def path_semigroup(self):
3550
+ """
3551
+ The partial semigroup formed by the paths of this quiver.
3552
+
3553
+ EXAMPLES::
3554
+
3555
+ sage: Q = DiGraph({1: {2: ['a', 'c']}, 2: {3: ['b']}})
3556
+ sage: F = Q.path_semigroup(); F # needs sage.libs.flint
3557
+ Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices
3558
+ sage: list(F) # needs sage.libs.flint
3559
+ [e_1, e_2, e_3, a, c, b, a*b, c*b]
3560
+ """
3561
+ from sage.quivers.path_semigroup import PathSemigroup
3562
+ return PathSemigroup(self)
3563
+
3564
+ def auslander_reiten_quiver(self):
3565
+ r"""
3566
+ Return the Auslander-Reiten quiver of ``self``.
3567
+
3568
+ .. SEEALSO::
3569
+
3570
+ :class:`~sage.quivers.ar_quiver.AuslanderReitenQuiver`
3571
+
3572
+ EXAMPLES::
3573
+
3574
+ sage: D = DiGraph([[1,2,'a'], [1,2,'b']], multiedges=True)
3575
+ sage: D.auslander_reiten_quiver()
3576
+ Auslander-Reiten quiver of Multi-digraph on 2 vertices
3577
+ """
3578
+ from sage.quivers.ar_quiver import AuslanderReitenQuiver
3579
+ return AuslanderReitenQuiver(self)
3580
+
3581
+ # Directed Acyclic Graphs (DAGs)
3582
+
3583
+ def topological_sort(self, implementation='default'):
3584
+ """
3585
+ Return a topological sort of the digraph if it is acyclic.
3586
+
3587
+ If the digraph contains a directed cycle, a :exc:`TypeError`
3588
+ is raised. As topological sorts are not necessarily unique,
3589
+ different implementations may yield different results.
3590
+
3591
+ A topological sort is an ordering of the vertices of the digraph such
3592
+ that each vertex comes before all of its successors. That is, if `u`
3593
+ comes before `v` in the sort, then there may be a directed path from `u`
3594
+ to `v`, but there will be no directed path from `v` to `u`.
3595
+
3596
+ INPUT:
3597
+
3598
+ - ``implementation`` -- string (default: ``'default'``); either use the
3599
+ default Cython implementation, or the default NetworkX library
3600
+ (``implementation = "NetworkX"``)
3601
+
3602
+ .. SEEALSO::
3603
+
3604
+ - :meth:`is_directed_acyclic` -- tests whether a directed graph is
3605
+ acyclic (can also join a certificate; a topological sort or a
3606
+ circuit in the graph)
3607
+
3608
+ EXAMPLES::
3609
+
3610
+ sage: D = DiGraph({0: [1, 2, 3], 4: [2, 5], 1: [8], 2: [7], 3: [7],
3611
+ ....: 5: [6, 7], 7: [8], 6: [9], 8: [10], 9: [10]})
3612
+ sage: D.plot(layout='circular').show() # needs sage.plot
3613
+ sage: D.topological_sort()
3614
+ [4, 5, 6, 9, 0, 1, 2, 3, 7, 8, 10]
3615
+
3616
+ ::
3617
+
3618
+ sage: D.add_edge(9, 7)
3619
+ sage: D.topological_sort()
3620
+ [4, 5, 6, 9, 0, 1, 2, 3, 7, 8, 10]
3621
+
3622
+ Using the NetworkX implementation ::
3623
+
3624
+ sage: s = list(D.topological_sort(implementation="NetworkX")); s # random # needs networkx
3625
+ [0, 4, 1, 3, 2, 5, 6, 9, 7, 8, 10]
3626
+ sage: all(s.index(u) < s.index(v) # needs networkx
3627
+ ....: for u, v in D.edges(sort=False, labels=False))
3628
+ True
3629
+
3630
+ ::
3631
+
3632
+ sage: D.add_edge(7, 4)
3633
+ sage: D.topological_sort()
3634
+ Traceback (most recent call last):
3635
+ ...
3636
+ TypeError: digraph is not acyclic; there is no topological sort
3637
+
3638
+ TESTS:
3639
+
3640
+ A wrong value for the ``implementation`` keyword::
3641
+
3642
+ sage: D.topological_sort(implementation = "cloud-reading")
3643
+ Traceback (most recent call last):
3644
+ ...
3645
+ ValueError: implementation must be set to one of "default" or "NetworkX"
3646
+ """
3647
+ if implementation == "default":
3648
+ b, ordering = self._backend.is_directed_acyclic(certificate=True)
3649
+ if b:
3650
+ return ordering
3651
+ else:
3652
+ raise TypeError('digraph is not acyclic; there is no topological sort')
3653
+
3654
+ elif implementation == "NetworkX":
3655
+ import networkx
3656
+ S = networkx.topological_sort(self.networkx_graph())
3657
+ if S is None:
3658
+ raise TypeError('digraph is not acyclic; there is no topological sort')
3659
+ else:
3660
+ return S
3661
+
3662
+ raise ValueError("implementation must be set to one of \"default\" or \"NetworkX\"")
3663
+
3664
+ def topological_sort_generator(self):
3665
+ """
3666
+ Return an iterator over all topological sorts of the digraph if
3667
+ it is acyclic.
3668
+
3669
+ If the digraph contains a directed cycle, a :exc:`TypeError`
3670
+ is raised.
3671
+
3672
+ A topological sort is an ordering of the vertices of the digraph such
3673
+ that each vertex comes before all of its successors. That is, if u comes
3674
+ before v in the sort, then there may be a directed path from u to v, but
3675
+ there will be no directed path from v to u. See also
3676
+ :meth:`topological_sort`.
3677
+
3678
+ AUTHORS:
3679
+
3680
+ - Mike Hansen - original implementation
3681
+
3682
+ - Robert L. Miller: wrapping, documentation
3683
+
3684
+ REFERENCE:
3685
+
3686
+ - [1] Pruesse, Gara and Ruskey, Frank. Generating Linear
3687
+ Extensions Fast. SIAM J. Comput., Vol. 23 (1994), no. 2, pp.
3688
+ 373-386.
3689
+
3690
+ EXAMPLES::
3691
+
3692
+ sage: D = DiGraph({0: [1, 2], 1: [3], 2: [3, 4]})
3693
+ sage: D.plot(layout='circular').show() # needs sage.plot
3694
+ sage: list(D.topological_sort_generator()) # needs sage.modules sage.rings.finite_rings
3695
+ [[0, 1, 2, 3, 4], [0, 2, 1, 3, 4], [0, 2, 1, 4, 3],
3696
+ [0, 2, 4, 1, 3], [0, 1, 2, 4, 3]]
3697
+
3698
+ ::
3699
+
3700
+ sage: for sort in D.topological_sort_generator(): # needs sage.modules sage.rings.finite_rings
3701
+ ....: for u, v in D.edge_iterator(labels=False):
3702
+ ....: if sort.index(u) > sort.index(v):
3703
+ ....: print("this should never happen")
3704
+ """
3705
+ from sage.combinat.posets.posets import Poset
3706
+ return Poset(self).linear_extensions()
3707
+
3708
+ # Visualization
3709
+
3710
+ def layout_acyclic(self, rankdir='up', **options):
3711
+ """
3712
+ Return a ranked layout so that all edges point upward.
3713
+
3714
+ To this end, the heights of the vertices are set according to the level
3715
+ set decomposition of the graph (see :meth:`.level_sets`).
3716
+
3717
+ This is achieved by calling ``graphviz`` and ``dot2tex`` if available
3718
+ (see :meth:`.layout_graphviz`), and using a spring layout with fixed
3719
+ vertical placement of the vertices otherwise (see
3720
+ :meth:`.layout_acyclic_dummy` and
3721
+ :meth:`~sage.graphs.generic_graph.GenericGraph.layout_ranked`).
3722
+
3723
+ Non acyclic graphs are partially supported by ``graphviz``, which then
3724
+ chooses some edges to point down.
3725
+
3726
+ INPUT:
3727
+
3728
+ - ``rankdir`` -- string (default: ``'up'``); indicates which direction
3729
+ the edges should point toward among ``'up'``, ``'down'``, ``'left'``,
3730
+ or ``'right'``
3731
+
3732
+ - ``**options`` -- passed down to
3733
+ :meth:`~sage.graphs.generic_graph.GenericGraph.layout_ranked` or
3734
+ :meth:`~sage.graphs.generic_graph.GenericGraph.layout_graphviz`
3735
+
3736
+ EXAMPLES::
3737
+
3738
+ sage: H = DiGraph({0: [1, 2], 1: [3], 2: [3], 3: [], 5: [1, 6], 6: [2, 3]})
3739
+
3740
+ The actual layout computed depends on whether dot2tex and graphviz are
3741
+ installed, so we don't test its relative values::
3742
+
3743
+ sage: H.layout_acyclic()
3744
+ {0: [..., ...], 1: [..., ...], 2: [..., ...], 3: [..., ...], 5: [..., ...], 6: [..., ...]}
3745
+
3746
+ sage: H = DiGraph({0: [1]})
3747
+ sage: pos = H.layout_acyclic(rankdir='up')
3748
+ sage: pos[1][1] > pos[0][1] + .5
3749
+ True
3750
+ sage: pos = H.layout_acyclic(rankdir='down')
3751
+ sage: pos[1][1] < pos[0][1] - .5
3752
+ True
3753
+ sage: pos = H.layout_acyclic(rankdir='right')
3754
+ sage: pos[1][0] > pos[0][0] + .5
3755
+ True
3756
+ sage: pos = H.layout_acyclic(rankdir='left')
3757
+ sage: pos[1][0] < pos[0][0] - .5
3758
+ True
3759
+ """
3760
+ if have_dot2tex():
3761
+ return self.layout_graphviz(rankdir=rankdir, **options)
3762
+ return self.layout_acyclic_dummy(rankdir=rankdir, **options)
3763
+
3764
+ def layout_acyclic_dummy(self, heights=None, rankdir='up', **options):
3765
+ """
3766
+ Return a ranked layout so that all edges point upward.
3767
+
3768
+ To this end, the heights of the vertices are set according to the level
3769
+ set decomposition of the graph (see :meth:`level_sets`). This is
3770
+ achieved by a spring layout with fixed vertical placement of the
3771
+ vertices otherwise (see :meth:`layout_acyclic_dummy` and
3772
+ :meth:`~sage.graphs.generic_graph.GenericGraph.layout_ranked`).
3773
+
3774
+ INPUT:
3775
+
3776
+ - ``rankdir`` -- string (default: ``'up'``); indicates which direction
3777
+ the edges should point toward among ``'up'``, ``'down'``, ``'left'``,
3778
+ or ``'right'``
3779
+
3780
+ - ``**options`` -- passed down to
3781
+ :meth:`~sage.graphs.generic_graph.GenericGraph.layout_ranked`
3782
+
3783
+ EXAMPLES::
3784
+
3785
+ sage: H = DiGraph({0: [1, 2], 1: [3], 2: [3], 3: [], 5: [1, 6], 6: [2, 3]})
3786
+ sage: H.layout_acyclic_dummy()
3787
+ {0: [1.0..., 0], 1: [1.0..., 1], 2: [1.5..., 2], 3: [1.5..., 3], 5: [2.0..., 0], 6: [2.0..., 1]}
3788
+
3789
+ sage: H = DiGraph({0: [1]})
3790
+ sage: H.layout_acyclic_dummy(rankdir='up')
3791
+ {0: [0.5..., 0], 1: [0.5..., 1]}
3792
+ sage: H.layout_acyclic_dummy(rankdir='down')
3793
+ {0: [0.5..., 1], 1: [0.5..., 0]}
3794
+ sage: H.layout_acyclic_dummy(rankdir='left')
3795
+ {0: [1, 0.5...], 1: [0, 0.5...]}
3796
+ sage: H.layout_acyclic_dummy(rankdir='right')
3797
+ {0: [0, 0.5...], 1: [1, 0.5...]}
3798
+ sage: H = DiGraph({0: [1, 2], 1: [3], 2: [3], 3: [1], 5: [1, 6], 6: [2, 3]})
3799
+ sage: H.layout_acyclic_dummy()
3800
+ Traceback (most recent call last):
3801
+ ...
3802
+ ValueError: `self` should be an acyclic graph
3803
+
3804
+ TESTS:
3805
+
3806
+ :issue:`31681` is fixed::
3807
+
3808
+ sage: H = DiGraph({0: [1], 'X': [1]}, format='dict_of_lists')
3809
+ sage: pos = H.layout_acyclic_dummy(rankdir='up')
3810
+ sage: pos['X'][1] == 0 and pos[0][1] == 0
3811
+ True
3812
+ sage: pos[1][1] == 1
3813
+ True
3814
+ """
3815
+ if heights is None:
3816
+ if not self.is_directed_acyclic():
3817
+ raise ValueError("`self` should be an acyclic graph")
3818
+ levels = self.level_sets()
3819
+ # Sort vertices in each level in best effort mode
3820
+ for i in range(len(levels)):
3821
+ try:
3822
+ label = sorted(levels[i])
3823
+ levels[i] = label
3824
+ except TypeError:
3825
+ continue
3826
+ if rankdir == 'down' or rankdir == 'left':
3827
+ levels.reverse()
3828
+ heights = {i: levels[i] for i in range(len(levels))}
3829
+ positions = self.layout_ranked(heights=heights, **options)
3830
+ if rankdir == 'left' or rankdir == 'right':
3831
+ for coordinates in positions.values():
3832
+ coordinates.reverse()
3833
+ return positions
3834
+
3835
+ def level_sets(self):
3836
+ r"""
3837
+ Return the level set decomposition of the digraph.
3838
+
3839
+ OUTPUT: list of non empty lists of vertices of this graph
3840
+
3841
+ The level set decomposition of the digraph is a list `l` such that the
3842
+ level `l[i]` contains all the vertices having all their predecessors in
3843
+ the levels `l[j]` for `j < i`, and at least one in level `l[i-1]`
3844
+ (unless `i = 0`).
3845
+
3846
+ The level decomposition contains exactly the vertices not occurring in
3847
+ any cycle of the graph. In particular, the graph is acyclic if and only
3848
+ if the decomposition forms a set partition of its vertices, and we
3849
+ recover the usual level set decomposition of the corresponding poset.
3850
+
3851
+ EXAMPLES::
3852
+
3853
+ sage: H = DiGraph({0: [1, 2], 1: [3], 2: [3], 3: [], 5: [1, 6], 6: [2, 3]})
3854
+ sage: H.level_sets()
3855
+ [[0, 5], [1, 6], [2], [3]]
3856
+
3857
+ sage: H = DiGraph({0: [1, 2], 1: [3], 2: [3], 3: [1], 5: [1, 6], 6: [2, 3]})
3858
+ sage: H.level_sets()
3859
+ [[0, 5], [6], [2]]
3860
+
3861
+ This routine is mostly used for Hasse diagrams of posets::
3862
+
3863
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3864
+ sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [3], 3: []})
3865
+ sage: [len(x) for x in H.level_sets()]
3866
+ [1, 2, 1]
3867
+
3868
+ ::
3869
+
3870
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3871
+ sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [4], 3: [4]})
3872
+ sage: [len(x) for x in H.level_sets()]
3873
+ [1, 2, 1, 1]
3874
+
3875
+ Complexity: `O(n+m)` in time and `O(n)` in memory (besides the storage
3876
+ of the graph itself), where `n` and `m` are respectively the number of
3877
+ vertices and edges (assuming that appending to a list is constant time,
3878
+ which it is not quite).
3879
+ """
3880
+ in_degrees = self.in_degree(labels=True)
3881
+ level = [x for x in in_degrees if not in_degrees[x]]
3882
+ Levels = []
3883
+ while level:
3884
+ Levels.append(level)
3885
+ new_level = []
3886
+ for x in level:
3887
+ for y in self.neighbor_out_iterator(x):
3888
+ in_degrees[y] -= 1
3889
+ if not in_degrees[y]:
3890
+ new_level.append(y)
3891
+ level = new_level
3892
+ return Levels
3893
+
3894
+ def is_aperiodic(self):
3895
+ r"""
3896
+ Return whether the current ``DiGraph`` is aperiodic.
3897
+
3898
+ A directed graph is aperiodic if there is no integer `k > 1` that
3899
+ divides the length of every cycle in the graph. See the
3900
+ :wikipedia:`Aperiodic_graph` for more information.
3901
+
3902
+ EXAMPLES:
3903
+
3904
+ The following graph has period ``2``, so it is not aperiodic::
3905
+
3906
+ sage: g = DiGraph({0: [1], 1: [0]})
3907
+ sage: g.is_aperiodic()
3908
+ False
3909
+
3910
+ The following graph has a cycle of length 2 and a cycle of length 3,
3911
+ so it is aperiodic::
3912
+
3913
+ sage: g = DiGraph({0: [1, 4], 1: [2], 2: [0], 4: [0]})
3914
+ sage: g.is_aperiodic()
3915
+ True
3916
+
3917
+ .. SEEALSO::
3918
+
3919
+ :meth:`period`
3920
+ """
3921
+ return self.period() == 1
3922
+
3923
+ def period(self):
3924
+ r"""
3925
+ Return the period of the current ``DiGraph``.
3926
+
3927
+ The period of a directed graph is the largest integer that divides the
3928
+ length of every cycle in the graph. See the :wikipedia:`Aperiodic_graph`
3929
+ for more information.
3930
+
3931
+ EXAMPLES:
3932
+
3933
+ The following graph has period ``2``::
3934
+
3935
+ sage: g = DiGraph({0: [1], 1: [0]})
3936
+ sage: g.period()
3937
+ 2
3938
+
3939
+ The following graph has a cycle of length 2 and a cycle of length 3,
3940
+ so it has period ``1``::
3941
+
3942
+ sage: g = DiGraph({0: [1, 4], 1: [2], 2: [0], 4: [0]})
3943
+ sage: g.period()
3944
+ 1
3945
+
3946
+ Here is an example of computing the period of a digraph which is not
3947
+ strongly connected. By definition, it is the :func:`gcd` of the periods
3948
+ of its strongly connected components::
3949
+
3950
+ sage: g = DiGraph({-1: [-2], -2: [-3], -3: [-1],
3951
+ ....: 1: [2], 2: [1]})
3952
+ sage: g.period()
3953
+ 1
3954
+ sage: sorted([s.period() for s
3955
+ ....: in g.strongly_connected_components_subgraphs()])
3956
+ [2, 3]
3957
+
3958
+ ALGORITHM:
3959
+
3960
+ See the networkX implementation of ``is_aperiodic``, that is based on
3961
+ breadth first search.
3962
+
3963
+ .. SEEALSO::
3964
+
3965
+ :meth:`is_aperiodic`
3966
+ """
3967
+ from sage.arith.misc import GCD as gcd
3968
+
3969
+ g = 0
3970
+
3971
+ for component in self.strongly_connected_components():
3972
+ levels = {s: None for s in component}
3973
+ vertices_in_scc = levels # considers level as a set
3974
+ s = component[0]
3975
+ levels[s] = 0
3976
+ this_level = [s]
3977
+ idx = 1
3978
+ while this_level:
3979
+ next_level = []
3980
+ for u in this_level:
3981
+ # we have levels[u] == idx - 1
3982
+ for v in self.neighbor_out_iterator(u):
3983
+ # ignore edges leaving the component
3984
+ if v not in vertices_in_scc:
3985
+ continue
3986
+ level_v = levels[v]
3987
+ if level_v is not None: # Non-Tree Edge
3988
+ g = gcd(g, idx - level_v)
3989
+ if g == 1:
3990
+ return 1
3991
+ else: # Tree Edge
3992
+ next_level.append(v)
3993
+ levels[v] = idx
3994
+ this_level = next_level
3995
+ idx += 1
3996
+
3997
+ return g
3998
+
3999
+ def flow_polytope(self, edges=None, ends=None, backend=None):
4000
+ r"""
4001
+ Return the flow polytope of a digraph.
4002
+
4003
+ The flow polytope of a directed graph is the polytope consisting of all
4004
+ nonnegative flows on the graph with a given set `S` of sources and a
4005
+ given set `T` of sinks.
4006
+
4007
+ A *flow* on a directed graph `G` with a given set `S` of sources and a
4008
+ given set `T` of sinks means an assignment of a nonnegative real to each
4009
+ edge of `G` such that the flow is conserved in each vertex outside of
4010
+ `S` and `T`, and there is a unit of flow entering each vertex in `S` and
4011
+ a unit of flow leaving each vertex in `T`. These flows clearly form a
4012
+ polytope in the space of all assignments of reals to the edges of `G`.
4013
+
4014
+ The polytope is empty unless the sets `S` and `T` are equinumerous.
4015
+
4016
+ By default, `S` is taken to be the set of all sources (i.e., vertices of
4017
+ indegree `0`) of `G`, and `T` is taken to be the set of all sinks (i.e.,
4018
+ vertices of outdegree `0`) of `G`. If a different choice of `S` and `T`
4019
+ is desired, it can be specified using the optional ``ends`` parameter.
4020
+
4021
+ The polytope is returned as a polytope in `\RR^m`, where `m` is the
4022
+ number of edges of the digraph ``self``. The `k`-th coordinate of a
4023
+ point in the polytope is the real assigned to the `k`-th edge of
4024
+ ``self``. The order of the edges is the one returned by
4025
+ ``self.edges(sort=True)``. If a different order is desired, it can be specified
4026
+ using the optional ``edges`` parameter.
4027
+
4028
+ The faces and volume of these polytopes are of interest. Examples of
4029
+ these polytopes are the Chan-Robbins-Yuen polytope and the
4030
+ Pitman-Stanley polytope [PS2002]_.
4031
+
4032
+ INPUT:
4033
+
4034
+ - ``edges`` -- list (default: ``None``); a list of edges of ``self``. If
4035
+ not specified, the list of all edges of ``self`` is used with the
4036
+ default ordering of ``self.edges(sort=True)``. This determines which coordinate
4037
+ of a point in the polytope will correspond to which edge of
4038
+ ``self``. It is also possible to specify a list which contains not all
4039
+ edges of ``self``; this results in a polytope corresponding to the
4040
+ flows which are `0` on all remaining edges. Notice that the edges
4041
+ entered here must be in the precisely same format as outputted by
4042
+ ``self.edges()``; so, if ``self.edges()`` outputs an edge in the form
4043
+ ``(1, 3, None)``, then ``(1, 3)`` will not do!
4044
+
4045
+ - ``ends`` -- (default: ``(self.sources(), self.sinks())``) a
4046
+ pair `(S, T)` of an iterable `S` and an iterable `T`
4047
+
4048
+ - ``backend`` -- string or ``None`` (default); the backend to use;
4049
+ see :meth:`sage.geometry.polyhedron.constructor.Polyhedron`
4050
+
4051
+ .. NOTE::
4052
+
4053
+ Flow polytopes can also be built through the ``polytopes.<tab>``
4054
+ object::
4055
+
4056
+ sage: polytopes.flow_polytope(digraphs.Path(5)) # needs sage.geometry.polyhedron
4057
+ A 0-dimensional polyhedron in QQ^4 defined as the convex hull of 1 vertex
4058
+
4059
+ EXAMPLES:
4060
+
4061
+ A commutative square::
4062
+
4063
+ sage: G = DiGraph({1: [2, 3], 2: [4], 3: [4]})
4064
+ sage: fl = G.flow_polytope(); fl # needs sage.geometry.polyhedron
4065
+ A 1-dimensional polyhedron in QQ^4 defined as the convex hull
4066
+ of 2 vertices
4067
+ sage: fl.vertices() # needs sage.geometry.polyhedron
4068
+ (A vertex at (0, 1, 0, 1), A vertex at (1, 0, 1, 0))
4069
+
4070
+ Using a different order for the edges of the graph::
4071
+
4072
+ sage: ordered_edges = G.edges(sort=True, key=lambda x: x[0] - x[1])
4073
+ sage: fl = G.flow_polytope(edges=ordered_edges); fl # needs sage.geometry.polyhedron
4074
+ A 1-dimensional polyhedron in QQ^4 defined as the convex hull of 2 vertices
4075
+ sage: fl.vertices() # needs sage.geometry.polyhedron
4076
+ (A vertex at (0, 1, 1, 0), A vertex at (1, 0, 0, 1))
4077
+
4078
+ A tournament on 4 vertices::
4079
+
4080
+ sage: H = digraphs.TransitiveTournament(4)
4081
+ sage: fl = H.flow_polytope(); fl # needs sage.geometry.polyhedron
4082
+ A 3-dimensional polyhedron in QQ^6 defined as the convex hull
4083
+ of 4 vertices
4084
+ sage: fl.vertices() # needs sage.geometry.polyhedron
4085
+ (A vertex at (0, 0, 1, 0, 0, 0),
4086
+ A vertex at (0, 1, 0, 0, 0, 1),
4087
+ A vertex at (1, 0, 0, 0, 1, 0),
4088
+ A vertex at (1, 0, 0, 1, 0, 1))
4089
+
4090
+ Restricting to a subset of the edges::
4091
+
4092
+ sage: fl = H.flow_polytope(edges=[(0, 1, None), (1, 2, None), # needs sage.geometry.polyhedron
4093
+ ....: (2, 3, None), (0, 3, None)]); fl
4094
+ A 1-dimensional polyhedron in QQ^4 defined as the convex hull
4095
+ of 2 vertices
4096
+ sage: fl.vertices() # needs sage.geometry.polyhedron
4097
+ (A vertex at (0, 0, 0, 1), A vertex at (1, 1, 1, 0))
4098
+
4099
+ Using a different choice of sources and sinks::
4100
+
4101
+ sage: # needs sage.geometry.polyhedron
4102
+ sage: fl = H.flow_polytope(ends=([1], [3])); fl
4103
+ A 1-dimensional polyhedron in QQ^6 defined as the convex hull
4104
+ of 2 vertices
4105
+ sage: fl.vertices()
4106
+ (A vertex at (0, 0, 0, 1, 0, 1), A vertex at (0, 0, 0, 0, 1, 0))
4107
+ sage: fl = H.flow_polytope(ends=([0, 1], [3])); fl
4108
+ The empty polyhedron in QQ^6
4109
+ sage: fl = H.flow_polytope(ends=([3], [0])); fl
4110
+ The empty polyhedron in QQ^6
4111
+ sage: fl = H.flow_polytope(ends=([0, 1], [2, 3])); fl
4112
+ A 3-dimensional polyhedron in QQ^6 defined as the convex hull
4113
+ of 5 vertices
4114
+ sage: fl.vertices()
4115
+ (A vertex at (0, 0, 1, 1, 0, 0),
4116
+ A vertex at (0, 1, 0, 0, 1, 0),
4117
+ A vertex at (1, 0, 0, 2, 0, 1),
4118
+ A vertex at (1, 0, 0, 1, 1, 0),
4119
+ A vertex at (0, 1, 0, 1, 0, 1))
4120
+ sage: fl = H.flow_polytope(edges=[(0, 1, None), (1, 2, None),
4121
+ ....: (2, 3, None), (0, 2, None),
4122
+ ....: (1, 3, None)],
4123
+ ....: ends=([0, 1], [2, 3])); fl
4124
+ A 2-dimensional polyhedron in QQ^5 defined as the convex hull
4125
+ of 4 vertices
4126
+ sage: fl.vertices()
4127
+ (A vertex at (0, 0, 0, 1, 1),
4128
+ A vertex at (1, 2, 1, 0, 0),
4129
+ A vertex at (1, 1, 0, 0, 1),
4130
+ A vertex at (0, 1, 1, 1, 0))
4131
+
4132
+ A digraph with one source and two sinks::
4133
+
4134
+ sage: Y = DiGraph({1: [2], 2: [3, 4]})
4135
+ sage: Y.flow_polytope() # needs sage.geometry.polyhedron
4136
+ The empty polyhedron in QQ^3
4137
+
4138
+ A digraph with one vertex and no edge::
4139
+
4140
+ sage: Z = DiGraph({1: []})
4141
+ sage: Z.flow_polytope() # needs sage.geometry.polyhedron
4142
+ A 0-dimensional polyhedron in QQ^0 defined as the convex hull
4143
+ of 1 vertex
4144
+
4145
+ A digraph with multiple edges (:issue:`28837`)::
4146
+
4147
+ sage: G = DiGraph([(0, 1), (0,1)], multiedges=True); G
4148
+ Multi-digraph on 2 vertices
4149
+ sage: P = G.flow_polytope(); P # needs sage.geometry.polyhedron
4150
+ A 1-dimensional polyhedron in QQ^2 defined as the convex hull of 2 vertices
4151
+ sage: P.vertices() # needs sage.geometry.polyhedron
4152
+ (A vertex at (1, 0), A vertex at (0, 1))
4153
+ sage: P.lines() # needs sage.geometry.polyhedron
4154
+ ()
4155
+ """
4156
+ from sage.geometry.polyhedron.constructor import Polyhedron
4157
+ if edges is None:
4158
+ edges = self.edges(sort=False)
4159
+ m = len(edges)
4160
+ ineqs = [[0] * (i + 1) + [1] + [0] * (m - i - 1) for i in range(m)]
4161
+
4162
+ eqs = []
4163
+ for u in self:
4164
+ ins = set(self.incoming_edge_iterator(u))
4165
+ outs = set(self.outgoing_edge_iterator(u))
4166
+ eq = [Integer(j in ins) - Integer(j in outs) for j in edges]
4167
+
4168
+ const = 0
4169
+ if ends is None:
4170
+ if not ins: # sources (indegree 0)
4171
+ const += 1
4172
+ if not outs: # sinks (outdegree 0)
4173
+ const -= 1
4174
+ else:
4175
+ if u in ends[0]: # chosen sources
4176
+ const += 1
4177
+ if u in ends[1]: # chosen sinks
4178
+ const -= 1
4179
+
4180
+ eq = [const] + eq
4181
+ eqs.append(eq)
4182
+
4183
+ return Polyhedron(ieqs=ineqs, eqns=eqs, backend=backend)
4184
+
4185
+ def is_tournament(self):
4186
+ r"""
4187
+ Check whether the digraph is a tournament.
4188
+
4189
+ A tournament is a digraph in which each pair of distinct vertices is
4190
+ connected by a single arc.
4191
+
4192
+ EXAMPLES::
4193
+
4194
+ sage: g = digraphs.RandomTournament(6)
4195
+ sage: g.is_tournament()
4196
+ True
4197
+ sage: u,v = next(g.edge_iterator(labels=False))
4198
+ sage: g.add_edge(v, u)
4199
+ sage: g.is_tournament()
4200
+ False
4201
+ sage: g.add_edges([(u, v), (v, u)])
4202
+ sage: g.is_tournament()
4203
+ False
4204
+
4205
+ .. SEEALSO::
4206
+
4207
+ - :wikipedia:`Tournament_(graph_theory)`
4208
+ - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.RandomTournament`
4209
+ - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.TransitiveTournament`
4210
+ """
4211
+ self._scream_if_not_simple()
4212
+
4213
+ if self.size() != self.order() * (self.order() - 1) // 2:
4214
+ return False
4215
+
4216
+ import itertools
4217
+ return not any(self.has_edge(u, v) == self.has_edge(v, u)
4218
+ for u, v in itertools.combinations(self, 2))
4219
+
4220
+ def _girth_bfs(self, odd=False, certificate=False):
4221
+ r"""
4222
+ Return the girth of the digraph using breadth-first search.
4223
+
4224
+ Loops are ignored, so the returned value is at least 2.
4225
+
4226
+ INPUT:
4227
+
4228
+ - ``odd`` -- boolean (default: ``False``); whether to compute the odd
4229
+ girth
4230
+
4231
+ - ``certificate`` -- boolean (default: ``False``); whether to return
4232
+ ``(g, c)``, where ``g`` is the (odd) girth and ``c`` is a list
4233
+ of vertices of a directed cycle of length ``g`` in the graph,
4234
+ thus providing a certificate that the (odd) girth is at most ``g``,
4235
+ or ``None`` if ``g`` is infinite
4236
+
4237
+ EXAMPLES:
4238
+
4239
+ A digraph with girth 4 and odd girth 5::
4240
+
4241
+ sage: G = DiGraph([(0, 1), (1, 2), (1, 3), (2, 3), (3, 4), (4, 0)])
4242
+ sage: G._girth_bfs(certificate=True) # random
4243
+ (4, [1, 3, 4, 0])
4244
+ sage: G._girth_bfs(odd=True)
4245
+ 5
4246
+
4247
+ .. SEEALSO::
4248
+
4249
+ * :meth:`~sage.graphs.GenericGraph.girth` -- return the girth of the
4250
+ graph
4251
+ * :meth:`~sage.graphs.GenericGraph.odd_girth` -- return the odd
4252
+ girth of the graph
4253
+ """
4254
+ n = self.num_verts()
4255
+ best = n + 1
4256
+ seen = set()
4257
+ for w in self:
4258
+ seen.add(w)
4259
+ inSpan, outSpan = {w: None}, {w: None}
4260
+ depth = 1
4261
+ outList, inList = set([w]), set([w])
4262
+ while 2 * depth <= best:
4263
+ nextOutList, nextInList = set(), set()
4264
+ for v in outList:
4265
+ for u in self.neighbor_out_iterator(v):
4266
+ if u in seen:
4267
+ continue
4268
+ if u not in outSpan:
4269
+ outSpan[u] = v
4270
+ nextOutList.add(u)
4271
+ if u in inList:
4272
+ best = depth * 2 - 1
4273
+ ends = (v, u)
4274
+ bestSpans = (outSpan, inSpan)
4275
+ break
4276
+ if best == 2 * depth - 1:
4277
+ break
4278
+ if best == 2 * depth - 1:
4279
+ break
4280
+ for v in inList:
4281
+ for u in self.neighbor_in_iterator(v):
4282
+ if u in seen:
4283
+ continue
4284
+ if u not in inSpan:
4285
+ inSpan[u] = v
4286
+ nextInList.add(u)
4287
+ if not odd and u in nextOutList:
4288
+ best = depth * 2
4289
+ ends = (u, v)
4290
+ bestSpans = (outSpan, inSpan)
4291
+ break
4292
+ if best == 2 * depth:
4293
+ break
4294
+ if best <= 2:
4295
+ break
4296
+ outList = nextOutList
4297
+ inList = nextInList
4298
+ depth += 1
4299
+ if best == n + 1:
4300
+ from sage.rings.infinity import Infinity
4301
+ return (Infinity, None) if certificate else Infinity
4302
+ if certificate:
4303
+ cycles = {}
4304
+ for x, span in zip(ends, bestSpans):
4305
+ cycles[x] = []
4306
+ y = x
4307
+ while span[y] is not None:
4308
+ cycles[x].append(y)
4309
+ y = span[y]
4310
+ cycles[x].append(y)
4311
+ u, v = ends
4312
+ return (best, list(reversed(cycles[u])) + cycles[v])
4313
+ return best
4314
+
4315
+ def out_branchings(self, source, spanning=True):
4316
+ r"""
4317
+ Return an iterator over the out branchings rooted at given vertex in
4318
+ ``self``.
4319
+
4320
+ An out-branching is a directed tree rooted at ``source`` whose arcs are
4321
+ directed from source to leaves. An out-branching is spanning if it
4322
+ contains all vertices of the digraph.
4323
+
4324
+ If no spanning out branching rooted at ``source`` exist, raises
4325
+ :exc:`ValueError` or return non spanning out branching rooted at
4326
+ ``source``, depending on the value of ``spanning``.
4327
+
4328
+ INPUT:
4329
+
4330
+ - ``source`` -- vertex used as the source for all out branchings
4331
+
4332
+ - ``spanning`` -- boolean (default: ``True``); if ``False`` return
4333
+ maximum out branching from ``source``. Otherwise, return spanning out
4334
+ branching if exists.
4335
+
4336
+ OUTPUT: an iterator over the out branchings rooted in the given source
4337
+
4338
+ .. SEEALSO::
4339
+
4340
+ - :meth:`~sage.graphs.digraph.DiGraph.in_branchings`
4341
+ -- iterator over in-branchings rooted at given vertex.
4342
+ - :meth:`~sage.graphs.graph.Graph.spanning_trees`
4343
+ -- returns all spanning trees.
4344
+ - :meth:`~sage.graphs.generic_graph.GenericGraph.spanning_trees_count`
4345
+ -- counts the number of spanning trees.
4346
+
4347
+ ALGORITHM:
4348
+
4349
+ Recursively computes all out branchings.
4350
+
4351
+ At each step:
4352
+
4353
+ 0. clean the graph (see below)
4354
+ 1. pick an edge e out of source
4355
+ 2. find all out branchings that do not contain e by first
4356
+ removing it
4357
+ 3. find all out branchings that do contain e by first
4358
+ merging the end vertices of e
4359
+
4360
+ Cleaning the graph implies to remove loops and replace multiedges by a
4361
+ single one with an appropriate label since these lead to similar steps
4362
+ of computation.
4363
+
4364
+ EXAMPLES:
4365
+
4366
+ A bidirectional 4-cycle::
4367
+
4368
+ sage: G = DiGraph({1:[2,3], 2:[1,4], 3:[1,4], 4:[2,3]}, format='dict_of_lists')
4369
+ sage: list(G.out_branchings(1))
4370
+ [Digraph on 4 vertices,
4371
+ Digraph on 4 vertices,
4372
+ Digraph on 4 vertices,
4373
+ Digraph on 4 vertices]
4374
+
4375
+ With the Petersen graph turned into a symmetric directed graph::
4376
+
4377
+ sage: G = graphs.PetersenGraph().to_directed()
4378
+ sage: len(list(G.out_branchings(0)))
4379
+ 2000
4380
+
4381
+ With a non connected ``DiGraph`` and ``spanning = True``::
4382
+
4383
+ sage: G = graphs.PetersenGraph().to_directed() + graphs.PetersenGraph().to_directed()
4384
+ sage: G.out_branchings(0, spanning=True)
4385
+ Traceback (most recent call last):
4386
+ ...
4387
+ ValueError: no spanning out branching from vertex (0) exist
4388
+
4389
+ With a non connected ``DiGraph`` and ``spanning = False``::
4390
+
4391
+ sage: g=DiGraph([(0,1), (0,1), (1,2), (3,4)],multiedges=True)
4392
+ sage: list(g.out_branchings(0, spanning=False))
4393
+ [Digraph on 3 vertices, Digraph on 3 vertices]
4394
+
4395
+ With multiedges::
4396
+
4397
+ sage: G = DiGraph({0:[1,1,1], 1:[2,2]}, format='dict_of_lists', multiedges=True)
4398
+ sage: len(list(G.out_branchings(0)))
4399
+ 6
4400
+
4401
+ With a DiGraph already being a spanning out branching::
4402
+
4403
+ sage: G = DiGraph({0:[1,2], 1:[3,4], 2:[5], 3:[], 4:[], 5:[]}, format='dict_of_lists')
4404
+ sage: next(G.out_branchings(0)) == G
4405
+ True
4406
+
4407
+ TESTS:
4408
+
4409
+ The empty ``DiGraph``::
4410
+
4411
+ sage: G = DiGraph()
4412
+ sage: G.out_branchings(0)
4413
+ Traceback (most recent call last):
4414
+ ...
4415
+ ValueError: vertex (0) is not a vertex of the digraph
4416
+
4417
+ sage: edges = [(0,0,'x'), (0,0,'y')]
4418
+ sage: G = DiGraph(edges, multiedges=True, loops=True, weighted=True)
4419
+ sage: list(G.out_branchings(0))
4420
+ [Digraph on 1 vertex]
4421
+
4422
+ sage: edges = [(0,1,'x'), (0,1,'y'), (1,2,'z'), (2,0,'w')]
4423
+ sage: G = DiGraph(edges, multiedges=True, loops=True, weighted=True)
4424
+ sage: len(list(G.out_branchings(0)))
4425
+ 2
4426
+ """
4427
+ def _rec_out_branchings(depth):
4428
+ r"""
4429
+ The recursive function used to enumerate out branchings.
4430
+
4431
+ This function makes use of the following to keep track of partial
4432
+ out branchings:
4433
+
4434
+ - ``list_edges`` -- list of edges in self
4435
+ - ``list_merged_edges`` -- list of edges that are currently merged
4436
+ - ``graph`` -- a copy of self where edges have an appropriate label
4437
+ """
4438
+ if not depth:
4439
+ # We have enough merged edges to form a out_branching
4440
+ # We iterate over the lists of labels in list_merged_edges and
4441
+ # yield the corresponding out_branchings
4442
+ for indexes in product(*list_merged_edges):
4443
+ yield DiGraph([list_edges[index] for index in indexes],
4444
+ format='list_of_edges', pos=self.get_pos())
4445
+
4446
+ # 1) Clean the graph
4447
+ # delete loops on source if any
4448
+ D.delete_edges(D.incoming_edge_iterator(source))
4449
+
4450
+ # merge multi-edges if any by concatenating their labels
4451
+ if D.has_multiple_edges():
4452
+ merged_multiple_edges = {}
4453
+ for u, v, label in D.multiple_edges():
4454
+ D.delete_edge(u, v, label)
4455
+ if (u, v) not in merged_multiple_edges:
4456
+ merged_multiple_edges[(u, v)] = label
4457
+ else:
4458
+ merged_multiple_edges[(u, v)] += label
4459
+ D.add_edges([(u, v, label) for (u, v), label in merged_multiple_edges.items()])
4460
+
4461
+ # 2) Pick an edge e outgoing from the source
4462
+ try:
4463
+ s, x, label = next(D.outgoing_edge_iterator(source))
4464
+ except StopIteration:
4465
+ return
4466
+ # 3) Find all out_branchings that do not contain e
4467
+ # by first removing it
4468
+ D.delete_edge(s, x, label)
4469
+ if len(list(D.depth_first_search(source))) == depth + 1:
4470
+ for out_branch in _rec_out_branchings(depth):
4471
+ yield out_branch
4472
+ D.add_edge(s, x, label)
4473
+
4474
+ # 4) Find all out_branchings that do contain e by merging
4475
+ # the end vertices of e
4476
+ # store different edges to unmerged the end vertices of e
4477
+ saved_edges = D.outgoing_edges(source)
4478
+ saved_edges.remove((s, x, label))
4479
+ saved_edges += D.outgoing_edges(x)
4480
+ saved_edges += D.incoming_edges(x)
4481
+
4482
+ D.merge_vertices((source, x))
4483
+
4484
+ list_merged_edges.add(label)
4485
+
4486
+ for out_branch in _rec_out_branchings(depth - 1):
4487
+ yield out_branch
4488
+
4489
+ list_merged_edges.remove(label)
4490
+
4491
+ # unmerge the end vertices of e
4492
+ D.delete_vertex(source)
4493
+ D.add_edges(saved_edges)
4494
+
4495
+ def _singleton_out_branching():
4496
+ r"""
4497
+ Return a DiGraph containing only ``source`` and no edges.
4498
+ """
4499
+ D = DiGraph()
4500
+ D.add_vertex(source)
4501
+ yield D
4502
+
4503
+ if not self.has_vertex(source):
4504
+ raise ValueError("vertex ({0}) is not a vertex of the digraph".format(source))
4505
+
4506
+ # check if self.order == 1
4507
+ if self.order() == 1:
4508
+ return _singleton_out_branching()
4509
+
4510
+ # check if the source can access to every other vertex
4511
+ if spanning:
4512
+ depth = self.order() - 1
4513
+ if len(list(self.depth_first_search(source))) < self.order():
4514
+ raise ValueError("no spanning out branching from vertex ({0}) exist".format(source))
4515
+ else:
4516
+ depth = len(list(self.depth_first_search(source))) - 1
4517
+ # if vertex is isolated
4518
+ if not depth:
4519
+ return _singleton_out_branching()
4520
+
4521
+ # We build a copy of self in which each edge has a distinct label.
4522
+ # On the way, we remove loops and edges incoming to source.
4523
+ D = DiGraph(multiedges=True, loops=True)
4524
+ list_edges = list(self.edges(sort=False))
4525
+ for i, (u, v, _) in enumerate(list_edges):
4526
+ if u != v and v != source:
4527
+ D.add_edge(u, v, (i,))
4528
+ list_merged_edges = set()
4529
+ return _rec_out_branchings(depth)
4530
+
4531
+ def in_branchings(self, source, spanning=True):
4532
+ r"""
4533
+ Return an iterator over the in branchings rooted at given vertex in
4534
+ ``self``.
4535
+
4536
+ An in-branching is a directed tree rooted at ``source`` whose arcs are
4537
+ directed to source from leaves. An in-branching is spanning if it
4538
+ contains all vertices of the digraph.
4539
+
4540
+ If no spanning in branching rooted at ``source`` exist, raises
4541
+ :exc:`ValueError` or return non spanning in branching rooted at
4542
+ ``source``, depending on the value of ``spanning``.
4543
+
4544
+ INPUT:
4545
+
4546
+ - ``source`` -- vertex used as the source for all in branchings
4547
+
4548
+ - ``spanning`` -- boolean (default: ``True``); if ``False`` return
4549
+ maximum in branching to ``source``. Otherwise, return spanning in
4550
+ branching if exists.
4551
+
4552
+ OUTPUT: an iterator over the in branchings rooted in the given source
4553
+
4554
+ .. SEEALSO::
4555
+
4556
+ - :meth:`~sage.graphs.digraph.DiGraph.out_branchings`
4557
+ -- iterator over out-branchings rooted at given vertex.
4558
+ - :meth:`~sage.graphs.graph.Graph.spanning_trees`
4559
+ -- returns all spanning trees.
4560
+ - :meth:`~sage.graphs.generic_graph.GenericGraph.spanning_trees_count`
4561
+ -- counts the number of spanning trees.
4562
+
4563
+ ALGORITHM:
4564
+
4565
+ Recursively computes all in branchings.
4566
+
4567
+ At each step:
4568
+
4569
+ 0. clean the graph (see below)
4570
+ 1. pick an edge e incoming to source
4571
+ 2. find all in branchings that do not contain e by first
4572
+ removing it
4573
+ 3. find all in branchings that do contain e by first
4574
+ merging the end vertices of e
4575
+
4576
+ Cleaning the graph implies to remove loops and replace multiedges by a
4577
+ single one with an appropriate label since these lead to similar steps
4578
+ of computation.
4579
+
4580
+ EXAMPLES:
4581
+
4582
+ A bidirectional 4-cycle::
4583
+
4584
+ sage: G = DiGraph({1:[2,3], 2:[1,4], 3:[1,4], 4:[2,3]}, format='dict_of_lists')
4585
+ sage: list(G.in_branchings(1))
4586
+ [Digraph on 4 vertices,
4587
+ Digraph on 4 vertices,
4588
+ Digraph on 4 vertices,
4589
+ Digraph on 4 vertices]
4590
+
4591
+ With the Petersen graph turned into a symmetric directed graph::
4592
+
4593
+ sage: G = graphs.PetersenGraph().to_directed()
4594
+ sage: len(list(G.in_branchings(0)))
4595
+ 2000
4596
+
4597
+ With a non connected ``DiGraph`` and ``spanning = True``::
4598
+
4599
+ sage: G = graphs.PetersenGraph().to_directed() + graphs.PetersenGraph().to_directed()
4600
+ sage: G.in_branchings(0)
4601
+ Traceback (most recent call last):
4602
+ ...
4603
+ ValueError: no spanning in branching to vertex (0) exist
4604
+
4605
+ With a non connected ``DiGraph`` and ``spanning = False``::
4606
+
4607
+ sage: g=DiGraph([(1,0), (1,0), (2,1), (3,4)],multiedges=True)
4608
+ sage: list(g.in_branchings(0,spanning=False))
4609
+ [Digraph on 3 vertices, Digraph on 3 vertices]
4610
+
4611
+ With multiedges::
4612
+
4613
+ sage: G = DiGraph({0:[1,1,1], 1:[2,2]}, format='dict_of_lists', multiedges=True)
4614
+ sage: len(list(G.in_branchings(2)))
4615
+ 6
4616
+
4617
+ With a DiGraph already being a spanning in branching::
4618
+
4619
+ sage: G = DiGraph({0:[], 1:[0], 2:[0], 3:[1], 4:[1], 5:[2]}, format='dict_of_lists')
4620
+ sage: next(G.in_branchings(0)) == G
4621
+ True
4622
+
4623
+ TESTS:
4624
+
4625
+ The empty ``DiGraph``::
4626
+
4627
+ sage: G = DiGraph()
4628
+ sage: G.in_branchings(0)
4629
+ Traceback (most recent call last):
4630
+ ...
4631
+ ValueError: vertex (0) is not a vertex of the digraph
4632
+
4633
+ sage: edges = [(0,0,'x'), (0,0,'y')]
4634
+ sage: G = DiGraph(edges, multiedges=True, loops=True, weighted=True)
4635
+ sage: list(G.in_branchings(0))
4636
+ [Digraph on 1 vertex]
4637
+
4638
+ sage: edges = [(0,1,'x'), (0,1,'y'), (1,2,'z'), (2,0,'w')]
4639
+ sage: G = DiGraph(edges, multiedges=True, loops=True, weighted=True)
4640
+ sage: len(list(G.in_branchings(0)))
4641
+ 1
4642
+ """
4643
+ def _rec_in_branchings(depth):
4644
+ r"""
4645
+ The recursive function used to enumerate in branchings.
4646
+
4647
+ This function makes use of the following to keep track of partial in
4648
+ branchings:
4649
+
4650
+ - ``list_edges`` -- list of edges in self
4651
+ - ``list_merged_edges`` -- list of edges that are currently merged
4652
+ - ``graph`` -- a copy of self where edges have an appropriate label
4653
+ """
4654
+ if not depth:
4655
+ # We have enough merged edges to form a in_branching
4656
+ # We iterate over the lists of labels in list_merged_edges and
4657
+ # yield the corresponding in_branchings
4658
+ for indexes in product(*list_merged_edges):
4659
+ yield DiGraph([list_edges[index] for index in indexes],
4660
+ format='list_of_edges', pos=self.get_pos())
4661
+
4662
+ # 1) Clean the graph
4663
+ # delete loops on source if any
4664
+ D.delete_edges(D.outgoing_edge_iterator(source))
4665
+
4666
+ # merge multi-edges if any by concatenating their labels
4667
+ if D.has_multiple_edges():
4668
+ merged_multiple_edges = {}
4669
+ for u, v, label in D.multiple_edges():
4670
+ D.delete_edge(u, v, label)
4671
+ if (u, v) not in merged_multiple_edges:
4672
+ merged_multiple_edges[(u, v)] = label
4673
+ else:
4674
+ merged_multiple_edges[(u, v)] += label
4675
+ D.add_edges([(u, v, label) for (u, v), label in merged_multiple_edges.items()])
4676
+
4677
+ # 2) Pick an edge e incoming to the source
4678
+ try:
4679
+ x, s, label = next(D.incoming_edge_iterator(source))
4680
+ except StopIteration:
4681
+ return
4682
+ # 3) Find all in_branchings that do not contain e
4683
+ # by first removing it
4684
+ D.delete_edge(x, s, label)
4685
+ if len(list(D.depth_first_search(source, neighbors=D.neighbor_in_iterator))) == depth + 1:
4686
+ for in_branch in _rec_in_branchings(depth):
4687
+ yield in_branch
4688
+ D.add_edge(x, s, label)
4689
+
4690
+ # 4) Find all in_branchings that do contain e by merging
4691
+ # the end vertices of e
4692
+ # store different edges to unmerged the end vertices of e
4693
+ saved_edges = D.incoming_edges(source)
4694
+ saved_edges.remove((x, s, label))
4695
+ saved_edges += D.outgoing_edges(x)
4696
+ saved_edges += D.incoming_edges(x)
4697
+
4698
+ D.merge_vertices((source, x))
4699
+
4700
+ list_merged_edges.add(label)
4701
+
4702
+ for in_branch in _rec_in_branchings(depth - 1):
4703
+ yield in_branch
4704
+
4705
+ list_merged_edges.remove(label)
4706
+
4707
+ # unmerge the end vertices of e
4708
+ D.delete_vertex(source)
4709
+ D.add_edges(saved_edges)
4710
+
4711
+ def _singleton_in_branching():
4712
+ r"""
4713
+ Return a DiGraph containing only ``source`` and no edges.
4714
+ """
4715
+ D = DiGraph()
4716
+ D.add_vertex(source)
4717
+ yield D
4718
+
4719
+ if not self.has_vertex(source):
4720
+ raise ValueError("vertex ({0}) is not a vertex of the digraph".format(source))
4721
+
4722
+ # check if self.order == 1
4723
+ if self.order() == 1:
4724
+ return _singleton_in_branching()
4725
+
4726
+ # check if the source can access to every other vertex
4727
+ if spanning:
4728
+ depth = self.order() - 1
4729
+ if len(list(self.depth_first_search(source, neighbors=self.neighbor_in_iterator))) < self.order():
4730
+ raise ValueError("no spanning in branching to vertex ({0}) exist".format(source))
4731
+ else:
4732
+ depth = len(list(self.depth_first_search(source, neighbors=self.neighbor_in_iterator))) - 1
4733
+ # if vertex is isolated
4734
+ if not depth:
4735
+ return _singleton_in_branching()
4736
+
4737
+ # We build a copy of self in which each edge has a distinct label.
4738
+ # On the way, we remove loops and edges incoming to source.
4739
+ D = DiGraph(multiedges=True, loops=True)
4740
+ list_edges = list(self.edges(sort=False))
4741
+ for i, (u, v, _) in enumerate(list_edges):
4742
+ if u != v and u != source:
4743
+ D.add_edge(u, v, (i,))
4744
+ list_merged_edges = set()
4745
+ return _rec_in_branchings(depth)
4746
+
4747
+ # Aliases to functions defined in other modules
4748
+ from sage.graphs.comparability import is_transitive
4749
+ from sage.graphs.base.static_sparse_graph import tarjan_strongly_connected_components as strongly_connected_components
4750
+ from sage.graphs.connectivity import is_strongly_connected
4751
+ from sage.graphs.connectivity import strongly_connected_components_digraph
4752
+ from sage.graphs.connectivity import strongly_connected_components_subgraphs
4753
+ from sage.graphs.connectivity import strongly_connected_component_containing_vertex
4754
+ from sage.graphs.connectivity import strong_articulation_points