passagemath-graphs 10.5.43__cp39-cp39-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 (258) hide show
  1. passagemath_graphs-10.5.43.dist-info/METADATA +293 -0
  2. passagemath_graphs-10.5.43.dist-info/RECORD +258 -0
  3. passagemath_graphs-10.5.43.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.5.43.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 +2552 -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 +125 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1556 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2262 -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 +534 -0
  25. sage/combinat/designs/database.py +5614 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-39-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-39-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-39-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 +548 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2243 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-39-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +966 -0
  44. sage/combinat/designs/resolvable_bibd.py +781 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-39-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/cartesian_product.py +493 -0
  57. sage/combinat/posets/d_complete.py +182 -0
  58. sage/combinat/posets/elements.py +273 -0
  59. sage/combinat/posets/forest.py +30 -0
  60. sage/combinat/posets/hasse_cython.cpython-39-aarch64-linux-gnu.so +0 -0
  61. sage/combinat/posets/hasse_cython.pyx +174 -0
  62. sage/combinat/posets/hasse_diagram.py +3678 -0
  63. sage/combinat/posets/incidence_algebras.py +796 -0
  64. sage/combinat/posets/lattices.py +5119 -0
  65. sage/combinat/posets/linear_extension_iterator.cpython-39-aarch64-linux-gnu.so +0 -0
  66. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  67. sage/combinat/posets/linear_extensions.py +1039 -0
  68. sage/combinat/posets/mobile.py +275 -0
  69. sage/combinat/posets/moebius_algebra.py +776 -0
  70. sage/combinat/posets/poset_examples.py +2131 -0
  71. sage/combinat/posets/posets.py +9169 -0
  72. sage/combinat/rooted_tree.py +1070 -0
  73. sage/combinat/shard_order.py +239 -0
  74. sage/combinat/tamari_lattices.py +384 -0
  75. sage/combinat/yang_baxter_graph.py +923 -0
  76. sage/databases/all__sagemath_graphs.py +1 -0
  77. sage/databases/knotinfo_db.py +1230 -0
  78. sage/ext_data/all__sagemath_graphs.py +1 -0
  79. sage/ext_data/graphs/graph_plot_js.html +330 -0
  80. sage/ext_data/kenzo/CP2.txt +45 -0
  81. sage/ext_data/kenzo/CP3.txt +349 -0
  82. sage/ext_data/kenzo/CP4.txt +4774 -0
  83. sage/ext_data/kenzo/README.txt +49 -0
  84. sage/ext_data/kenzo/S4.txt +20 -0
  85. sage/graphs/all.py +42 -0
  86. sage/graphs/asteroidal_triples.cpython-39-aarch64-linux-gnu.so +0 -0
  87. sage/graphs/asteroidal_triples.pyx +299 -0
  88. sage/graphs/base/all.py +1 -0
  89. sage/graphs/base/boost_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  90. sage/graphs/base/boost_graph.pxd +106 -0
  91. sage/graphs/base/boost_graph.pyx +3045 -0
  92. sage/graphs/base/c_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  93. sage/graphs/base/c_graph.pxd +106 -0
  94. sage/graphs/base/c_graph.pyx +5096 -0
  95. sage/graphs/base/dense_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  96. sage/graphs/base/dense_graph.pxd +26 -0
  97. sage/graphs/base/dense_graph.pyx +757 -0
  98. sage/graphs/base/graph_backends.cpython-39-aarch64-linux-gnu.so +0 -0
  99. sage/graphs/base/graph_backends.pxd +5 -0
  100. sage/graphs/base/graph_backends.pyx +797 -0
  101. sage/graphs/base/overview.py +85 -0
  102. sage/graphs/base/sparse_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  103. sage/graphs/base/sparse_graph.pxd +90 -0
  104. sage/graphs/base/sparse_graph.pyx +1653 -0
  105. sage/graphs/base/static_dense_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  106. sage/graphs/base/static_dense_graph.pxd +5 -0
  107. sage/graphs/base/static_dense_graph.pyx +1032 -0
  108. sage/graphs/base/static_sparse_backend.cpython-39-aarch64-linux-gnu.so +0 -0
  109. sage/graphs/base/static_sparse_backend.pxd +27 -0
  110. sage/graphs/base/static_sparse_backend.pyx +1580 -0
  111. sage/graphs/base/static_sparse_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  112. sage/graphs/base/static_sparse_graph.pxd +37 -0
  113. sage/graphs/base/static_sparse_graph.pyx +1304 -0
  114. sage/graphs/bipartite_graph.py +2709 -0
  115. sage/graphs/centrality.cpython-39-aarch64-linux-gnu.so +0 -0
  116. sage/graphs/centrality.pyx +965 -0
  117. sage/graphs/cographs.py +519 -0
  118. sage/graphs/comparability.cpython-39-aarch64-linux-gnu.so +0 -0
  119. sage/graphs/comparability.pyx +813 -0
  120. sage/graphs/connectivity.cpython-39-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/connectivity.pxd +157 -0
  122. sage/graphs/connectivity.pyx +4813 -0
  123. sage/graphs/convexity_properties.cpython-39-aarch64-linux-gnu.so +0 -0
  124. sage/graphs/convexity_properties.pxd +16 -0
  125. sage/graphs/convexity_properties.pyx +827 -0
  126. sage/graphs/digraph.py +4410 -0
  127. sage/graphs/digraph_generators.py +1921 -0
  128. sage/graphs/distances_all_pairs.cpython-39-aarch64-linux-gnu.so +0 -0
  129. sage/graphs/distances_all_pairs.pxd +12 -0
  130. sage/graphs/distances_all_pairs.pyx +2938 -0
  131. sage/graphs/domination.py +1363 -0
  132. sage/graphs/dot2tex_utils.py +100 -0
  133. sage/graphs/edge_connectivity.cpython-39-aarch64-linux-gnu.so +0 -0
  134. sage/graphs/edge_connectivity.pyx +1215 -0
  135. sage/graphs/generators/all.py +1 -0
  136. sage/graphs/generators/basic.py +1769 -0
  137. sage/graphs/generators/chessboard.py +538 -0
  138. sage/graphs/generators/classical_geometries.py +1611 -0
  139. sage/graphs/generators/degree_sequence.py +235 -0
  140. sage/graphs/generators/distance_regular.cpython-39-aarch64-linux-gnu.so +0 -0
  141. sage/graphs/generators/distance_regular.pyx +2846 -0
  142. sage/graphs/generators/families.py +4749 -0
  143. sage/graphs/generators/intersection.py +565 -0
  144. sage/graphs/generators/platonic_solids.py +262 -0
  145. sage/graphs/generators/random.py +2623 -0
  146. sage/graphs/generators/smallgraphs.py +5741 -0
  147. sage/graphs/generators/world_map.py +724 -0
  148. sage/graphs/generic_graph.py +26395 -0
  149. sage/graphs/generic_graph_pyx.cpython-39-aarch64-linux-gnu.so +0 -0
  150. sage/graphs/generic_graph_pyx.pxd +34 -0
  151. sage/graphs/generic_graph_pyx.pyx +1626 -0
  152. sage/graphs/genus.cpython-39-aarch64-linux-gnu.so +0 -0
  153. sage/graphs/genus.pyx +623 -0
  154. sage/graphs/graph.py +9362 -0
  155. sage/graphs/graph_coloring.cpython-39-aarch64-linux-gnu.so +0 -0
  156. sage/graphs/graph_coloring.pyx +2284 -0
  157. sage/graphs/graph_database.py +1122 -0
  158. sage/graphs/graph_decompositions/all.py +1 -0
  159. sage/graphs/graph_decompositions/bandwidth.cpython-39-aarch64-linux-gnu.so +0 -0
  160. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  161. sage/graphs/graph_decompositions/clique_separators.cpython-39-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/clique_separators.pyx +595 -0
  163. sage/graphs/graph_decompositions/cutwidth.cpython-39-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  165. sage/graphs/graph_decompositions/fast_digraph.cpython-39-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  167. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  168. sage/graphs/graph_decompositions/graph_products.cpython-39-aarch64-linux-gnu.so +0 -0
  169. sage/graphs/graph_decompositions/graph_products.pyx +462 -0
  170. sage/graphs/graph_decompositions/modular_decomposition.cpython-39-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  173. sage/graphs/graph_decompositions/slice_decomposition.cpython-39-aarch64-linux-gnu.so +0 -0
  174. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.pyx +1080 -0
  176. sage/graphs/graph_decompositions/tree_decomposition.cpython-39-aarch64-linux-gnu.so +0 -0
  177. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  179. sage/graphs/graph_decompositions/vertex_separation.cpython-39-aarch64-linux-gnu.so +0 -0
  180. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  181. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  182. sage/graphs/graph_editor.py +82 -0
  183. sage/graphs/graph_generators.py +3301 -0
  184. sage/graphs/graph_generators_pyx.cpython-39-aarch64-linux-gnu.so +0 -0
  185. sage/graphs/graph_generators_pyx.pyx +95 -0
  186. sage/graphs/graph_input.py +812 -0
  187. sage/graphs/graph_latex.py +2064 -0
  188. sage/graphs/graph_list.py +367 -0
  189. sage/graphs/graph_plot.py +1749 -0
  190. sage/graphs/graph_plot_js.py +338 -0
  191. sage/graphs/hyperbolicity.cpython-39-aarch64-linux-gnu.so +0 -0
  192. sage/graphs/hyperbolicity.pyx +1702 -0
  193. sage/graphs/hypergraph_generators.py +364 -0
  194. sage/graphs/independent_sets.cpython-39-aarch64-linux-gnu.so +0 -0
  195. sage/graphs/independent_sets.pxd +13 -0
  196. sage/graphs/independent_sets.pyx +402 -0
  197. sage/graphs/isgci.py +1033 -0
  198. sage/graphs/isoperimetric_inequalities.cpython-39-aarch64-linux-gnu.so +0 -0
  199. sage/graphs/isoperimetric_inequalities.pyx +453 -0
  200. sage/graphs/line_graph.cpython-39-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/line_graph.pyx +627 -0
  202. sage/graphs/lovasz_theta.py +77 -0
  203. sage/graphs/matching.py +1633 -0
  204. sage/graphs/matching_covered_graph.py +3566 -0
  205. sage/graphs/orientations.py +1504 -0
  206. sage/graphs/partial_cube.py +459 -0
  207. sage/graphs/path_enumeration.cpython-39-aarch64-linux-gnu.so +0 -0
  208. sage/graphs/path_enumeration.pyx +2040 -0
  209. sage/graphs/pq_trees.py +1129 -0
  210. sage/graphs/print_graphs.py +201 -0
  211. sage/graphs/schnyder.py +865 -0
  212. sage/graphs/spanning_tree.cpython-39-aarch64-linux-gnu.so +0 -0
  213. sage/graphs/spanning_tree.pyx +1457 -0
  214. sage/graphs/strongly_regular_db.cpython-39-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/strongly_regular_db.pyx +3340 -0
  216. sage/graphs/traversals.cpython-39-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/traversals.pxd +9 -0
  218. sage/graphs/traversals.pyx +1871 -0
  219. sage/graphs/trees.cpython-39-aarch64-linux-gnu.so +0 -0
  220. sage/graphs/trees.pxd +15 -0
  221. sage/graphs/trees.pyx +310 -0
  222. sage/graphs/tutte_polynomial.py +713 -0
  223. sage/graphs/views.cpython-39-aarch64-linux-gnu.so +0 -0
  224. sage/graphs/views.pyx +794 -0
  225. sage/graphs/weakly_chordal.cpython-39-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/weakly_chordal.pyx +562 -0
  227. sage/groups/all__sagemath_graphs.py +1 -0
  228. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  229. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-39-aarch64-linux-gnu.so +0 -0
  231. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  233. sage/knots/all.py +6 -0
  234. sage/knots/free_knotinfo_monoid.py +507 -0
  235. sage/knots/gauss_code.py +291 -0
  236. sage/knots/knot.py +682 -0
  237. sage/knots/knot_table.py +284 -0
  238. sage/knots/knotinfo.py +2880 -0
  239. sage/knots/link.py +4682 -0
  240. sage/sandpiles/all.py +13 -0
  241. sage/sandpiles/examples.py +225 -0
  242. sage/sandpiles/sandpile.py +6365 -0
  243. sage/topology/all.py +22 -0
  244. sage/topology/cell_complex.py +1214 -0
  245. sage/topology/cubical_complex.py +1977 -0
  246. sage/topology/delta_complex.py +1806 -0
  247. sage/topology/filtered_simplicial_complex.py +744 -0
  248. sage/topology/moment_angle_complex.py +823 -0
  249. sage/topology/simplicial_complex.py +5161 -0
  250. sage/topology/simplicial_complex_catalog.py +86 -0
  251. sage/topology/simplicial_complex_examples.py +1692 -0
  252. sage/topology/simplicial_complex_homset.py +205 -0
  253. sage/topology/simplicial_complex_morphism.py +836 -0
  254. sage/topology/simplicial_set.py +4102 -0
  255. sage/topology/simplicial_set_catalog.py +55 -0
  256. sage/topology/simplicial_set_constructions.py +2954 -0
  257. sage/topology/simplicial_set_examples.py +865 -0
  258. sage/topology/simplicial_set_morphism.py +1464 -0
sage/graphs/digraph.py ADDED
@@ -0,0 +1,4410 @@
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
+ if hasattr(self, '_embedding'):
1092
+ G._embedding = copy(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
+ attributes_to_copy = ('_assoc', '_embedding')
1855
+ for attr in attributes_to_copy:
1856
+ if hasattr(self, attr):
1857
+ copy_attr = {}
1858
+ old_attr = getattr(self, attr)
1859
+ if isinstance(old_attr, dict):
1860
+ for v, value in old_attr.items():
1861
+ try:
1862
+ copy_attr[v] = value.copy()
1863
+ except AttributeError:
1864
+ copy_attr[v] = copy(value)
1865
+ setattr(H, attr, copy_attr)
1866
+ else:
1867
+ setattr(H, attr, copy(old_attr))
1868
+
1869
+ if immutable or (immutable is None and self.is_immutable()):
1870
+ return H.copy(immutable=True)
1871
+
1872
+ return H
1873
+
1874
+ def reverse_edge(self, u, v=None, label=None, inplace=True, multiedges=None):
1875
+ """
1876
+ Reverse the edge from `u` to `v`.
1877
+
1878
+ INPUT:
1879
+
1880
+ - ``inplace`` -- boolean (default: ``True``); if ``False``, a new
1881
+ digraph is created and returned as output, otherwise ``self`` is
1882
+ modified.
1883
+
1884
+ - ``multiedges`` -- boolean (default: ``None``); how to decide what
1885
+ should be done in case of doubt (for instance when edge `(1,2)` is to
1886
+ be reversed in a graph while `(2,1)` already exists):
1887
+
1888
+ - If set to ``True``, input graph will be forced to allow parallel
1889
+ edges if necessary and edge `(1,2)` will appear twice in the graph.
1890
+
1891
+ - If set to ``False``, only one edge `(1,2)` will remain in the graph
1892
+ after `(2,1)` is reversed. Besides, the label of edge `(1,2)` will
1893
+ be overwritten with the label of edge `(2,1)`.
1894
+
1895
+ The default behaviour (``multiedges = None``) will raise an exception
1896
+ each time a subjective decision (setting ``multiedges`` to ``True`` or
1897
+ ``False``) is necessary to perform the operation.
1898
+
1899
+ The following forms are all accepted:
1900
+
1901
+ - D.reverse_edge( 1, 2 )
1902
+ - D.reverse_edge( (1, 2) )
1903
+ - D.reverse_edge( [1, 2] )
1904
+ - D.reverse_edge( 1, 2, 'label' )
1905
+ - D.reverse_edge( ( 1, 2, 'label') )
1906
+ - D.reverse_edge( [1, 2, 'label'] )
1907
+ - D.reverse_edge( ( 1, 2), label='label' )
1908
+
1909
+ EXAMPLES:
1910
+
1911
+ If ``inplace`` is ``True`` (default), ``self`` is modified::
1912
+
1913
+ sage: D = DiGraph([(0, 1 ,2)])
1914
+ sage: D.reverse_edge(0, 1)
1915
+ sage: D.edges(sort=True)
1916
+ [(1, 0, 2)]
1917
+
1918
+ If ``inplace`` is ``False``, ``self`` is not modified and a new digraph
1919
+ is returned::
1920
+
1921
+ sage: D = DiGraph([(0, 1, 2)])
1922
+ sage: re = D.reverse_edge(0, 1, inplace=False)
1923
+ sage: re.edges(sort=True)
1924
+ [(1, 0, 2)]
1925
+ sage: D.edges(sort=True)
1926
+ [(0, 1, 2)]
1927
+
1928
+ If ``multiedges`` is ``True``, ``self`` will be forced to allow parallel
1929
+ edges when and only when it is necessary::
1930
+
1931
+ sage: D = DiGraph([(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)])
1932
+ sage: D.reverse_edge(1, 2, multiedges=True)
1933
+ sage: D.edges(sort=True)
1934
+ [(2, 1, 'A'), (2, 1, 'A'), (2, 3, None)]
1935
+ sage: D.allows_multiple_edges()
1936
+ True
1937
+
1938
+ Even if ``multiedges`` is ``True``, ``self`` will not be forced to allow
1939
+ parallel edges when it is not necessary::
1940
+
1941
+ sage: D = DiGraph( [(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)] )
1942
+ sage: D.reverse_edge(2, 3, multiedges=True)
1943
+ sage: D.edges(sort=True)
1944
+ [(1, 2, 'A'), (2, 1, 'A'), (3, 2, None)]
1945
+ sage: D.allows_multiple_edges()
1946
+ False
1947
+
1948
+ If user specifies ``multiedges = False``, ``self`` will not be forced to
1949
+ allow parallel edges and a parallel edge will get deleted::
1950
+
1951
+ sage: D = DiGraph( [(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)] )
1952
+ sage: D.edges(sort=True)
1953
+ [(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)]
1954
+ sage: D.reverse_edge(1, 2, multiedges=False)
1955
+ sage: D.edges(sort=True)
1956
+ [(2, 1, 'A'), (2, 3, None)]
1957
+
1958
+ Note that in the following graph, specifying ``multiedges = False`` will
1959
+ result in overwriting the label of `(1, 2)` with the label of `(2, 1)`::
1960
+
1961
+ sage: D = DiGraph( [(1, 2, 'B'), (2, 1, 'A'), (2, 3, None)] )
1962
+ sage: D.edges(sort=True)
1963
+ [(1, 2, 'B'), (2, 1, 'A'), (2, 3, None)]
1964
+ sage: D.reverse_edge(2, 1, multiedges=False)
1965
+ sage: D.edges(sort=True)
1966
+ [(1, 2, 'A'), (2, 3, None)]
1967
+
1968
+ If input edge in digraph has weight/label, then the weight/label should
1969
+ be preserved in the output digraph. User does not need to specify the
1970
+ weight/label when calling function::
1971
+
1972
+ sage: D = DiGraph([[0, 1, 2], [1, 2, 1]], weighted=True)
1973
+ sage: D.reverse_edge(0, 1)
1974
+ sage: D.edges(sort=True)
1975
+ [(1, 0, 2), (1, 2, 1)]
1976
+ sage: re = D.reverse_edge([1, 2], inplace=False)
1977
+ sage: re.edges(sort=True)
1978
+ [(1, 0, 2), (2, 1, 1)]
1979
+
1980
+ If ``self`` has multiple copies (parallel edges) of the input edge, only
1981
+ 1 of the parallel edges is reversed::
1982
+
1983
+ sage: D = DiGraph([(0, 1, '01'), (0, 1, '01'), (0, 1, 'cat'), (1, 2, '12')], weighted=True, multiedges=True)
1984
+ sage: re = D.reverse_edge([0, 1, '01'], inplace=False)
1985
+ sage: re.edges(sort=True)
1986
+ [(0, 1, '01'), (0, 1, 'cat'), (1, 0, '01'), (1, 2, '12')]
1987
+
1988
+ If ``self`` has multiple copies (parallel edges) of the input edge but
1989
+ with distinct labels and no input label is specified, only 1 of the
1990
+ parallel edges is reversed (the edge that is labeled by the first label
1991
+ on the list returned by :meth:`.edge_label`)::
1992
+
1993
+ sage: D = DiGraph([(0, 1, 'A'), (0, 1, 'B'), (0, 1, 'mouse'), (0, 1, 'cat')], multiedges=true)
1994
+ sage: D.edge_label(0, 1)
1995
+ ['cat', 'mouse', 'B', 'A']
1996
+ sage: D.reverse_edge(0, 1)
1997
+ sage: D.edges(sort=True)
1998
+ [(0, 1, 'A'), (0, 1, 'B'), (0, 1, 'mouse'), (1, 0, 'cat')]
1999
+
2000
+ Finally, an exception is raised when Sage does not know how to choose
2001
+ between allowing multiple edges and losing some data::
2002
+
2003
+ sage: D = DiGraph([(0, 1, 'A'), (1, 0, 'B')])
2004
+ sage: D.reverse_edge(0, 1)
2005
+ Traceback (most recent call last):
2006
+ ...
2007
+ ValueError: reversing the given edge is about to create two parallel
2008
+ edges but input digraph doesn't allow them - User needs to specify
2009
+ multiedges is True or False.
2010
+
2011
+ The following syntax is supported, but note that you must use the
2012
+ ``label`` keyword::
2013
+
2014
+ sage: D = DiGraph()
2015
+ sage: D.add_edge((1, 2), label='label')
2016
+ sage: D.edges(sort=True)
2017
+ [(1, 2, 'label')]
2018
+ sage: D.reverse_edge((1, 2), label='label')
2019
+ sage: D.edges(sort=True)
2020
+ [(2, 1, 'label')]
2021
+ sage: D.add_edge((1, 2), 'label')
2022
+ sage: D.edges(sort=False)
2023
+ [((1, 2), 'label', None), (2, 1, 'label')]
2024
+ sage: D.reverse_edge((1, 2), 'label')
2025
+ sage: D.edges(sort=False)
2026
+ [('label', (1, 2), None), (2, 1, 'label')]
2027
+
2028
+ TESTS::
2029
+
2030
+ sage: D = DiGraph([(0, 1, None)])
2031
+ sage: D.reverse_edge(0, 1, 'mylabel')
2032
+ Traceback (most recent call last):
2033
+ ...
2034
+ ValueError: input edge must exist in the digraph
2035
+ """
2036
+ # Assigns the expected values to u,v, and label depending on the input.
2037
+ if label is None:
2038
+ if v is None:
2039
+ try:
2040
+ u, v, label = u
2041
+ except Exception:
2042
+ try:
2043
+ u, v = u
2044
+ except Exception:
2045
+ pass
2046
+ else:
2047
+ if v is None:
2048
+ try:
2049
+ u, v = u
2050
+ except Exception:
2051
+ pass
2052
+
2053
+ if not self.has_edge(u, v, label):
2054
+ raise ValueError("input edge must exist in the digraph")
2055
+
2056
+ tempG = self if inplace else copy(self)
2057
+
2058
+ if label is None:
2059
+ if not tempG.allows_multiple_edges():
2060
+ label = tempG.edge_label(u, v)
2061
+ else:
2062
+ # If digraph has parallel edges for input edge, pick the first
2063
+ # from the labels on the list
2064
+ label = tempG.edge_label(u, v)[0]
2065
+
2066
+ if ((not tempG.allows_multiple_edges()) and (tempG.has_edge(v, u))):
2067
+ # If user wants to force digraph to allow parallel edges
2068
+ if multiedges:
2069
+ tempG.allow_multiple_edges(True)
2070
+ tempG.delete_edge(u, v, label)
2071
+ tempG.add_edge(v, u, label)
2072
+
2073
+ # If user does not want to force digraph to allow parallel edges,
2074
+ # we delete edge u to v and overwrite v,u with the label of u,v
2075
+ elif multiedges is False:
2076
+ tempG.delete_edge(u, v, label)
2077
+ tempG.set_edge_label(v, u, label)
2078
+
2079
+ # User is supposed to specify multiedges True or False
2080
+ else:
2081
+ raise ValueError("reversing the given edge is about to "
2082
+ "create two parallel edges but input digraph "
2083
+ "doesn't allow them - User needs to specify "
2084
+ "multiedges is True or False.")
2085
+ else:
2086
+ tempG.delete_edge(u, v, label)
2087
+ tempG.add_edge(v, u, label)
2088
+
2089
+ if not inplace:
2090
+ return tempG
2091
+
2092
+ def reverse_edges(self, edges, inplace=True, multiedges=None):
2093
+ """
2094
+ Reverse a list of edges.
2095
+
2096
+ INPUT:
2097
+
2098
+ - ``edges`` -- list of edges in the DiGraph
2099
+
2100
+ - ``inplace`` -- boolean (default: ``True``); if ``False``, a new
2101
+ digraph is created and returned as output, otherwise ``self`` is
2102
+ modified
2103
+
2104
+ - ``multiedges`` -- boolean (default: ``None``); if ``True``, input
2105
+ graph will be forced to allow parallel edges when necessary (for more
2106
+ information see the documentation of :meth:`~DiGraph.reverse_edge`)
2107
+
2108
+ .. SEEALSO::
2109
+
2110
+ :meth:`~DiGraph.reverse_edge` -- reverses a single edge
2111
+
2112
+ EXAMPLES:
2113
+
2114
+ If ``inplace`` is ``True`` (default), ``self`` is modified::
2115
+
2116
+ sage: D = DiGraph({ 0: [1, 1, 3], 2: [3, 3], 4: [1, 5]}, multiedges=true)
2117
+ sage: D.reverse_edges([[0, 1], [0, 3]])
2118
+ sage: D.reverse_edges([(2, 3), (4, 5)])
2119
+ sage: D.edges(sort=True)
2120
+ [(0, 1, None), (1, 0, None), (2, 3, None), (3, 0, None),
2121
+ (3, 2, None), (4, 1, None), (5, 4, None)]
2122
+
2123
+ If ``inplace`` is ``False``, ``self`` is not modified and a new digraph
2124
+ is returned::
2125
+
2126
+ sage: D = DiGraph([(0, 1, 'A'), (1, 0, 'B'), (1, 2, 'C')])
2127
+ sage: re = D.reverse_edges([(0, 1), (1, 2)],
2128
+ ....: inplace=False,
2129
+ ....: multiedges=True)
2130
+ sage: re.edges(sort=True)
2131
+ [(1, 0, 'A'), (1, 0, 'B'), (2, 1, 'C')]
2132
+ sage: D.edges(sort=True)
2133
+ [(0, 1, 'A'), (1, 0, 'B'), (1, 2, 'C')]
2134
+ sage: D.allows_multiple_edges()
2135
+ False
2136
+ sage: re.allows_multiple_edges()
2137
+ True
2138
+
2139
+ If ``multiedges`` is ``True``, ``self`` will be forced to allow parallel
2140
+ edges when and only when it is necessary::
2141
+
2142
+ sage: D = DiGraph([(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)])
2143
+ sage: D.reverse_edges([(1, 2), (2, 3)], multiedges=True)
2144
+ sage: D.edges(sort=True)
2145
+ [(2, 1, 'A'), (2, 1, 'A'), (3, 2, None)]
2146
+ sage: D.allows_multiple_edges()
2147
+ True
2148
+
2149
+ Even if ``multiedges`` is ``True``, ``self`` will not be forced to allow
2150
+ parallel edges when it is not necessary::
2151
+
2152
+ sage: D = DiGraph([(1, 2, 'A'), (2, 1, 'A'), (2, 3, None)])
2153
+ sage: D.reverse_edges([(2, 3)], multiedges=True)
2154
+ sage: D.edges(sort=True)
2155
+ [(1, 2, 'A'), (2, 1, 'A'), (3, 2, None)]
2156
+ sage: D.allows_multiple_edges()
2157
+ False
2158
+
2159
+ If ``multiedges`` is ``False``, ``self`` will not be forced to allow
2160
+ parallel edges and an edge will get deleted::
2161
+
2162
+ sage: D = DiGraph([(1, 2), (2, 1)])
2163
+ sage: D.edges(sort=True)
2164
+ [(1, 2, None), (2, 1, None)]
2165
+ sage: D.reverse_edges([(1, 2)], multiedges=False)
2166
+ sage: D.edges(sort=True)
2167
+ [(2, 1, None)]
2168
+
2169
+ If input edge in digraph has weight/label, then the weight/label should
2170
+ be preserved in the output digraph. User does not need to specify the
2171
+ weight/label when calling function::
2172
+
2173
+ sage: D = DiGraph([(0, 1, '01'), (1, 2, 1), (2, 3, '23')], weighted=True)
2174
+ sage: D.reverse_edges([(0, 1, '01'), (1, 2), (2, 3)])
2175
+ sage: D.edges(sort=True)
2176
+ [(1, 0, '01'), (2, 1, 1), (3, 2, '23')]
2177
+
2178
+ TESTS::
2179
+
2180
+ sage: D = digraphs.Circuit(6)
2181
+ sage: D.reverse_edges(D.edges(sort=True), inplace=False).edges(sort=True)
2182
+ [(0, 5, None), (1, 0, None), (2, 1, None),
2183
+ (3, 2, None), (4, 3, None), (5, 4, None)]
2184
+
2185
+ sage: D = digraphs.Kautz(2, 3) # needs sage.combinat
2186
+ sage: Dr = D.reverse_edges(D.edges(sort=True), inplace=False, # needs sage.combinat
2187
+ ....: multiedges=True)
2188
+ sage: Dr.edges(sort=True) == D.reverse().edges(sort=True) # needs sage.combinat
2189
+ True
2190
+ """
2191
+ tempG = self if inplace else copy(self)
2192
+ for e in edges:
2193
+ tempG.reverse_edge(e, inplace=True, multiedges=multiedges)
2194
+ if not inplace:
2195
+ return tempG
2196
+
2197
+ # Distances
2198
+
2199
+ def eccentricity(self, v=None, by_weight=False, algorithm=None,
2200
+ weight_function=None, check_weight=True, dist_dict=None,
2201
+ with_labels=False):
2202
+ """
2203
+ Return the eccentricity of vertex (or vertices) ``v``.
2204
+
2205
+ The eccentricity of a vertex is the maximum distance to any other
2206
+ vertex.
2207
+
2208
+ For more information and examples on how to use input variables, see
2209
+ :meth:`~GenericGraph.shortest_path_all_pairs`,
2210
+ :meth:`~GenericGraph.shortest_path_lengths` and
2211
+ :meth:`~GenericGraph.shortest_paths`
2212
+
2213
+ INPUT:
2214
+
2215
+ - ``v`` -- either a single vertex or a list of vertices. If it is not
2216
+ specified, then it is taken to be all vertices
2217
+
2218
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, edge
2219
+ weights are taken into account; if ``False``, all edges have weight 1
2220
+
2221
+ - ``algorithm`` -- string (default: ``None``); one of the following
2222
+ algorithms:
2223
+
2224
+ - ``'BFS'`` -- the computation is done through a BFS centered on each
2225
+ vertex successively. Works only if ``by_weight==False``
2226
+
2227
+ - ``'Floyd-Warshall-Cython'`` -- a Cython implementation of the
2228
+ Floyd-Warshall algorithm. Works only if ``by_weight==False`` and
2229
+ ``v is None`` or ``v`` should contain all vertices of ``self``.
2230
+
2231
+ - ``'Floyd-Warshall-Python'`` -- a Python implementation of the
2232
+ Floyd-Warshall algorithm. Works also with weighted graphs, even with
2233
+ negative weights (but no negative cycle is allowed). However, ``v``
2234
+ must be ``None`` or ``v`` should contain all vertices of ``self``.
2235
+
2236
+ - ``'Dijkstra_NetworkX'`` -- the Dijkstra algorithm, implemented in
2237
+ NetworkX. It works with weighted graphs, but no negative weight is
2238
+ allowed.
2239
+
2240
+ - ``'Dijkstra_Boost'`` -- the Dijkstra algorithm, implemented in Boost
2241
+ (works only with positive weights)
2242
+
2243
+ - ``'Johnson_Boost'`` -- the Johnson algorithm, implemented in
2244
+ Boost (works also with negative weights, if there is no negative
2245
+ cycle). Works only if ``v is None`` or ``v`` should contain all
2246
+ vertices of ``self``.
2247
+
2248
+ - ``'From_Dictionary'`` -- uses the (already computed) distances, that
2249
+ are provided by input variable ``dist_dict``
2250
+
2251
+ - ``None`` (default): Sage chooses the best algorithm:
2252
+ ``'From_Dictionary'`` if ``dist_dict`` is not None, ``'BFS'`` for
2253
+ unweighted graphs, ``'Dijkstra_Boost'`` if all weights are
2254
+ positive, ``'Johnson_Boost'`` otherwise.
2255
+
2256
+ - ``weight_function`` -- function (default: ``None``); a function that
2257
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
2258
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
2259
+ and ``by_weight`` is ``True``, we use the edge label ``l``, if ``l``
2260
+ is not ``None``, else ``1`` as a weight.
2261
+
2262
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
2263
+ that the ``weight_function`` outputs a number for each edge
2264
+
2265
+ - ``dist_dict`` -- dictionary (default: ``None``); a dict of dicts of
2266
+ distances (used only if ``algorithm=='From_Dictionary'``)
2267
+
2268
+ - ``with_labels`` -- boolean (default: ``False``); whether to return a
2269
+ list or a dictionary keyed by vertices
2270
+
2271
+ EXAMPLES::
2272
+
2273
+ sage: G = graphs.KrackhardtKiteGraph().to_directed()
2274
+ sage: G.eccentricity()
2275
+ [4, 4, 4, 4, 4, 3, 3, 2, 3, 4]
2276
+ sage: G.vertices(sort=True)
2277
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2278
+ sage: G.eccentricity(7)
2279
+ 2
2280
+ sage: G.eccentricity([7,8,9])
2281
+ [2, 3, 4]
2282
+ sage: G.eccentricity([7,8,9], with_labels=True) == {8: 3, 9: 4, 7: 2}
2283
+ True
2284
+ sage: G = DiGraph(3)
2285
+ sage: G.eccentricity(with_labels=True)
2286
+ {0: +Infinity, 1: +Infinity, 2: +Infinity}
2287
+ sage: G = DiGraph({0:[]})
2288
+ sage: G.eccentricity(with_labels=True)
2289
+ {0: 0}
2290
+ sage: G = DiGraph([(0,1,2), (1,2,3), (2,0,2)])
2291
+ sage: G.eccentricity(algorithm='BFS')
2292
+ [2, 2, 2]
2293
+ sage: G.eccentricity(algorithm='Floyd-Warshall-Cython')
2294
+ [2, 2, 2]
2295
+ sage: G.eccentricity(by_weight=True, algorithm='Dijkstra_NetworkX') # needs networkx
2296
+ [5, 5, 4]
2297
+ sage: G.eccentricity(by_weight=True, algorithm='Dijkstra_Boost')
2298
+ [5, 5, 4]
2299
+ sage: G.eccentricity(by_weight=True, algorithm='Johnson_Boost')
2300
+ [5, 5, 4]
2301
+ sage: G.eccentricity(by_weight=True, algorithm='Floyd-Warshall-Python')
2302
+ [5, 5, 4]
2303
+ sage: G.eccentricity(dist_dict=G.shortest_path_all_pairs(by_weight=True)[0])
2304
+ [5, 5, 4]
2305
+
2306
+ TESTS:
2307
+
2308
+ A non-implemented algorithm::
2309
+
2310
+ sage: G.eccentricity(algorithm='boh')
2311
+ Traceback (most recent call last):
2312
+ ...
2313
+ ValueError: unknown algorithm "boh"
2314
+
2315
+ An algorithm that does not work with edge weights::
2316
+
2317
+ sage: G.eccentricity(by_weight=True, algorithm='BFS')
2318
+ Traceback (most recent call last):
2319
+ ...
2320
+ ValueError: algorithm 'BFS' does not work with weights
2321
+ sage: G.eccentricity(by_weight=True, algorithm='Floyd-Warshall-Cython')
2322
+ Traceback (most recent call last):
2323
+ ...
2324
+ ValueError: algorithm 'Floyd-Warshall-Cython' does not work with weights
2325
+
2326
+ An algorithm that computes the all-pair-shortest-paths when not all
2327
+ vertices are needed::
2328
+
2329
+ sage: G.eccentricity(0, algorithm='Floyd-Warshall-Cython')
2330
+ Traceback (most recent call last):
2331
+ ...
2332
+ ValueError: algorithm 'Floyd-Warshall-Cython' works only if all eccentricities are needed
2333
+ sage: G.eccentricity(0, algorithm='Floyd-Warshall-Python')
2334
+ Traceback (most recent call last):
2335
+ ...
2336
+ ValueError: algorithm 'Floyd-Warshall-Python' works only if all eccentricities are needed
2337
+ sage: G.eccentricity(0, algorithm='Johnson_Boost')
2338
+ Traceback (most recent call last):
2339
+ ...
2340
+ ValueError: algorithm 'Johnson_Boost' works only if all eccentricities are needed
2341
+ """
2342
+ by_weight, weight_function = self._get_weight_function(by_weight=by_weight,
2343
+ weight_function=weight_function,
2344
+ check_weight=check_weight)
2345
+
2346
+ if not by_weight:
2347
+ # We don't want the default weight function
2348
+ weight_function = None
2349
+ elif algorithm in ['BFS', 'Floyd-Warshall-Cython']:
2350
+ raise ValueError("algorithm '{}' does not work with weights".format(algorithm))
2351
+ if algorithm is None:
2352
+ if dist_dict is not None:
2353
+ algorithm = 'From_Dictionary'
2354
+ elif not by_weight:
2355
+ algorithm = 'BFS'
2356
+ elif any(float(weight_function(e)) < 0 for e in self.edge_iterator()):
2357
+ algorithm = 'Johnson_Boost'
2358
+ if algorithm is None:
2359
+ algorithm = 'Dijkstra_Boost'
2360
+
2361
+ if v is not None:
2362
+ if not isinstance(v, list):
2363
+ v = [v]
2364
+ v_set = set(v)
2365
+
2366
+ if v is None or all(u in v_set for u in self):
2367
+ if v is None:
2368
+ v = list(self)
2369
+
2370
+ # If we want to use BFS, we use the Cython routine
2371
+ if algorithm == 'BFS':
2372
+ from sage.graphs.distances_all_pairs import eccentricity
2373
+ algo = 'standard'
2374
+ if with_labels:
2375
+ return dict(zip(v, eccentricity(self, algorithm=algo, vertex_list=v)))
2376
+ else:
2377
+ return eccentricity(self, algorithm=algo, vertex_list=v)
2378
+
2379
+ if algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost']:
2380
+ dist_dict = self.shortest_path_all_pairs(by_weight=by_weight, algorithm=algorithm,
2381
+ weight_function=weight_function,
2382
+ check_weight=False)[0]
2383
+ algorithm = 'From_Dictionary'
2384
+
2385
+ elif algorithm in ['Floyd-Warshall-Python', 'Floyd-Warshall-Cython', 'Johnson_Boost']:
2386
+ raise ValueError("algorithm '" + algorithm + "' works only if all" +
2387
+ " eccentricities are needed")
2388
+
2389
+ ecc = {}
2390
+
2391
+ from sage.rings.infinity import Infinity
2392
+
2393
+ for u in v:
2394
+ if algorithm == 'From_Dictionary':
2395
+ length = dist_dict[u]
2396
+ else:
2397
+ # If algorithm is wrong, the error is raised by the
2398
+ # shortest_path_lengths function
2399
+ length = self.shortest_path_lengths(u, by_weight=by_weight,
2400
+ algorithm=algorithm,
2401
+ weight_function=weight_function,
2402
+ check_weight=False)
2403
+
2404
+ if len(length) != self.num_verts():
2405
+ ecc[u] = Infinity
2406
+ else:
2407
+ ecc[u] = max(length.values())
2408
+
2409
+ if with_labels:
2410
+ return ecc
2411
+ if len(ecc) == 1:
2412
+ # return single value
2413
+ v, = ecc.values()
2414
+ return v
2415
+ return [ecc[u] for u in v]
2416
+
2417
+ def radius(self, by_weight=False, algorithm=None, weight_function=None,
2418
+ check_weight=True):
2419
+ r"""
2420
+ Return the radius of the DiGraph.
2421
+
2422
+ The radius is defined to be the minimum eccentricity of any vertex,
2423
+ where the eccentricity is the maximum distance to any other
2424
+ vertex. For more information and examples on how to use input variables,
2425
+ see :meth:`~GenericGraph.shortest_paths` and
2426
+ :meth:`~DiGraph.eccentricity`
2427
+
2428
+ INPUT:
2429
+
2430
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, edge
2431
+ weights are taken into account; if ``False``, all edges have weight 1
2432
+
2433
+ - ``algorithm`` -- string (default: ``None``); see method
2434
+ :meth:`eccentricity` for the list of available algorithms
2435
+
2436
+ - ``weight_function`` -- function (default: ``None``); a function that
2437
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
2438
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
2439
+ and ``by_weight`` is ``True``, we use the edge label ``l``, if ``l``
2440
+ is not ``None``, else ``1`` as a weight.
2441
+
2442
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
2443
+ that the ``weight_function`` outputs a number for each edge
2444
+
2445
+ EXAMPLES:
2446
+
2447
+ The more symmetric a DiGraph is, the smaller (diameter - radius) is::
2448
+
2449
+ sage: G = graphs.BarbellGraph(9, 3).to_directed()
2450
+ sage: G.radius()
2451
+ 3
2452
+ sage: G.diameter()
2453
+ 6
2454
+
2455
+ ::
2456
+
2457
+ sage: G = digraphs.Circuit(9)
2458
+ sage: G.radius()
2459
+ 8
2460
+ sage: G.diameter()
2461
+ 8
2462
+
2463
+ TESTS::
2464
+
2465
+ sage: G = DiGraph()
2466
+ sage: G.radius()
2467
+ Traceback (most recent call last):
2468
+ ...
2469
+ ValueError: radius is not defined for the empty DiGraph
2470
+
2471
+ Check that :issue:`35300` is fixed::
2472
+
2473
+ sage: H = DiGraph([[42, 'John'], [(42, 'John')]])
2474
+ sage: H.radius()
2475
+ 1
2476
+ """
2477
+ if not self.order():
2478
+ raise ValueError("radius is not defined for the empty DiGraph")
2479
+
2480
+ return min(self.eccentricity(v=list(self), by_weight=by_weight,
2481
+ weight_function=weight_function,
2482
+ check_weight=check_weight,
2483
+ algorithm=algorithm))
2484
+
2485
+ def diameter(self, by_weight=False, algorithm=None, weight_function=None,
2486
+ check_weight=True):
2487
+ r"""
2488
+ Return the diameter of the DiGraph.
2489
+
2490
+ The diameter is defined to be the maximum distance between two vertices.
2491
+ It is infinite if the DiGraph is not strongly connected.
2492
+
2493
+ For more information and examples on how to use input variables, see
2494
+ :meth:`~GenericGraph.shortest_paths` and
2495
+ :meth:`~DiGraph.eccentricity`
2496
+
2497
+ INPUT:
2498
+
2499
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, edge
2500
+ weights are taken into account; if ``False``, all edges have weight 1
2501
+
2502
+ - ``algorithm`` -- string (default: ``None``); one of the following
2503
+ algorithms:
2504
+
2505
+ - ``'BFS'``: the computation is done through a BFS centered on each
2506
+ vertex successively. Works only if ``by_weight==False``. It computes
2507
+ all the eccentricities and return the maximum value.
2508
+
2509
+ - ``'Floyd-Warshall-Cython'``: a Cython implementation of the
2510
+ Floyd-Warshall algorithm. Works only if ``by_weight==False``. It
2511
+ computes all the eccentricities and return the maximum value.
2512
+
2513
+ - ``'Floyd-Warshall-Python'``: a Python implementation of the
2514
+ Floyd-Warshall algorithm. Works also with weighted graphs, even with
2515
+ negative weights (but no negative cycle is allowed). It computes all
2516
+ the eccentricities and return the maximum value.
2517
+
2518
+ - ``'Dijkstra_NetworkX'``: the Dijkstra algorithm, implemented in
2519
+ NetworkX. It works with weighted graphs, but no negative weight is
2520
+ allowed. It computes all the eccentricities and return the maximum
2521
+ value.
2522
+
2523
+ - ``'DiFUB'``, ``'2Dsweep'``: these algorithms are
2524
+ implemented in :func:`sage.graphs.distances_all_pairs.diameter` and
2525
+ :func:`sage.graphs.base.boost_graph.diameter`. ``'2Dsweep'`` returns
2526
+ lower bound on the diameter, while ``'DiFUB'`` returns the exact
2527
+ computed diameter. They also work with negative weight, if there is
2528
+ no negative cycle. See the functions documentation for more
2529
+ information.
2530
+
2531
+ - ``'standard'`` : the standard algorithm is implemented in
2532
+ :func:`sage.graphs.distances_all_pairs.diameter`. It works only
2533
+ if ``by_weight==False``. See the function documentation for more
2534
+ information. It computes all the eccentricities and return the
2535
+ maximum value.
2536
+
2537
+ - ``'Dijkstra_Boost'``: the Dijkstra algorithm, implemented in Boost
2538
+ (works only with positive weights). It computes all the
2539
+ eccentricities and return the maximum value.
2540
+
2541
+ - ``'Johnson_Boost'``: the Johnson algorithm, implemented in
2542
+ Boost (works also with negative weights, if there is no negative
2543
+ cycle). It computes all the eccentricities and return the maximum
2544
+ value.
2545
+
2546
+ - ``None`` (default): Sage chooses the best algorithm: ``'DiFUB'``.
2547
+
2548
+ - ``weight_function`` -- function (default: ``None``); a function that
2549
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
2550
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
2551
+ and ``by_weight`` is ``True``, we use the edge label ``l``, if ``l``
2552
+ is not ``None``, else ``1`` as weight.
2553
+
2554
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
2555
+ that the ``weight_function`` outputs a number for each edge
2556
+
2557
+ EXAMPLES::
2558
+
2559
+ sage: # needs sage.combinat
2560
+ sage: G = digraphs.DeBruijn(5,4)
2561
+ sage: G.diameter()
2562
+ 4
2563
+ sage: G = digraphs.GeneralizedDeBruijn(9, 3)
2564
+ sage: G.diameter()
2565
+ 2
2566
+
2567
+ TESTS::
2568
+
2569
+ sage: G = graphs.RandomGNP(40, 0.4).to_directed()
2570
+ sage: d1 = G.diameter(algorithm='DiFUB', by_weight=True)
2571
+ sage: d2 = max(G.eccentricity(algorithm='Dijkstra_Boost', by_weight=True))
2572
+ sage: d1 == d2
2573
+ True
2574
+ sage: G.diameter(algorithm='BFS', by_weight=True)
2575
+ Traceback (most recent call last):
2576
+ ...
2577
+ ValueError: algorithm 'BFS' does not work with weights
2578
+ sage: G.diameter(algorithm='Floyd-Warshall-Cython', by_weight=True)
2579
+ Traceback (most recent call last):
2580
+ ...
2581
+ ValueError: algorithm 'Floyd-Warshall-Cython' does not work with weights
2582
+ sage: G = digraphs.Path(5)
2583
+ sage: G.diameter(algorithm = 'DiFUB')
2584
+ +Infinity
2585
+ sage: G = DiGraph([(1,2,4), (2,1,7)])
2586
+ sage: G.diameter(algorithm='2Dsweep', by_weight=True)
2587
+ 7.0
2588
+ sage: G.delete_edge(2,1,7)
2589
+ sage: G.add_edge(2,1,-5)
2590
+ sage: G.diameter(algorithm='2Dsweep', by_weight=True)
2591
+ Traceback (most recent call last):
2592
+ ...
2593
+ ValueError: the graph contains a negative cycle
2594
+ sage: G = DiGraph()
2595
+ sage: G.diameter()
2596
+ Traceback (most recent call last):
2597
+ ...
2598
+ ValueError: diameter is not defined for the empty DiGraph
2599
+
2600
+ :issue:`32095` is fixed::
2601
+
2602
+ sage: g6 = 'guQOUOQCW[IaDBCVP_IE\\RfxV@WMSaeHgheEIA@tfOJkB~@EpGLCrs'
2603
+ sage: g6 += 'aPIpwgQI_`Abs_x?VWxNJAo@w\\hffCDAW]bYGMIZGC_PYOrIw[Gp['
2604
+ sage: g6 += '@FTgc_O}E?fXAnGCB{gSaUcD'
2605
+ sage: G = Graph(g6).to_directed()
2606
+ sage: G.diameter(algorithm='DiFUB', by_weight=False)
2607
+ 3
2608
+ sage: G.diameter(algorithm='DiFUB', by_weight=True)
2609
+ 3.0
2610
+
2611
+ Check that :issue:`35300` is fixed::
2612
+
2613
+ sage: H = DiGraph([[42, 'John'], [(42, 'John')]])
2614
+ sage: H.diameter()
2615
+ +Infinity
2616
+ sage: H.add_edge('John', 42)
2617
+ sage: H.diameter()
2618
+ 1
2619
+ """
2620
+ if not self.order():
2621
+ raise ValueError("diameter is not defined for the empty DiGraph")
2622
+
2623
+ by_weight, weight_function = self._get_weight_function(by_weight=by_weight,
2624
+ weight_function=weight_function,
2625
+ check_weight=check_weight)
2626
+
2627
+ if not by_weight:
2628
+ # We don't want the default weight function
2629
+ weight_function = None
2630
+ elif algorithm in ['BFS', 'Floyd-Warshall-Cython']:
2631
+ raise ValueError("algorithm '{}' does not work with weights".format(algorithm))
2632
+
2633
+ if algorithm is None:
2634
+ algorithm = 'DiFUB'
2635
+
2636
+ if algorithm in ['2Dsweep', 'DiFUB']:
2637
+ if not by_weight:
2638
+ from sage.graphs.distances_all_pairs import diameter
2639
+ return diameter(self, algorithm=algorithm)
2640
+ else:
2641
+ from sage.graphs.base.boost_graph import diameter
2642
+ return diameter(self, algorithm=algorithm,
2643
+ weight_function=weight_function,
2644
+ check_weight=False)
2645
+
2646
+ if algorithm == 'BFS':
2647
+ from sage.graphs.distances_all_pairs import diameter
2648
+ return diameter(self, algorithm='standard')
2649
+
2650
+ return max(self.eccentricity(v=list(self), by_weight=by_weight,
2651
+ weight_function=weight_function,
2652
+ check_weight=False,
2653
+ algorithm=algorithm))
2654
+
2655
+ def center(self, by_weight=False, algorithm=None, weight_function=None,
2656
+ check_weight=True):
2657
+ r"""
2658
+ Return the set of vertices in the center of the DiGraph.
2659
+
2660
+ The center is the set of vertices whose eccentricity is equal to the
2661
+ radius of the DiGraph, i.e., achieving the minimum eccentricity.
2662
+
2663
+ For more information and examples on how to use input variables,
2664
+ see :meth:`~GenericGraph.shortest_paths` and
2665
+ :meth:`~DiGraph.eccentricity`
2666
+
2667
+ INPUT:
2668
+
2669
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, edge
2670
+ weights are taken into account; if ``False``, all edges have weight 1
2671
+
2672
+ - ``algorithm`` -- string (default: ``None``); see method
2673
+ :meth:`eccentricity` for the list of available algorithms
2674
+
2675
+ - ``weight_function`` -- function (default: ``None``); a function that
2676
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
2677
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
2678
+ and ``by_weight`` is ``True``, we use the edge label ``l`` as a
2679
+ weight, if ``l`` is not ``None``, else ``1`` as a weight.
2680
+
2681
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
2682
+ that the ``weight_function`` outputs a number for each edge
2683
+
2684
+ EXAMPLES:
2685
+
2686
+ Every vertex is a center in a Circuit-DiGraph::
2687
+
2688
+ sage: G = digraphs.Circuit(9)
2689
+ sage: G.center()
2690
+ [0, 1, 2, 3, 4, 5, 6, 7, 8]
2691
+
2692
+ Center can be the whole graph::
2693
+
2694
+ sage: G.subgraph(G.center()) == G
2695
+ True
2696
+
2697
+ Some other graphs::
2698
+
2699
+ sage: G = digraphs.Path(5)
2700
+ sage: G.center()
2701
+ [0]
2702
+ sage: G = DiGraph([(0,1,2), (1,2,3), (2,0,2)])
2703
+ sage: G.center(by_weight=True)
2704
+ [2]
2705
+
2706
+ TESTS::
2707
+
2708
+ sage: G = DiGraph()
2709
+ sage: G.center()
2710
+ []
2711
+ sage: G = DiGraph(3)
2712
+ sage: G.center()
2713
+ [0, 1, 2]
2714
+ """
2715
+ ecc = self.eccentricity(v=list(self), by_weight=by_weight,
2716
+ weight_function=weight_function,
2717
+ algorithm=algorithm,
2718
+ check_weight=check_weight,
2719
+ with_labels=True)
2720
+ try:
2721
+ r = min(ecc.values())
2722
+ except Exception:
2723
+ return []
2724
+ return [v for v in self if ecc[v] == r]
2725
+
2726
+ def periphery(self, by_weight=False, algorithm=None, weight_function=None,
2727
+ check_weight=True):
2728
+ r"""
2729
+ Return the set of vertices in the periphery of the DiGraph.
2730
+
2731
+ The periphery is the set of vertices whose eccentricity is equal to the
2732
+ diameter of the DiGraph, i.e., achieving the maximum eccentricity.
2733
+
2734
+ For more information and examples on how to use input variables,
2735
+ see :meth:`~GenericGraph.shortest_paths` and
2736
+ :meth:`~DiGraph.eccentricity`
2737
+
2738
+ INPUT:
2739
+
2740
+ - ``by_weight`` -- boolean (default: ``False``); if ``True``, edge
2741
+ weights are taken into account; if ``False``, all edges have weight 1
2742
+
2743
+ - ``algorithm`` -- string (default: ``None``); see method
2744
+ :meth:`eccentricity` for the list of available algorithms
2745
+
2746
+ - ``weight_function`` -- function (default: ``None``); a function that
2747
+ takes as input an edge ``(u, v, l)`` and outputs its weight. If not
2748
+ ``None``, ``by_weight`` is automatically set to ``True``. If ``None``
2749
+ and ``by_weight`` is ``True``, we use the edge label ``l`` as a
2750
+ weight, if ``l`` is not ``None``, else ``1`` as a weight.
2751
+
2752
+ - ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
2753
+ that the ``weight_function`` outputs a number for each edge
2754
+
2755
+ EXAMPLES::
2756
+
2757
+ sage: G = graphs.DiamondGraph().to_directed()
2758
+ sage: G.periphery()
2759
+ [0, 3]
2760
+ sage: P = digraphs.Path(5)
2761
+ sage: P.periphery()
2762
+ [1, 2, 3, 4]
2763
+ sage: G = digraphs.Complete(5)
2764
+ sage: G.subgraph(G.periphery()) == G
2765
+ True
2766
+
2767
+ TESTS::
2768
+
2769
+ sage: G = DiGraph()
2770
+ sage: G.periphery()
2771
+ []
2772
+ sage: G.add_vertex()
2773
+ 0
2774
+ sage: G.periphery()
2775
+ [0]
2776
+ """
2777
+ ecc = self.eccentricity(v=list(self), by_weight=by_weight,
2778
+ weight_function=weight_function,
2779
+ algorithm=algorithm,
2780
+ check_weight=check_weight,
2781
+ with_labels=True)
2782
+ try:
2783
+ d = max(ecc.values())
2784
+ except Exception:
2785
+ return []
2786
+ return [v for v in self if ecc[v] == d]
2787
+
2788
+ # Paths and cycles iterators
2789
+
2790
+ def _all_cycles_iterator_vertex(self, vertex, starting_vertices=None, simple=False,
2791
+ rooted=False, max_length=None, trivial=False,
2792
+ remove_acyclic_edges=True):
2793
+ r"""
2794
+ Return an iterator over the cycles of ``self`` starting with the given
2795
+ vertex.
2796
+
2797
+ INPUT:
2798
+
2799
+ - ``vertex`` -- the starting vertex of the cycle
2800
+
2801
+ - ``starting_vertices`` -- iterable (default: ``None``); vertices from
2802
+ which the cycles must start. If ``None``, then all vertices of the
2803
+ graph can be starting points. This argument is necessary if ``rooted``
2804
+ is set to ``True``.
2805
+
2806
+ - ``simple`` -- boolean (default: ``False``); if set to ``True``, then
2807
+ only simple cycles are considered. A cycle is simple if the only
2808
+ vertex occurring twice in it is the starting and ending one.
2809
+
2810
+ - ``rooted`` -- boolean (default: ``False``); if set to False, then
2811
+ cycles differing only by their starting vertex are considered the same
2812
+ (e.g. ``['a', 'b', 'c', 'a']`` and ``['b', 'c', 'a',
2813
+ 'b']``). Otherwise, all cycles are enumerated.
2814
+
2815
+ - ``max_length`` -- nonnegative integer (default: ``None``); the
2816
+ maximum length of the enumerated paths. If set to ``None``, then all
2817
+ lengths are allowed.
2818
+
2819
+ - ``trivial`` -- boolean (default: ``False``); if set to ``True``, then
2820
+ the empty paths are also enumerated
2821
+
2822
+ - ``remove_acyclic_edges`` -- boolean (default: ``True``); whether
2823
+ acyclic edges must be removed from the graph. Used to avoid
2824
+ recomputing it for each vertex
2825
+
2826
+ OUTPUT: iterator
2827
+
2828
+ EXAMPLES::
2829
+
2830
+ sage: g = DiGraph({'a': ['a', 'b'], 'b': ['c'], 'c': ['d'], 'd': ['c']}, loops=True)
2831
+ sage: it = g._all_cycles_iterator_vertex('a', simple=False, max_length=None)
2832
+ sage: for i in range(5): print(next(it))
2833
+ ['a', 'a']
2834
+ ['a', 'a', 'a']
2835
+ ['a', 'a', 'a', 'a']
2836
+ ['a', 'a', 'a', 'a', 'a']
2837
+ ['a', 'a', 'a', 'a', 'a', 'a']
2838
+ sage: it = g._all_cycles_iterator_vertex('c', simple=False, max_length=None)
2839
+ sage: for i in range(5): print(next(it))
2840
+ ['c', 'd', 'c']
2841
+ ['c', 'd', 'c', 'd', 'c']
2842
+ ['c', 'd', 'c', 'd', 'c', 'd', 'c']
2843
+ ['c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c']
2844
+ ['c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c']
2845
+
2846
+ sage: it = g._all_cycles_iterator_vertex('d', simple=False, max_length=None)
2847
+ sage: for i in range(5): print(next(it))
2848
+ ['d', 'c', 'd']
2849
+ ['d', 'c', 'd', 'c', 'd']
2850
+ ['d', 'c', 'd', 'c', 'd', 'c', 'd']
2851
+ ['d', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd']
2852
+ ['d', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd', 'c', 'd']
2853
+
2854
+ It is possible to set a maximum length so that the number of cycles is
2855
+ finite::
2856
+
2857
+ sage: it = g._all_cycles_iterator_vertex('d', simple=False, max_length=6)
2858
+ sage: list(it)
2859
+ [['d', 'c', 'd'], ['d', 'c', 'd', 'c', 'd'], ['d', 'c', 'd', 'c', 'd', 'c', 'd']]
2860
+
2861
+ When ``simple`` is set to True, the number of cycles is finite since no vertex
2862
+ but the first one can occur more than once::
2863
+
2864
+ sage: it = g._all_cycles_iterator_vertex('d', simple=True, max_length=None)
2865
+ sage: list(it)
2866
+ [['d', 'c', 'd']]
2867
+
2868
+ By default, the empty cycle is not enumerated::
2869
+
2870
+ sage: it = g._all_cycles_iterator_vertex('d', simple=True, trivial=True)
2871
+ sage: list(it)
2872
+ [['d'], ['d', 'c', 'd']]
2873
+ """
2874
+ if starting_vertices is None:
2875
+ starting_vertices = [vertex]
2876
+ # First enumerate the empty cycle
2877
+ if trivial:
2878
+ yield [vertex]
2879
+ # First we remove vertices and edges that are not part of any cycle
2880
+ if remove_acyclic_edges:
2881
+ sccs = self.strongly_connected_components()
2882
+ d = {}
2883
+ for id, component in enumerate(sccs):
2884
+ for v in component:
2885
+ d[v] = id
2886
+ h = copy(self)
2887
+ h.delete_edges((u, v) for u, v in h.edge_iterator(labels=False) if d[u] != d[v])
2888
+ else:
2889
+ h = self
2890
+ queue = [[vertex]]
2891
+ if max_length is None:
2892
+ from sage.rings.infinity import Infinity
2893
+ max_length = Infinity
2894
+ while queue:
2895
+ path = queue.pop(0)
2896
+ # Checks if a cycle has been found
2897
+ if len(path) > 1 and path[0] == path[-1]:
2898
+ yield path
2899
+ # Makes sure that the current cycle is not too long
2900
+ # Also if a cycle has been encountered and only simple cycles are
2901
+ # allowed, Then it discards the current path
2902
+ if len(path) <= max_length and (not simple or path.count(path[-1]) == 1):
2903
+ for neighbor in h.neighbor_out_iterator(path[-1]):
2904
+ # If cycles are not rooted, makes sure to keep only the
2905
+ # minimum cycle according to the lexicographic order
2906
+ if rooted or neighbor not in starting_vertices or path[0] <= neighbor:
2907
+ queue.append(path + [neighbor])
2908
+
2909
+ def all_cycles_iterator(self, starting_vertices=None, simple=False,
2910
+ rooted=False, max_length=None, trivial=False):
2911
+ r"""
2912
+ Return an iterator over all the cycles of ``self`` starting with one of
2913
+ the given vertices.
2914
+
2915
+ The cycles are enumerated in increasing length order.
2916
+
2917
+ INPUT:
2918
+
2919
+ - ``starting_vertices`` -- iterable (default: ``None``); vertices from
2920
+ which the cycles must start. If ``None``, then all vertices of the
2921
+ graph can be starting points. This argument is necessary if ``rooted``
2922
+ is set to ``True``.
2923
+
2924
+ - ``simple`` -- boolean (default: ``False``); if set to ``True``, then
2925
+ only simple cycles are considered. A cycle is simple if the only
2926
+ vertex occurring twice in it is the starting and ending one.
2927
+
2928
+ - ``rooted`` -- boolean (default: ``False``); if set to False, then
2929
+ cycles differing only by their starting vertex are considered the same
2930
+ (e.g. ``['a', 'b', 'c', 'a']`` and ``['b', 'c', 'a',
2931
+ 'b']``). Otherwise, all cycles are enumerated.
2932
+
2933
+ - ``max_length`` -- nonnegative integer (default: ``None``); the
2934
+ maximum length of the enumerated paths. If set to ``None``, then all
2935
+ lengths are allowed.
2936
+
2937
+ - ``trivial`` -- boolean (default: ``False``); if set to ``True``, then
2938
+ the empty paths are also enumerated
2939
+
2940
+ OUTPUT: iterator
2941
+
2942
+ .. SEEALSO::
2943
+
2944
+ - :meth:`all_simple_cycles`
2945
+
2946
+ AUTHOR:
2947
+
2948
+ Alexandre Blondin Masse
2949
+
2950
+ EXAMPLES::
2951
+
2952
+ sage: g = DiGraph({'a': ['a', 'b'], 'b': ['c'], 'c': ['d'], 'd': ['c']}, loops=True)
2953
+ sage: it = g.all_cycles_iterator()
2954
+ sage: for _ in range(7): print(next(it))
2955
+ ['a', 'a']
2956
+ ['a', 'a', 'a']
2957
+ ['c', 'd', 'c']
2958
+ ['a', 'a', 'a', 'a']
2959
+ ['a', 'a', 'a', 'a', 'a']
2960
+ ['c', 'd', 'c', 'd', 'c']
2961
+ ['a', 'a', 'a', 'a', 'a', 'a']
2962
+
2963
+ There are no cycles in the empty graph and in acyclic graphs::
2964
+
2965
+ sage: g = DiGraph()
2966
+ sage: it = g.all_cycles_iterator()
2967
+ sage: list(it)
2968
+ []
2969
+ sage: g = DiGraph({0:[1]})
2970
+ sage: it = g.all_cycles_iterator()
2971
+ sage: list(it)
2972
+ []
2973
+
2974
+ It is possible to restrict the starting vertices of the cycles::
2975
+
2976
+ sage: g = DiGraph({'a': ['a', 'b'], 'b': ['c'], 'c': ['d'], 'd': ['c']}, loops=True)
2977
+ sage: it = g.all_cycles_iterator(starting_vertices=['b', 'c'])
2978
+ sage: for _ in range(3): print(next(it))
2979
+ ['c', 'd', 'c']
2980
+ ['c', 'd', 'c', 'd', 'c']
2981
+ ['c', 'd', 'c', 'd', 'c', 'd', 'c']
2982
+
2983
+ Also, one can bound the length of the cycles::
2984
+
2985
+ sage: it = g.all_cycles_iterator(max_length=3)
2986
+ sage: list(it)
2987
+ [['a', 'a'], ['a', 'a', 'a'], ['c', 'd', 'c'],
2988
+ ['a', 'a', 'a', 'a']]
2989
+
2990
+ By default, cycles differing only by their starting point are not all
2991
+ enumerated, but this may be parametrized::
2992
+
2993
+ sage: it = g.all_cycles_iterator(max_length=3, rooted=False)
2994
+ sage: list(it)
2995
+ [['a', 'a'], ['a', 'a', 'a'], ['c', 'd', 'c'],
2996
+ ['a', 'a', 'a', 'a']]
2997
+ sage: it = g.all_cycles_iterator(max_length=3, rooted=True)
2998
+ sage: list(it)
2999
+ [['a', 'a'], ['a', 'a', 'a'], ['c', 'd', 'c'], ['d', 'c', 'd'],
3000
+ ['a', 'a', 'a', 'a']]
3001
+
3002
+ One may prefer to enumerate simple cycles, i.e. cycles such that the only
3003
+ vertex occurring twice in it is the starting and ending one (see also
3004
+ :meth:`all_simple_cycles`)::
3005
+
3006
+ sage: it = g.all_cycles_iterator(simple=True)
3007
+ sage: list(it)
3008
+ [['a', 'a'], ['c', 'd', 'c']]
3009
+ sage: g = digraphs.Circuit(4)
3010
+ sage: list(g.all_cycles_iterator(simple=True))
3011
+ [[0, 1, 2, 3, 0]]
3012
+ """
3013
+ if starting_vertices is None:
3014
+ starting_vertices = self
3015
+ # Since a cycle is always included in a given strongly connected
3016
+ # component, we may remove edges from the graph
3017
+ sccs = self.strongly_connected_components()
3018
+ d = {}
3019
+ for id, component in enumerate(sccs):
3020
+ for v in component:
3021
+ d[v] = id
3022
+ h = copy(self)
3023
+ h.delete_edges((u, v) for u, v in h.edge_iterator(labels=False) if d[u] != d[v])
3024
+
3025
+ # We create one cycles iterator per vertex. This is necessary if we
3026
+ # want to iterate over cycles with increasing length.
3027
+ def cycle_iter(v):
3028
+ return h._all_cycles_iterator_vertex(v,
3029
+ starting_vertices=starting_vertices,
3030
+ simple=simple,
3031
+ rooted=rooted,
3032
+ max_length=max_length,
3033
+ trivial=trivial,
3034
+ remove_acyclic_edges=False)
3035
+
3036
+ vertex_iterators = {v: cycle_iter(v) for v in starting_vertices}
3037
+
3038
+ cycles = []
3039
+ for vi in vertex_iterators.values():
3040
+ try:
3041
+ cycle = next(vi)
3042
+ cycles.append((len(cycle), cycle))
3043
+ except StopIteration:
3044
+ pass
3045
+ # Since we always extract a shortest path, using a heap
3046
+ # can speed up the algorithm
3047
+ from heapq import heapify, heappop, heappush
3048
+ heapify(cycles)
3049
+ while cycles:
3050
+ # We choose the shortest available cycle
3051
+ _, shortest_cycle = heappop(cycles)
3052
+ yield shortest_cycle
3053
+ # We update the cycle iterator to its next available cycle if it
3054
+ # exists
3055
+ try:
3056
+ cycle = next(vertex_iterators[shortest_cycle[0]])
3057
+ heappush(cycles, (len(cycle), cycle))
3058
+ except StopIteration:
3059
+ pass
3060
+
3061
+ def all_simple_cycles(self, starting_vertices=None, rooted=False,
3062
+ max_length=None, trivial=False):
3063
+ r"""
3064
+ Return a list of all simple cycles of ``self``.
3065
+
3066
+ INPUT:
3067
+
3068
+ - ``starting_vertices`` -- iterable (default: ``None``); vertices from
3069
+ which the cycles must start. If ``None``, then all vertices of the
3070
+ graph can be starting points. This argument is necessary if ``rooted``
3071
+ is set to ``True``.
3072
+
3073
+ - ``rooted`` -- boolean (default: ``False``); if set to False, then
3074
+ cycles differing only by their starting vertex are considered the same
3075
+ (e.g. ``['a', 'b', 'c', 'a']`` and ``['b', 'c', 'a',
3076
+ 'b']``). Otherwise, all cycles are enumerated.
3077
+
3078
+ - ``max_length`` -- nonnegative integer (default: ``None``); the
3079
+ maximum length of the enumerated paths. If set to ``None``, then all
3080
+ lengths are allowed.
3081
+
3082
+ - ``trivial`` -- boolean (default: ``False``); if set to ``True``, then
3083
+ the empty paths are also enumerated
3084
+
3085
+ OUTPUT: list
3086
+
3087
+ .. NOTE::
3088
+
3089
+ Although the number of simple cycles of a finite graph is always
3090
+ finite, computing all its cycles may take a very long time.
3091
+
3092
+ EXAMPLES::
3093
+
3094
+ sage: g = DiGraph({'a': ['a', 'b'], 'b': ['c'], 'c': ['d'], 'd': ['c']}, loops=True)
3095
+ sage: g.all_simple_cycles()
3096
+ [['a', 'a'], ['c', 'd', 'c']]
3097
+
3098
+ The directed version of the Petersen graph::
3099
+
3100
+ sage: g = graphs.PetersenGraph().to_directed()
3101
+ sage: g.all_simple_cycles(max_length=4)
3102
+ [[0, 1, 0], [0, 4, 0], [0, 5, 0], [1, 2, 1], [1, 6, 1], [2, 3, 2],
3103
+ [2, 7, 2], [3, 4, 3], [3, 8, 3], [4, 9, 4], [5, 7, 5], [5, 8, 5],
3104
+ [6, 8, 6], [6, 9, 6], [7, 9, 7]]
3105
+ sage: g.all_simple_cycles(max_length=6)
3106
+ [[0, 1, 0], [0, 4, 0], [0, 5, 0], [1, 2, 1], [1, 6, 1], [2, 3, 2],
3107
+ [2, 7, 2], [3, 4, 3], [3, 8, 3], [4, 9, 4], [5, 7, 5], [5, 8, 5],
3108
+ [6, 8, 6], [6, 9, 6], [7, 9, 7], [0, 1, 2, 3, 4, 0],
3109
+ [0, 1, 2, 7, 5, 0], [0, 1, 6, 8, 5, 0], [0, 1, 6, 9, 4, 0],
3110
+ [0, 4, 3, 2, 1, 0], [0, 4, 3, 8, 5, 0], [0, 4, 9, 6, 1, 0],
3111
+ [0, 4, 9, 7, 5, 0], [0, 5, 7, 2, 1, 0], [0, 5, 7, 9, 4, 0],
3112
+ [0, 5, 8, 3, 4, 0], [0, 5, 8, 6, 1, 0], [1, 2, 3, 8, 6, 1],
3113
+ [1, 2, 7, 9, 6, 1], [1, 6, 8, 3, 2, 1], [1, 6, 9, 7, 2, 1],
3114
+ [2, 3, 4, 9, 7, 2], [2, 3, 8, 5, 7, 2], [2, 7, 5, 8, 3, 2],
3115
+ [2, 7, 9, 4, 3, 2], [3, 4, 9, 6, 8, 3], [3, 8, 6, 9, 4, 3],
3116
+ [5, 7, 9, 6, 8, 5], [5, 8, 6, 9, 7, 5], [0, 1, 2, 3, 8, 5, 0],
3117
+ [0, 1, 2, 7, 9, 4, 0], [0, 1, 6, 8, 3, 4, 0],
3118
+ [0, 1, 6, 9, 7, 5, 0], [0, 4, 3, 2, 7, 5, 0],
3119
+ [0, 4, 3, 8, 6, 1, 0], [0, 4, 9, 6, 8, 5, 0],
3120
+ [0, 4, 9, 7, 2, 1, 0], [0, 5, 7, 2, 3, 4, 0],
3121
+ [0, 5, 7, 9, 6, 1, 0], [0, 5, 8, 3, 2, 1, 0],
3122
+ [0, 5, 8, 6, 9, 4, 0], [1, 2, 3, 4, 9, 6, 1],
3123
+ [1, 2, 7, 5, 8, 6, 1], [1, 6, 8, 5, 7, 2, 1],
3124
+ [1, 6, 9, 4, 3, 2, 1], [2, 3, 8, 6, 9, 7, 2],
3125
+ [2, 7, 9, 6, 8, 3, 2], [3, 4, 9, 7, 5, 8, 3],
3126
+ [3, 8, 5, 7, 9, 4, 3]]
3127
+
3128
+ The complete graph (without loops) on `4` vertices::
3129
+
3130
+ sage: g = graphs.CompleteGraph(4).to_directed()
3131
+ sage: g.all_simple_cycles()
3132
+ [[0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 2, 1], [1, 3, 1], [2, 3, 2],
3133
+ [0, 1, 2, 0], [0, 1, 3, 0], [0, 2, 1, 0], [0, 2, 3, 0],
3134
+ [0, 3, 1, 0], [0, 3, 2, 0], [1, 2, 3, 1], [1, 3, 2, 1],
3135
+ [0, 1, 2, 3, 0], [0, 1, 3, 2, 0], [0, 2, 1, 3, 0],
3136
+ [0, 2, 3, 1, 0], [0, 3, 1, 2, 0], [0, 3, 2, 1, 0]]
3137
+
3138
+ If the graph contains a large number of cycles, one can bound the length
3139
+ of the cycles, or simply restrict the possible starting vertices of the
3140
+ cycles::
3141
+
3142
+ sage: g = graphs.CompleteGraph(20).to_directed()
3143
+ sage: g.all_simple_cycles(max_length=2)
3144
+ [[0, 16, 0], [0, 1, 0], [0, 17, 0], [0, 2, 0], [0, 18, 0],
3145
+ [0, 3, 0], [0, 19, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0],
3146
+ [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 0], [0, 12, 0],
3147
+ [0, 13, 0], [0, 14, 0], [0, 15, 0], [1, 16, 1], [1, 17, 1],
3148
+ [1, 2, 1], [1, 18, 1], [1, 3, 1], [1, 19, 1], [1, 4, 1], [1, 5, 1],
3149
+ [1, 6, 1], [1, 7, 1], [1, 8, 1], [1, 9, 1], [1, 10, 1], [1, 11, 1],
3150
+ [1, 12, 1], [1, 13, 1], [1, 14, 1], [1, 15, 1], [2, 16, 2],
3151
+ [2, 17, 2], [2, 18, 2], [2, 3, 2], [2, 19, 2], [2, 4, 2],
3152
+ [2, 5, 2], [2, 6, 2], [2, 7, 2], [2, 8, 2], [2, 9, 2], [2, 10, 2],
3153
+ [2, 11, 2], [2, 12, 2], [2, 13, 2], [2, 14, 2], [2, 15, 2],
3154
+ [3, 16, 3], [3, 17, 3], [3, 18, 3], [3, 19, 3], [3, 4, 3],
3155
+ [3, 5, 3], [3, 6, 3], [3, 7, 3], [3, 8, 3], [3, 9, 3], [3, 10, 3],
3156
+ [3, 11, 3], [3, 12, 3], [3, 13, 3], [3, 14, 3], [3, 15, 3],
3157
+ [4, 16, 4], [4, 17, 4], [4, 18, 4], [4, 19, 4], [4, 5, 4],
3158
+ [4, 6, 4], [4, 7, 4], [4, 8, 4], [4, 9, 4], [4, 10, 4], [4, 11, 4],
3159
+ [4, 12, 4], [4, 13, 4], [4, 14, 4], [4, 15, 4], [5, 16, 5],
3160
+ [5, 17, 5], [5, 18, 5], [5, 19, 5], [5, 6, 5], [5, 7, 5],
3161
+ [5, 8, 5], [5, 9, 5], [5, 10, 5], [5, 11, 5], [5, 12, 5],
3162
+ [5, 13, 5], [5, 14, 5], [5, 15, 5], [6, 16, 6], [6, 17, 6],
3163
+ [6, 18, 6], [6, 19, 6], [6, 7, 6], [6, 8, 6], [6, 9, 6],
3164
+ [6, 10, 6], [6, 11, 6], [6, 12, 6], [6, 13, 6], [6, 14, 6],
3165
+ [6, 15, 6], [7, 16, 7], [7, 17, 7], [7, 18, 7], [7, 19, 7],
3166
+ [7, 8, 7], [7, 9, 7], [7, 10, 7], [7, 11, 7], [7, 12, 7],
3167
+ [7, 13, 7], [7, 14, 7], [7, 15, 7], [8, 16, 8], [8, 17, 8],
3168
+ [8, 18, 8], [8, 19, 8], [8, 9, 8], [8, 10, 8], [8, 11, 8],
3169
+ [8, 12, 8], [8, 13, 8], [8, 14, 8], [8, 15, 8], [9, 16, 9],
3170
+ [9, 17, 9], [9, 18, 9], [9, 19, 9], [9, 10, 9], [9, 11, 9],
3171
+ [9, 12, 9], [9, 13, 9], [9, 14, 9], [9, 15, 9], [10, 16, 10],
3172
+ [10, 17, 10], [10, 18, 10], [10, 19, 10], [10, 11, 10],
3173
+ [10, 12, 10], [10, 13, 10], [10, 14, 10], [10, 15, 10],
3174
+ [11, 16, 11], [11, 17, 11], [11, 18, 11], [11, 19, 11],
3175
+ [11, 12, 11], [11, 13, 11], [11, 14, 11], [11, 15, 11],
3176
+ [12, 16, 12], [12, 17, 12], [12, 18, 12], [12, 19, 12],
3177
+ [12, 13, 12], [12, 14, 12], [12, 15, 12], [13, 16, 13],
3178
+ [13, 17, 13], [13, 18, 13], [13, 19, 13], [13, 14, 13],
3179
+ [13, 15, 13], [14, 16, 14], [14, 17, 14], [14, 18, 14],
3180
+ [14, 19, 14], [14, 15, 14], [15, 16, 15], [15, 17, 15],
3181
+ [15, 18, 15], [15, 19, 15], [16, 17, 16], [16, 18, 16],
3182
+ [16, 19, 16], [17, 18, 17], [17, 19, 17], [18, 19, 18]]
3183
+
3184
+ sage: g = graphs.CompleteGraph(20).to_directed()
3185
+ sage: g.all_simple_cycles(max_length=2, starting_vertices=[0])
3186
+ [[0, 16, 0], [0, 1, 0], [0, 17, 0], [0, 2, 0], [0, 18, 0],
3187
+ [0, 3, 0], [0, 19, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0],
3188
+ [0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 0],
3189
+ [0, 12, 0], [0, 13, 0], [0, 14, 0], [0, 15, 0]]
3190
+
3191
+ One may prefer to distinguish equivalent cycles having distinct starting
3192
+ vertices (compare the following examples)::
3193
+
3194
+ sage: g = graphs.CompleteGraph(4).to_directed()
3195
+ sage: g.all_simple_cycles(max_length=2, rooted=False)
3196
+ [[0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 2, 1], [1, 3, 1], [2, 3, 2]]
3197
+ sage: g.all_simple_cycles(max_length=2, rooted=True)
3198
+ [[0, 1, 0], [0, 2, 0], [0, 3, 0], [1, 0, 1], [1, 2, 1], [1, 3, 1],
3199
+ [2, 0, 2], [2, 1, 2], [2, 3, 2], [3, 0, 3], [3, 1, 3], [3, 2, 3]]
3200
+ """
3201
+ return list(self.all_cycles_iterator(starting_vertices=starting_vertices,
3202
+ simple=True, rooted=rooted,
3203
+ max_length=max_length, trivial=trivial))
3204
+
3205
+ def path_semigroup(self):
3206
+ """
3207
+ The partial semigroup formed by the paths of this quiver.
3208
+
3209
+ EXAMPLES::
3210
+
3211
+ sage: Q = DiGraph({1: {2: ['a', 'c']}, 2: {3: ['b']}})
3212
+ sage: F = Q.path_semigroup(); F # needs sage.libs.flint
3213
+ Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices
3214
+ sage: list(F) # needs sage.libs.flint
3215
+ [e_1, e_2, e_3, a, c, b, a*b, c*b]
3216
+ """
3217
+ from sage.quivers.path_semigroup import PathSemigroup
3218
+ return PathSemigroup(self)
3219
+
3220
+ def auslander_reiten_quiver(self):
3221
+ r"""
3222
+ Return the Auslander-Reiten quiver of ``self``.
3223
+
3224
+ .. SEEALSO::
3225
+
3226
+ :class:`~sage.quivers.ar_quiver.AuslanderReitenQuiver`
3227
+
3228
+ EXAMPLES::
3229
+
3230
+ sage: D = DiGraph([[1,2,'a'], [1,2,'b']], multiedges=True)
3231
+ sage: D.auslander_reiten_quiver()
3232
+ Auslander-Reiten quiver of Multi-digraph on 2 vertices
3233
+ """
3234
+ from sage.quivers.ar_quiver import AuslanderReitenQuiver
3235
+ return AuslanderReitenQuiver(self)
3236
+
3237
+ # Directed Acyclic Graphs (DAGs)
3238
+
3239
+ def topological_sort(self, implementation='default'):
3240
+ """
3241
+ Return a topological sort of the digraph if it is acyclic.
3242
+
3243
+ If the digraph contains a directed cycle, a :exc:`TypeError`
3244
+ is raised. As topological sorts are not necessarily unique,
3245
+ different implementations may yield different results.
3246
+
3247
+ A topological sort is an ordering of the vertices of the digraph such
3248
+ that each vertex comes before all of its successors. That is, if `u`
3249
+ comes before `v` in the sort, then there may be a directed path from `u`
3250
+ to `v`, but there will be no directed path from `v` to `u`.
3251
+
3252
+ INPUT:
3253
+
3254
+ - ``implementation`` -- string (default: ``'default'``); either use the
3255
+ default Cython implementation, or the default NetworkX library
3256
+ (``implementation = "NetworkX"``)
3257
+
3258
+ .. SEEALSO::
3259
+
3260
+ - :meth:`is_directed_acyclic` -- tests whether a directed graph is
3261
+ acyclic (can also join a certificate; a topological sort or a
3262
+ circuit in the graph)
3263
+
3264
+ EXAMPLES::
3265
+
3266
+ sage: D = DiGraph({0: [1, 2, 3], 4: [2, 5], 1: [8], 2: [7], 3: [7],
3267
+ ....: 5: [6, 7], 7: [8], 6: [9], 8: [10], 9: [10]})
3268
+ sage: D.plot(layout='circular').show() # needs sage.plot
3269
+ sage: D.topological_sort()
3270
+ [4, 5, 6, 9, 0, 1, 2, 3, 7, 8, 10]
3271
+
3272
+ ::
3273
+
3274
+ sage: D.add_edge(9, 7)
3275
+ sage: D.topological_sort()
3276
+ [4, 5, 6, 9, 0, 1, 2, 3, 7, 8, 10]
3277
+
3278
+ Using the NetworkX implementation ::
3279
+
3280
+ sage: s = list(D.topological_sort(implementation="NetworkX")); s # random # needs networkx
3281
+ [0, 4, 1, 3, 2, 5, 6, 9, 7, 8, 10]
3282
+ sage: all(s.index(u) < s.index(v) # needs networkx
3283
+ ....: for u, v in D.edges(sort=False, labels=False))
3284
+ True
3285
+
3286
+ ::
3287
+
3288
+ sage: D.add_edge(7, 4)
3289
+ sage: D.topological_sort()
3290
+ Traceback (most recent call last):
3291
+ ...
3292
+ TypeError: digraph is not acyclic; there is no topological sort
3293
+
3294
+ TESTS:
3295
+
3296
+ A wrong value for the ``implementation`` keyword::
3297
+
3298
+ sage: D.topological_sort(implementation = "cloud-reading")
3299
+ Traceback (most recent call last):
3300
+ ...
3301
+ ValueError: implementation must be set to one of "default" or "NetworkX"
3302
+ """
3303
+ if implementation == "default":
3304
+ b, ordering = self._backend.is_directed_acyclic(certificate=True)
3305
+ if b:
3306
+ return ordering
3307
+ else:
3308
+ raise TypeError('digraph is not acyclic; there is no topological sort')
3309
+
3310
+ elif implementation == "NetworkX":
3311
+ import networkx
3312
+ S = networkx.topological_sort(self.networkx_graph())
3313
+ if S is None:
3314
+ raise TypeError('digraph is not acyclic; there is no topological sort')
3315
+ else:
3316
+ return S
3317
+
3318
+ raise ValueError("implementation must be set to one of \"default\" or \"NetworkX\"")
3319
+
3320
+ def topological_sort_generator(self):
3321
+ """
3322
+ Return an iterator over all topological sorts of the digraph if
3323
+ it is acyclic.
3324
+
3325
+ If the digraph contains a directed cycle, a :exc:`TypeError`
3326
+ is raised.
3327
+
3328
+ A topological sort is an ordering of the vertices of the digraph such
3329
+ that each vertex comes before all of its successors. That is, if u comes
3330
+ before v in the sort, then there may be a directed path from u to v, but
3331
+ there will be no directed path from v to u. See also
3332
+ :meth:`topological_sort`.
3333
+
3334
+ AUTHORS:
3335
+
3336
+ - Mike Hansen - original implementation
3337
+
3338
+ - Robert L. Miller: wrapping, documentation
3339
+
3340
+ REFERENCE:
3341
+
3342
+ - [1] Pruesse, Gara and Ruskey, Frank. Generating Linear
3343
+ Extensions Fast. SIAM J. Comput., Vol. 23 (1994), no. 2, pp.
3344
+ 373-386.
3345
+
3346
+ EXAMPLES::
3347
+
3348
+ sage: D = DiGraph({0: [1, 2], 1: [3], 2: [3, 4]})
3349
+ sage: D.plot(layout='circular').show() # needs sage.plot
3350
+ sage: list(D.topological_sort_generator()) # needs sage.modules sage.rings.finite_rings
3351
+ [[0, 1, 2, 3, 4], [0, 2, 1, 3, 4], [0, 2, 1, 4, 3],
3352
+ [0, 2, 4, 1, 3], [0, 1, 2, 4, 3]]
3353
+
3354
+ ::
3355
+
3356
+ sage: for sort in D.topological_sort_generator(): # needs sage.modules sage.rings.finite_rings
3357
+ ....: for u, v in D.edge_iterator(labels=False):
3358
+ ....: if sort.index(u) > sort.index(v):
3359
+ ....: print("this should never happen")
3360
+ """
3361
+ from sage.combinat.posets.posets import Poset
3362
+ return Poset(self).linear_extensions()
3363
+
3364
+ # Visualization
3365
+
3366
+ def layout_acyclic(self, rankdir='up', **options):
3367
+ """
3368
+ Return a ranked layout so that all edges point upward.
3369
+
3370
+ To this end, the heights of the vertices are set according to the level
3371
+ set decomposition of the graph (see :meth:`.level_sets`).
3372
+
3373
+ This is achieved by calling ``graphviz`` and ``dot2tex`` if available
3374
+ (see :meth:`.layout_graphviz`), and using a spring layout with fixed
3375
+ vertical placement of the vertices otherwise (see
3376
+ :meth:`.layout_acyclic_dummy` and
3377
+ :meth:`~sage.graphs.generic_graph.GenericGraph.layout_ranked`).
3378
+
3379
+ Non acyclic graphs are partially supported by ``graphviz``, which then
3380
+ chooses some edges to point down.
3381
+
3382
+ INPUT:
3383
+
3384
+ - ``rankdir`` -- string (default: ``'up'``); indicates which direction
3385
+ the edges should point toward among ``'up'``, ``'down'``, ``'left'``,
3386
+ or ``'right'``
3387
+
3388
+ - ``**options`` -- passed down to
3389
+ :meth:`~sage.graphs.generic_graph.GenericGraph.layout_ranked` or
3390
+ :meth:`~sage.graphs.generic_graph.GenericGraph.layout_graphviz`
3391
+
3392
+ EXAMPLES::
3393
+
3394
+ sage: H = DiGraph({0: [1, 2], 1: [3], 2: [3], 3: [], 5: [1, 6], 6: [2, 3]})
3395
+
3396
+ The actual layout computed depends on whether dot2tex and graphviz are
3397
+ installed, so we don't test its relative values::
3398
+
3399
+ sage: H.layout_acyclic()
3400
+ {0: [..., ...], 1: [..., ...], 2: [..., ...], 3: [..., ...], 5: [..., ...], 6: [..., ...]}
3401
+
3402
+ sage: H = DiGraph({0: [1]})
3403
+ sage: pos = H.layout_acyclic(rankdir='up')
3404
+ sage: pos[1][1] > pos[0][1] + .5
3405
+ True
3406
+ sage: pos = H.layout_acyclic(rankdir='down')
3407
+ sage: pos[1][1] < pos[0][1] - .5
3408
+ True
3409
+ sage: pos = H.layout_acyclic(rankdir='right')
3410
+ sage: pos[1][0] > pos[0][0] + .5
3411
+ True
3412
+ sage: pos = H.layout_acyclic(rankdir='left')
3413
+ sage: pos[1][0] < pos[0][0] - .5
3414
+ True
3415
+ """
3416
+ if have_dot2tex():
3417
+ return self.layout_graphviz(rankdir=rankdir, **options)
3418
+ return self.layout_acyclic_dummy(rankdir=rankdir, **options)
3419
+
3420
+ def layout_acyclic_dummy(self, heights=None, rankdir='up', **options):
3421
+ """
3422
+ Return a ranked layout so that all edges point upward.
3423
+
3424
+ To this end, the heights of the vertices are set according to the level
3425
+ set decomposition of the graph (see :meth:`level_sets`). This is
3426
+ achieved by a spring layout with fixed vertical placement of the
3427
+ vertices otherwise (see :meth:`layout_acyclic_dummy` and
3428
+ :meth:`~sage.graphs.generic_graph.GenericGraph.layout_ranked`).
3429
+
3430
+ INPUT:
3431
+
3432
+ - ``rankdir`` -- string (default: ``'up'``); indicates which direction
3433
+ the edges should point toward among ``'up'``, ``'down'``, ``'left'``,
3434
+ or ``'right'``
3435
+
3436
+ - ``**options`` -- passed down to
3437
+ :meth:`~sage.graphs.generic_graph.GenericGraph.layout_ranked`
3438
+
3439
+ EXAMPLES::
3440
+
3441
+ sage: H = DiGraph({0: [1, 2], 1: [3], 2: [3], 3: [], 5: [1, 6], 6: [2, 3]})
3442
+ sage: H.layout_acyclic_dummy()
3443
+ {0: [1.0..., 0], 1: [1.0..., 1], 2: [1.5..., 2], 3: [1.5..., 3], 5: [2.0..., 0], 6: [2.0..., 1]}
3444
+
3445
+ sage: H = DiGraph({0: [1]})
3446
+ sage: H.layout_acyclic_dummy(rankdir='up')
3447
+ {0: [0.5..., 0], 1: [0.5..., 1]}
3448
+ sage: H.layout_acyclic_dummy(rankdir='down')
3449
+ {0: [0.5..., 1], 1: [0.5..., 0]}
3450
+ sage: H.layout_acyclic_dummy(rankdir='left')
3451
+ {0: [1, 0.5...], 1: [0, 0.5...]}
3452
+ sage: H.layout_acyclic_dummy(rankdir='right')
3453
+ {0: [0, 0.5...], 1: [1, 0.5...]}
3454
+ sage: H = DiGraph({0: [1, 2], 1: [3], 2: [3], 3: [1], 5: [1, 6], 6: [2, 3]})
3455
+ sage: H.layout_acyclic_dummy()
3456
+ Traceback (most recent call last):
3457
+ ...
3458
+ ValueError: `self` should be an acyclic graph
3459
+
3460
+ TESTS:
3461
+
3462
+ :issue:`31681` is fixed::
3463
+
3464
+ sage: H = DiGraph({0: [1], 'X': [1]}, format='dict_of_lists')
3465
+ sage: pos = H.layout_acyclic_dummy(rankdir='up')
3466
+ sage: pos['X'][1] == 0 and pos[0][1] == 0
3467
+ True
3468
+ sage: pos[1][1] == 1
3469
+ True
3470
+ """
3471
+ if heights is None:
3472
+ if not self.is_directed_acyclic():
3473
+ raise ValueError("`self` should be an acyclic graph")
3474
+ levels = self.level_sets()
3475
+ # Sort vertices in each level in best effort mode
3476
+ for i in range(len(levels)):
3477
+ try:
3478
+ label = sorted(levels[i])
3479
+ levels[i] = label
3480
+ except TypeError:
3481
+ continue
3482
+ if rankdir == 'down' or rankdir == 'left':
3483
+ levels.reverse()
3484
+ heights = {i: levels[i] for i in range(len(levels))}
3485
+ positions = self.layout_ranked(heights=heights, **options)
3486
+ if rankdir == 'left' or rankdir == 'right':
3487
+ for coordinates in positions.values():
3488
+ coordinates.reverse()
3489
+ return positions
3490
+
3491
+ def level_sets(self):
3492
+ r"""
3493
+ Return the level set decomposition of the digraph.
3494
+
3495
+ OUTPUT: list of non empty lists of vertices of this graph
3496
+
3497
+ The level set decomposition of the digraph is a list `l` such that the
3498
+ level `l[i]` contains all the vertices having all their predecessors in
3499
+ the levels `l[j]` for `j < i`, and at least one in level `l[i-1]`
3500
+ (unless `i = 0`).
3501
+
3502
+ The level decomposition contains exactly the vertices not occurring in
3503
+ any cycle of the graph. In particular, the graph is acyclic if and only
3504
+ if the decomposition forms a set partition of its vertices, and we
3505
+ recover the usual level set decomposition of the corresponding poset.
3506
+
3507
+ EXAMPLES::
3508
+
3509
+ sage: H = DiGraph({0: [1, 2], 1: [3], 2: [3], 3: [], 5: [1, 6], 6: [2, 3]})
3510
+ sage: H.level_sets()
3511
+ [[0, 5], [1, 6], [2], [3]]
3512
+
3513
+ sage: H = DiGraph({0: [1, 2], 1: [3], 2: [3], 3: [1], 5: [1, 6], 6: [2, 3]})
3514
+ sage: H.level_sets()
3515
+ [[0, 5], [6], [2]]
3516
+
3517
+ This routine is mostly used for Hasse diagrams of posets::
3518
+
3519
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3520
+ sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [3], 3: []})
3521
+ sage: [len(x) for x in H.level_sets()]
3522
+ [1, 2, 1]
3523
+
3524
+ ::
3525
+
3526
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3527
+ sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [4], 3: [4]})
3528
+ sage: [len(x) for x in H.level_sets()]
3529
+ [1, 2, 1, 1]
3530
+
3531
+ Complexity: `O(n+m)` in time and `O(n)` in memory (besides the storage
3532
+ of the graph itself), where `n` and `m` are respectively the number of
3533
+ vertices and edges (assuming that appending to a list is constant time,
3534
+ which it is not quite).
3535
+ """
3536
+ in_degrees = self.in_degree(labels=True)
3537
+ level = [x for x in in_degrees if not in_degrees[x]]
3538
+ Levels = []
3539
+ while level:
3540
+ Levels.append(level)
3541
+ new_level = []
3542
+ for x in level:
3543
+ for y in self.neighbor_out_iterator(x):
3544
+ in_degrees[y] -= 1
3545
+ if not in_degrees[y]:
3546
+ new_level.append(y)
3547
+ level = new_level
3548
+ return Levels
3549
+
3550
+ def is_aperiodic(self):
3551
+ r"""
3552
+ Return whether the current ``DiGraph`` is aperiodic.
3553
+
3554
+ A directed graph is aperiodic if there is no integer `k > 1` that
3555
+ divides the length of every cycle in the graph. See the
3556
+ :wikipedia:`Aperiodic_graph` for more information.
3557
+
3558
+ EXAMPLES:
3559
+
3560
+ The following graph has period ``2``, so it is not aperiodic::
3561
+
3562
+ sage: g = DiGraph({0: [1], 1: [0]})
3563
+ sage: g.is_aperiodic()
3564
+ False
3565
+
3566
+ The following graph has a cycle of length 2 and a cycle of length 3,
3567
+ so it is aperiodic::
3568
+
3569
+ sage: g = DiGraph({0: [1, 4], 1: [2], 2: [0], 4: [0]})
3570
+ sage: g.is_aperiodic()
3571
+ True
3572
+
3573
+ .. SEEALSO::
3574
+
3575
+ :meth:`period`
3576
+ """
3577
+ return self.period() == 1
3578
+
3579
+ def period(self):
3580
+ r"""
3581
+ Return the period of the current ``DiGraph``.
3582
+
3583
+ The period of a directed graph is the largest integer that divides the
3584
+ length of every cycle in the graph. See the :wikipedia:`Aperiodic_graph`
3585
+ for more information.
3586
+
3587
+ EXAMPLES:
3588
+
3589
+ The following graph has period ``2``::
3590
+
3591
+ sage: g = DiGraph({0: [1], 1: [0]})
3592
+ sage: g.period()
3593
+ 2
3594
+
3595
+ The following graph has a cycle of length 2 and a cycle of length 3,
3596
+ so it has period ``1``::
3597
+
3598
+ sage: g = DiGraph({0: [1, 4], 1: [2], 2: [0], 4: [0]})
3599
+ sage: g.period()
3600
+ 1
3601
+
3602
+ Here is an example of computing the period of a digraph which is not
3603
+ strongly connected. By definition, it is the :func:`gcd` of the periods
3604
+ of its strongly connected components::
3605
+
3606
+ sage: g = DiGraph({-1: [-2], -2: [-3], -3: [-1],
3607
+ ....: 1: [2], 2: [1]})
3608
+ sage: g.period()
3609
+ 1
3610
+ sage: sorted([s.period() for s
3611
+ ....: in g.strongly_connected_components_subgraphs()])
3612
+ [2, 3]
3613
+
3614
+ ALGORITHM:
3615
+
3616
+ See the networkX implementation of ``is_aperiodic``, that is based on
3617
+ breadth first search.
3618
+
3619
+ .. SEEALSO::
3620
+
3621
+ :meth:`is_aperiodic`
3622
+ """
3623
+ from sage.arith.misc import GCD as gcd
3624
+
3625
+ g = 0
3626
+
3627
+ for component in self.strongly_connected_components():
3628
+ levels = {s: None for s in component}
3629
+ vertices_in_scc = levels # considers level as a set
3630
+ s = component[0]
3631
+ levels[s] = 0
3632
+ this_level = [s]
3633
+ idx = 1
3634
+ while this_level:
3635
+ next_level = []
3636
+ for u in this_level:
3637
+ # we have levels[u] == idx - 1
3638
+ for v in self.neighbor_out_iterator(u):
3639
+ # ignore edges leaving the component
3640
+ if v not in vertices_in_scc:
3641
+ continue
3642
+ level_v = levels[v]
3643
+ if level_v is not None: # Non-Tree Edge
3644
+ g = gcd(g, idx - level_v)
3645
+ if g == 1:
3646
+ return 1
3647
+ else: # Tree Edge
3648
+ next_level.append(v)
3649
+ levels[v] = idx
3650
+ this_level = next_level
3651
+ idx += 1
3652
+
3653
+ return g
3654
+
3655
+ def flow_polytope(self, edges=None, ends=None, backend=None):
3656
+ r"""
3657
+ Return the flow polytope of a digraph.
3658
+
3659
+ The flow polytope of a directed graph is the polytope consisting of all
3660
+ nonnegative flows on the graph with a given set `S` of sources and a
3661
+ given set `T` of sinks.
3662
+
3663
+ A *flow* on a directed graph `G` with a given set `S` of sources and a
3664
+ given set `T` of sinks means an assignment of a nonnegative real to each
3665
+ edge of `G` such that the flow is conserved in each vertex outside of
3666
+ `S` and `T`, and there is a unit of flow entering each vertex in `S` and
3667
+ a unit of flow leaving each vertex in `T`. These flows clearly form a
3668
+ polytope in the space of all assignments of reals to the edges of `G`.
3669
+
3670
+ The polytope is empty unless the sets `S` and `T` are equinumerous.
3671
+
3672
+ By default, `S` is taken to be the set of all sources (i.e., vertices of
3673
+ indegree `0`) of `G`, and `T` is taken to be the set of all sinks (i.e.,
3674
+ vertices of outdegree `0`) of `G`. If a different choice of `S` and `T`
3675
+ is desired, it can be specified using the optional ``ends`` parameter.
3676
+
3677
+ The polytope is returned as a polytope in `\RR^m`, where `m` is the
3678
+ number of edges of the digraph ``self``. The `k`-th coordinate of a
3679
+ point in the polytope is the real assigned to the `k`-th edge of
3680
+ ``self``. The order of the edges is the one returned by
3681
+ ``self.edges(sort=True)``. If a different order is desired, it can be specified
3682
+ using the optional ``edges`` parameter.
3683
+
3684
+ The faces and volume of these polytopes are of interest. Examples of
3685
+ these polytopes are the Chan-Robbins-Yuen polytope and the
3686
+ Pitman-Stanley polytope [PS2002]_.
3687
+
3688
+ INPUT:
3689
+
3690
+ - ``edges`` -- list (default: ``None``); a list of edges of ``self``. If
3691
+ not specified, the list of all edges of ``self`` is used with the
3692
+ default ordering of ``self.edges(sort=True)``. This determines which coordinate
3693
+ of a point in the polytope will correspond to which edge of
3694
+ ``self``. It is also possible to specify a list which contains not all
3695
+ edges of ``self``; this results in a polytope corresponding to the
3696
+ flows which are `0` on all remaining edges. Notice that the edges
3697
+ entered here must be in the precisely same format as outputted by
3698
+ ``self.edges()``; so, if ``self.edges()`` outputs an edge in the form
3699
+ ``(1, 3, None)``, then ``(1, 3)`` will not do!
3700
+
3701
+ - ``ends`` -- (default: ``(self.sources(), self.sinks())``) a
3702
+ pair `(S, T)` of an iterable `S` and an iterable `T`
3703
+
3704
+ - ``backend`` -- string or ``None`` (default); the backend to use;
3705
+ see :meth:`sage.geometry.polyhedron.constructor.Polyhedron`
3706
+
3707
+ .. NOTE::
3708
+
3709
+ Flow polytopes can also be built through the ``polytopes.<tab>``
3710
+ object::
3711
+
3712
+ sage: polytopes.flow_polytope(digraphs.Path(5)) # needs sage.geometry.polyhedron
3713
+ A 0-dimensional polyhedron in QQ^4 defined as the convex hull of 1 vertex
3714
+
3715
+ EXAMPLES:
3716
+
3717
+ A commutative square::
3718
+
3719
+ sage: G = DiGraph({1: [2, 3], 2: [4], 3: [4]})
3720
+ sage: fl = G.flow_polytope(); fl # needs sage.geometry.polyhedron
3721
+ A 1-dimensional polyhedron in QQ^4 defined as the convex hull
3722
+ of 2 vertices
3723
+ sage: fl.vertices() # needs sage.geometry.polyhedron
3724
+ (A vertex at (0, 1, 0, 1), A vertex at (1, 0, 1, 0))
3725
+
3726
+ Using a different order for the edges of the graph::
3727
+
3728
+ sage: ordered_edges = G.edges(sort=True, key=lambda x: x[0] - x[1])
3729
+ sage: fl = G.flow_polytope(edges=ordered_edges); fl # needs sage.geometry.polyhedron
3730
+ A 1-dimensional polyhedron in QQ^4 defined as the convex hull of 2 vertices
3731
+ sage: fl.vertices() # needs sage.geometry.polyhedron
3732
+ (A vertex at (0, 1, 1, 0), A vertex at (1, 0, 0, 1))
3733
+
3734
+ A tournament on 4 vertices::
3735
+
3736
+ sage: H = digraphs.TransitiveTournament(4)
3737
+ sage: fl = H.flow_polytope(); fl # needs sage.geometry.polyhedron
3738
+ A 3-dimensional polyhedron in QQ^6 defined as the convex hull
3739
+ of 4 vertices
3740
+ sage: fl.vertices() # needs sage.geometry.polyhedron
3741
+ (A vertex at (0, 0, 1, 0, 0, 0),
3742
+ A vertex at (0, 1, 0, 0, 0, 1),
3743
+ A vertex at (1, 0, 0, 0, 1, 0),
3744
+ A vertex at (1, 0, 0, 1, 0, 1))
3745
+
3746
+ Restricting to a subset of the edges::
3747
+
3748
+ sage: fl = H.flow_polytope(edges=[(0, 1, None), (1, 2, None), # needs sage.geometry.polyhedron
3749
+ ....: (2, 3, None), (0, 3, None)]); fl
3750
+ A 1-dimensional polyhedron in QQ^4 defined as the convex hull
3751
+ of 2 vertices
3752
+ sage: fl.vertices() # needs sage.geometry.polyhedron
3753
+ (A vertex at (0, 0, 0, 1), A vertex at (1, 1, 1, 0))
3754
+
3755
+ Using a different choice of sources and sinks::
3756
+
3757
+ sage: # needs sage.geometry.polyhedron
3758
+ sage: fl = H.flow_polytope(ends=([1], [3])); fl
3759
+ A 1-dimensional polyhedron in QQ^6 defined as the convex hull
3760
+ of 2 vertices
3761
+ sage: fl.vertices()
3762
+ (A vertex at (0, 0, 0, 1, 0, 1), A vertex at (0, 0, 0, 0, 1, 0))
3763
+ sage: fl = H.flow_polytope(ends=([0, 1], [3])); fl
3764
+ The empty polyhedron in QQ^6
3765
+ sage: fl = H.flow_polytope(ends=([3], [0])); fl
3766
+ The empty polyhedron in QQ^6
3767
+ sage: fl = H.flow_polytope(ends=([0, 1], [2, 3])); fl
3768
+ A 3-dimensional polyhedron in QQ^6 defined as the convex hull
3769
+ of 5 vertices
3770
+ sage: fl.vertices()
3771
+ (A vertex at (0, 0, 1, 1, 0, 0),
3772
+ A vertex at (0, 1, 0, 0, 1, 0),
3773
+ A vertex at (1, 0, 0, 2, 0, 1),
3774
+ A vertex at (1, 0, 0, 1, 1, 0),
3775
+ A vertex at (0, 1, 0, 1, 0, 1))
3776
+ sage: fl = H.flow_polytope(edges=[(0, 1, None), (1, 2, None),
3777
+ ....: (2, 3, None), (0, 2, None),
3778
+ ....: (1, 3, None)],
3779
+ ....: ends=([0, 1], [2, 3])); fl
3780
+ A 2-dimensional polyhedron in QQ^5 defined as the convex hull
3781
+ of 4 vertices
3782
+ sage: fl.vertices()
3783
+ (A vertex at (0, 0, 0, 1, 1),
3784
+ A vertex at (1, 2, 1, 0, 0),
3785
+ A vertex at (1, 1, 0, 0, 1),
3786
+ A vertex at (0, 1, 1, 1, 0))
3787
+
3788
+ A digraph with one source and two sinks::
3789
+
3790
+ sage: Y = DiGraph({1: [2], 2: [3, 4]})
3791
+ sage: Y.flow_polytope() # needs sage.geometry.polyhedron
3792
+ The empty polyhedron in QQ^3
3793
+
3794
+ A digraph with one vertex and no edge::
3795
+
3796
+ sage: Z = DiGraph({1: []})
3797
+ sage: Z.flow_polytope() # needs sage.geometry.polyhedron
3798
+ A 0-dimensional polyhedron in QQ^0 defined as the convex hull
3799
+ of 1 vertex
3800
+
3801
+ A digraph with multiple edges (:issue:`28837`)::
3802
+
3803
+ sage: G = DiGraph([(0, 1), (0,1)], multiedges=True); G
3804
+ Multi-digraph on 2 vertices
3805
+ sage: P = G.flow_polytope(); P # needs sage.geometry.polyhedron
3806
+ A 1-dimensional polyhedron in QQ^2 defined as the convex hull of 2 vertices
3807
+ sage: P.vertices() # needs sage.geometry.polyhedron
3808
+ (A vertex at (1, 0), A vertex at (0, 1))
3809
+ sage: P.lines() # needs sage.geometry.polyhedron
3810
+ ()
3811
+ """
3812
+ from sage.geometry.polyhedron.constructor import Polyhedron
3813
+ if edges is None:
3814
+ edges = self.edges(sort=False)
3815
+ m = len(edges)
3816
+ ineqs = [[0] * (i + 1) + [1] + [0] * (m - i - 1) for i in range(m)]
3817
+
3818
+ eqs = []
3819
+ for u in self:
3820
+ ins = set(self.incoming_edge_iterator(u))
3821
+ outs = set(self.outgoing_edge_iterator(u))
3822
+ eq = [Integer(j in ins) - Integer(j in outs) for j in edges]
3823
+
3824
+ const = 0
3825
+ if ends is None:
3826
+ if not ins: # sources (indegree 0)
3827
+ const += 1
3828
+ if not outs: # sinks (outdegree 0)
3829
+ const -= 1
3830
+ else:
3831
+ if u in ends[0]: # chosen sources
3832
+ const += 1
3833
+ if u in ends[1]: # chosen sinks
3834
+ const -= 1
3835
+
3836
+ eq = [const] + eq
3837
+ eqs.append(eq)
3838
+
3839
+ return Polyhedron(ieqs=ineqs, eqns=eqs, backend=backend)
3840
+
3841
+ def is_tournament(self):
3842
+ r"""
3843
+ Check whether the digraph is a tournament.
3844
+
3845
+ A tournament is a digraph in which each pair of distinct vertices is
3846
+ connected by a single arc.
3847
+
3848
+ EXAMPLES::
3849
+
3850
+ sage: g = digraphs.RandomTournament(6)
3851
+ sage: g.is_tournament()
3852
+ True
3853
+ sage: u,v = next(g.edge_iterator(labels=False))
3854
+ sage: g.add_edge(v, u)
3855
+ sage: g.is_tournament()
3856
+ False
3857
+ sage: g.add_edges([(u, v), (v, u)])
3858
+ sage: g.is_tournament()
3859
+ False
3860
+
3861
+ .. SEEALSO::
3862
+
3863
+ - :wikipedia:`Tournament_(graph_theory)`
3864
+ - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.RandomTournament`
3865
+ - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.TransitiveTournament`
3866
+ """
3867
+ self._scream_if_not_simple()
3868
+
3869
+ if self.size() != self.order() * (self.order() - 1) // 2:
3870
+ return False
3871
+
3872
+ import itertools
3873
+ return not any(self.has_edge(u, v) == self.has_edge(v, u)
3874
+ for u, v in itertools.combinations(self, 2))
3875
+
3876
+ def _girth_bfs(self, odd=False, certificate=False):
3877
+ r"""
3878
+ Return the girth of the digraph using breadth-first search.
3879
+
3880
+ Loops are ignored, so the returned value is at least 2.
3881
+
3882
+ INPUT:
3883
+
3884
+ - ``odd`` -- boolean (default: ``False``); whether to compute the odd
3885
+ girth
3886
+
3887
+ - ``certificate`` -- boolean (default: ``False``); whether to return
3888
+ ``(g, c)``, where ``g`` is the (odd) girth and ``c`` is a list
3889
+ of vertices of a directed cycle of length ``g`` in the graph,
3890
+ thus providing a certificate that the (odd) girth is at most ``g``,
3891
+ or ``None`` if ``g`` is infinite
3892
+
3893
+ EXAMPLES:
3894
+
3895
+ A digraph with girth 4 and odd girth 5::
3896
+
3897
+ sage: G = DiGraph([(0, 1), (1, 2), (1, 3), (2, 3), (3, 4), (4, 0)])
3898
+ sage: G._girth_bfs(certificate=True) # random
3899
+ (4, [1, 3, 4, 0])
3900
+ sage: G._girth_bfs(odd=True)
3901
+ 5
3902
+
3903
+ .. SEEALSO::
3904
+
3905
+ * :meth:`~sage.graphs.GenericGraph.girth` -- return the girth of the
3906
+ graph
3907
+ * :meth:`~sage.graphs.GenericGraph.odd_girth` -- return the odd
3908
+ girth of the graph
3909
+ """
3910
+ n = self.num_verts()
3911
+ best = n + 1
3912
+ seen = set()
3913
+ for w in self:
3914
+ seen.add(w)
3915
+ inSpan, outSpan = {w: None}, {w: None}
3916
+ depth = 1
3917
+ outList, inList = set([w]), set([w])
3918
+ while 2 * depth <= best:
3919
+ nextOutList, nextInList = set(), set()
3920
+ for v in outList:
3921
+ for u in self.neighbor_out_iterator(v):
3922
+ if u in seen:
3923
+ continue
3924
+ if u not in outSpan:
3925
+ outSpan[u] = v
3926
+ nextOutList.add(u)
3927
+ if u in inList:
3928
+ best = depth * 2 - 1
3929
+ ends = (v, u)
3930
+ bestSpans = (outSpan, inSpan)
3931
+ break
3932
+ if best == 2 * depth - 1:
3933
+ break
3934
+ if best == 2 * depth - 1:
3935
+ break
3936
+ for v in inList:
3937
+ for u in self.neighbor_in_iterator(v):
3938
+ if u in seen:
3939
+ continue
3940
+ if u not in inSpan:
3941
+ inSpan[u] = v
3942
+ nextInList.add(u)
3943
+ if not odd and u in nextOutList:
3944
+ best = depth * 2
3945
+ ends = (u, v)
3946
+ bestSpans = (outSpan, inSpan)
3947
+ break
3948
+ if best == 2 * depth:
3949
+ break
3950
+ if best <= 2:
3951
+ break
3952
+ outList = nextOutList
3953
+ inList = nextInList
3954
+ depth += 1
3955
+ if best == n + 1:
3956
+ from sage.rings.infinity import Infinity
3957
+ return (Infinity, None) if certificate else Infinity
3958
+ if certificate:
3959
+ cycles = {}
3960
+ for x, span in zip(ends, bestSpans):
3961
+ cycles[x] = []
3962
+ y = x
3963
+ while span[y] is not None:
3964
+ cycles[x].append(y)
3965
+ y = span[y]
3966
+ cycles[x].append(y)
3967
+ u, v = ends
3968
+ return (best, list(reversed(cycles[u])) + cycles[v])
3969
+ return best
3970
+
3971
+ def out_branchings(self, source, spanning=True):
3972
+ r"""
3973
+ Return an iterator over the out branchings rooted at given vertex in
3974
+ ``self``.
3975
+
3976
+ An out-branching is a directed tree rooted at ``source`` whose arcs are
3977
+ directed from source to leaves. An out-branching is spanning if it
3978
+ contains all vertices of the digraph.
3979
+
3980
+ If no spanning out branching rooted at ``source`` exist, raises
3981
+ :exc:`ValueError` or return non spanning out branching rooted at
3982
+ ``source``, depending on the value of ``spanning``.
3983
+
3984
+ INPUT:
3985
+
3986
+ - ``source`` -- vertex used as the source for all out branchings
3987
+
3988
+ - ``spanning`` -- boolean (default: ``True``); if ``False`` return
3989
+ maximum out branching from ``source``. Otherwise, return spanning out
3990
+ branching if exists.
3991
+
3992
+ OUTPUT: an iterator over the out branchings rooted in the given source
3993
+
3994
+ .. SEEALSO::
3995
+
3996
+ - :meth:`~sage.graphs.digraph.DiGraph.in_branchings`
3997
+ -- iterator over in-branchings rooted at given vertex.
3998
+ - :meth:`~sage.graphs.graph.Graph.spanning_trees`
3999
+ -- returns all spanning trees.
4000
+ - :meth:`~sage.graphs.generic_graph.GenericGraph.spanning_trees_count`
4001
+ -- counts the number of spanning trees.
4002
+
4003
+ ALGORITHM:
4004
+
4005
+ Recursively computes all out branchings.
4006
+
4007
+ At each step:
4008
+
4009
+ 0. clean the graph (see below)
4010
+ 1. pick an edge e out of source
4011
+ 2. find all out branchings that do not contain e by first
4012
+ removing it
4013
+ 3. find all out branchings that do contain e by first
4014
+ merging the end vertices of e
4015
+
4016
+ Cleaning the graph implies to remove loops and replace multiedges by a
4017
+ single one with an appropriate label since these lead to similar steps
4018
+ of computation.
4019
+
4020
+ EXAMPLES:
4021
+
4022
+ A bidirectional 4-cycle::
4023
+
4024
+ sage: G = DiGraph({1:[2,3], 2:[1,4], 3:[1,4], 4:[2,3]}, format='dict_of_lists')
4025
+ sage: list(G.out_branchings(1))
4026
+ [Digraph on 4 vertices,
4027
+ Digraph on 4 vertices,
4028
+ Digraph on 4 vertices,
4029
+ Digraph on 4 vertices]
4030
+
4031
+ With the Petersen graph turned into a symmetric directed graph::
4032
+
4033
+ sage: G = graphs.PetersenGraph().to_directed()
4034
+ sage: len(list(G.out_branchings(0)))
4035
+ 2000
4036
+
4037
+ With a non connected ``DiGraph`` and ``spanning = True``::
4038
+
4039
+ sage: G = graphs.PetersenGraph().to_directed() + graphs.PetersenGraph().to_directed()
4040
+ sage: G.out_branchings(0, spanning=True)
4041
+ Traceback (most recent call last):
4042
+ ...
4043
+ ValueError: no spanning out branching from vertex (0) exist
4044
+
4045
+ With a non connected ``DiGraph`` and ``spanning = False``::
4046
+
4047
+ sage: g=DiGraph([(0,1), (0,1), (1,2), (3,4)],multiedges=True)
4048
+ sage: list(g.out_branchings(0, spanning=False))
4049
+ [Digraph on 3 vertices, Digraph on 3 vertices]
4050
+
4051
+ With multiedges::
4052
+
4053
+ sage: G = DiGraph({0:[1,1,1], 1:[2,2]}, format='dict_of_lists', multiedges=True)
4054
+ sage: len(list(G.out_branchings(0)))
4055
+ 6
4056
+
4057
+ With a DiGraph already being a spanning out branching::
4058
+
4059
+ sage: G = DiGraph({0:[1,2], 1:[3,4], 2:[5], 3:[], 4:[], 5:[]}, format='dict_of_lists')
4060
+ sage: next(G.out_branchings(0)) == G
4061
+ True
4062
+
4063
+ TESTS:
4064
+
4065
+ The empty ``DiGraph``::
4066
+
4067
+ sage: G = DiGraph()
4068
+ sage: G.out_branchings(0)
4069
+ Traceback (most recent call last):
4070
+ ...
4071
+ ValueError: vertex (0) is not a vertex of the digraph
4072
+
4073
+ sage: edges = [(0,0,'x'), (0,0,'y')]
4074
+ sage: G = DiGraph(edges, multiedges=True, loops=True, weighted=True)
4075
+ sage: list(G.out_branchings(0))
4076
+ [Digraph on 1 vertex]
4077
+
4078
+ sage: edges = [(0,1,'x'), (0,1,'y'), (1,2,'z'), (2,0,'w')]
4079
+ sage: G = DiGraph(edges, multiedges=True, loops=True, weighted=True)
4080
+ sage: len(list(G.out_branchings(0)))
4081
+ 2
4082
+ """
4083
+ def _rec_out_branchings(depth):
4084
+ r"""
4085
+ The recursive function used to enumerate out branchings.
4086
+
4087
+ This function makes use of the following to keep track of partial
4088
+ out branchings:
4089
+
4090
+ - ``list_edges`` -- list of edges in self
4091
+ - ``list_merged_edges`` -- list of edges that are currently merged
4092
+ - ``graph`` -- a copy of self where edges have an appropriate label
4093
+ """
4094
+ if not depth:
4095
+ # We have enough merged edges to form a out_branching
4096
+ # We iterate over the lists of labels in list_merged_edges and
4097
+ # yield the corresponding out_branchings
4098
+ for indexes in product(*list_merged_edges):
4099
+ yield DiGraph([list_edges[index] for index in indexes],
4100
+ format='list_of_edges', pos=self.get_pos())
4101
+
4102
+ # 1) Clean the graph
4103
+ # delete loops on source if any
4104
+ D.delete_edges(D.incoming_edge_iterator(source))
4105
+
4106
+ # merge multi-edges if any by concatenating their labels
4107
+ if D.has_multiple_edges():
4108
+ merged_multiple_edges = {}
4109
+ for u, v, label in D.multiple_edges():
4110
+ D.delete_edge(u, v, label)
4111
+ if (u, v) not in merged_multiple_edges:
4112
+ merged_multiple_edges[(u, v)] = label
4113
+ else:
4114
+ merged_multiple_edges[(u, v)] += label
4115
+ D.add_edges([(u, v, label) for (u, v), label in merged_multiple_edges.items()])
4116
+
4117
+ # 2) Pick an edge e outgoing from the source
4118
+ try:
4119
+ s, x, label = next(D.outgoing_edge_iterator(source))
4120
+ except StopIteration:
4121
+ return
4122
+ # 3) Find all out_branchings that do not contain e
4123
+ # by first removing it
4124
+ D.delete_edge(s, x, label)
4125
+ if len(list(D.depth_first_search(source))) == depth + 1:
4126
+ for out_branch in _rec_out_branchings(depth):
4127
+ yield out_branch
4128
+ D.add_edge(s, x, label)
4129
+
4130
+ # 4) Find all out_branchings that do contain e by merging
4131
+ # the end vertices of e
4132
+ # store different edges to unmerged the end vertices of e
4133
+ saved_edges = D.outgoing_edges(source)
4134
+ saved_edges.remove((s, x, label))
4135
+ saved_edges += D.outgoing_edges(x)
4136
+ saved_edges += D.incoming_edges(x)
4137
+
4138
+ D.merge_vertices((source, x))
4139
+
4140
+ list_merged_edges.add(label)
4141
+
4142
+ for out_branch in _rec_out_branchings(depth - 1):
4143
+ yield out_branch
4144
+
4145
+ list_merged_edges.remove(label)
4146
+
4147
+ # unmerge the end vertices of e
4148
+ D.delete_vertex(source)
4149
+ D.add_edges(saved_edges)
4150
+
4151
+ def _singleton_out_branching():
4152
+ r"""
4153
+ Return a DiGraph containing only ``source`` and no edges.
4154
+ """
4155
+ D = DiGraph()
4156
+ D.add_vertex(source)
4157
+ yield D
4158
+
4159
+ if not self.has_vertex(source):
4160
+ raise ValueError("vertex ({0}) is not a vertex of the digraph".format(source))
4161
+
4162
+ # check if self.order == 1
4163
+ if self.order() == 1:
4164
+ return _singleton_out_branching()
4165
+
4166
+ # check if the source can access to every other vertex
4167
+ if spanning:
4168
+ depth = self.order() - 1
4169
+ if len(list(self.depth_first_search(source))) < self.order():
4170
+ raise ValueError("no spanning out branching from vertex ({0}) exist".format(source))
4171
+ else:
4172
+ depth = len(list(self.depth_first_search(source))) - 1
4173
+ # if vertex is isolated
4174
+ if not depth:
4175
+ return _singleton_out_branching()
4176
+
4177
+ # We build a copy of self in which each edge has a distinct label.
4178
+ # On the way, we remove loops and edges incoming to source.
4179
+ D = DiGraph(multiedges=True, loops=True)
4180
+ list_edges = list(self.edges(sort=False))
4181
+ for i, (u, v, _) in enumerate(list_edges):
4182
+ if u != v and v != source:
4183
+ D.add_edge(u, v, (i,))
4184
+ list_merged_edges = set()
4185
+ return _rec_out_branchings(depth)
4186
+
4187
+ def in_branchings(self, source, spanning=True):
4188
+ r"""
4189
+ Return an iterator over the in branchings rooted at given vertex in
4190
+ ``self``.
4191
+
4192
+ An in-branching is a directed tree rooted at ``source`` whose arcs are
4193
+ directed to source from leaves. An in-branching is spanning if it
4194
+ contains all vertices of the digraph.
4195
+
4196
+ If no spanning in branching rooted at ``source`` exist, raises
4197
+ :exc:`ValueError` or return non spanning in branching rooted at
4198
+ ``source``, depending on the value of ``spanning``.
4199
+
4200
+ INPUT:
4201
+
4202
+ - ``source`` -- vertex used as the source for all in branchings
4203
+
4204
+ - ``spanning`` -- boolean (default: ``True``); if ``False`` return
4205
+ maximum in branching to ``source``. Otherwise, return spanning in
4206
+ branching if exists.
4207
+
4208
+ OUTPUT: an iterator over the in branchings rooted in the given source
4209
+
4210
+ .. SEEALSO::
4211
+
4212
+ - :meth:`~sage.graphs.digraph.DiGraph.out_branchings`
4213
+ -- iterator over out-branchings rooted at given vertex.
4214
+ - :meth:`~sage.graphs.graph.Graph.spanning_trees`
4215
+ -- returns all spanning trees.
4216
+ - :meth:`~sage.graphs.generic_graph.GenericGraph.spanning_trees_count`
4217
+ -- counts the number of spanning trees.
4218
+
4219
+ ALGORITHM:
4220
+
4221
+ Recursively computes all in branchings.
4222
+
4223
+ At each step:
4224
+
4225
+ 0. clean the graph (see below)
4226
+ 1. pick an edge e incoming to source
4227
+ 2. find all in branchings that do not contain e by first
4228
+ removing it
4229
+ 3. find all in branchings that do contain e by first
4230
+ merging the end vertices of e
4231
+
4232
+ Cleaning the graph implies to remove loops and replace multiedges by a
4233
+ single one with an appropriate label since these lead to similar steps
4234
+ of computation.
4235
+
4236
+ EXAMPLES:
4237
+
4238
+ A bidirectional 4-cycle::
4239
+
4240
+ sage: G = DiGraph({1:[2,3], 2:[1,4], 3:[1,4], 4:[2,3]}, format='dict_of_lists')
4241
+ sage: list(G.in_branchings(1))
4242
+ [Digraph on 4 vertices,
4243
+ Digraph on 4 vertices,
4244
+ Digraph on 4 vertices,
4245
+ Digraph on 4 vertices]
4246
+
4247
+ With the Petersen graph turned into a symmetric directed graph::
4248
+
4249
+ sage: G = graphs.PetersenGraph().to_directed()
4250
+ sage: len(list(G.in_branchings(0)))
4251
+ 2000
4252
+
4253
+ With a non connected ``DiGraph`` and ``spanning = True``::
4254
+
4255
+ sage: G = graphs.PetersenGraph().to_directed() + graphs.PetersenGraph().to_directed()
4256
+ sage: G.in_branchings(0)
4257
+ Traceback (most recent call last):
4258
+ ...
4259
+ ValueError: no spanning in branching to vertex (0) exist
4260
+
4261
+ With a non connected ``DiGraph`` and ``spanning = False``::
4262
+
4263
+ sage: g=DiGraph([(1,0), (1,0), (2,1), (3,4)],multiedges=True)
4264
+ sage: list(g.in_branchings(0,spanning=False))
4265
+ [Digraph on 3 vertices, Digraph on 3 vertices]
4266
+
4267
+ With multiedges::
4268
+
4269
+ sage: G = DiGraph({0:[1,1,1], 1:[2,2]}, format='dict_of_lists', multiedges=True)
4270
+ sage: len(list(G.in_branchings(2)))
4271
+ 6
4272
+
4273
+ With a DiGraph already being a spanning in branching::
4274
+
4275
+ sage: G = DiGraph({0:[], 1:[0], 2:[0], 3:[1], 4:[1], 5:[2]}, format='dict_of_lists')
4276
+ sage: next(G.in_branchings(0)) == G
4277
+ True
4278
+
4279
+ TESTS:
4280
+
4281
+ The empty ``DiGraph``::
4282
+
4283
+ sage: G = DiGraph()
4284
+ sage: G.in_branchings(0)
4285
+ Traceback (most recent call last):
4286
+ ...
4287
+ ValueError: vertex (0) is not a vertex of the digraph
4288
+
4289
+ sage: edges = [(0,0,'x'), (0,0,'y')]
4290
+ sage: G = DiGraph(edges, multiedges=True, loops=True, weighted=True)
4291
+ sage: list(G.in_branchings(0))
4292
+ [Digraph on 1 vertex]
4293
+
4294
+ sage: edges = [(0,1,'x'), (0,1,'y'), (1,2,'z'), (2,0,'w')]
4295
+ sage: G = DiGraph(edges, multiedges=True, loops=True, weighted=True)
4296
+ sage: len(list(G.in_branchings(0)))
4297
+ 1
4298
+ """
4299
+ def _rec_in_branchings(depth):
4300
+ r"""
4301
+ The recursive function used to enumerate in branchings.
4302
+
4303
+ This function makes use of the following to keep track of partial in
4304
+ branchings:
4305
+
4306
+ - ``list_edges`` -- list of edges in self
4307
+ - ``list_merged_edges`` -- list of edges that are currently merged
4308
+ - ``graph`` -- a copy of self where edges have an appropriate label
4309
+ """
4310
+ if not depth:
4311
+ # We have enough merged edges to form a in_branching
4312
+ # We iterate over the lists of labels in list_merged_edges and
4313
+ # yield the corresponding in_branchings
4314
+ for indexes in product(*list_merged_edges):
4315
+ yield DiGraph([list_edges[index] for index in indexes],
4316
+ format='list_of_edges', pos=self.get_pos())
4317
+
4318
+ # 1) Clean the graph
4319
+ # delete loops on source if any
4320
+ D.delete_edges(D.outgoing_edge_iterator(source))
4321
+
4322
+ # merge multi-edges if any by concatenating their labels
4323
+ if D.has_multiple_edges():
4324
+ merged_multiple_edges = {}
4325
+ for u, v, label in D.multiple_edges():
4326
+ D.delete_edge(u, v, label)
4327
+ if (u, v) not in merged_multiple_edges:
4328
+ merged_multiple_edges[(u, v)] = label
4329
+ else:
4330
+ merged_multiple_edges[(u, v)] += label
4331
+ D.add_edges([(u, v, label) for (u, v), label in merged_multiple_edges.items()])
4332
+
4333
+ # 2) Pick an edge e incoming to the source
4334
+ try:
4335
+ x, s, label = next(D.incoming_edge_iterator(source))
4336
+ except StopIteration:
4337
+ return
4338
+ # 3) Find all in_branchings that do not contain e
4339
+ # by first removing it
4340
+ D.delete_edge(x, s, label)
4341
+ if len(list(D.depth_first_search(source, neighbors=D.neighbor_in_iterator))) == depth + 1:
4342
+ for in_branch in _rec_in_branchings(depth):
4343
+ yield in_branch
4344
+ D.add_edge(x, s, label)
4345
+
4346
+ # 4) Find all in_branchings that do contain e by merging
4347
+ # the end vertices of e
4348
+ # store different edges to unmerged the end vertices of e
4349
+ saved_edges = D.incoming_edges(source)
4350
+ saved_edges.remove((x, s, label))
4351
+ saved_edges += D.outgoing_edges(x)
4352
+ saved_edges += D.incoming_edges(x)
4353
+
4354
+ D.merge_vertices((source, x))
4355
+
4356
+ list_merged_edges.add(label)
4357
+
4358
+ for in_branch in _rec_in_branchings(depth - 1):
4359
+ yield in_branch
4360
+
4361
+ list_merged_edges.remove(label)
4362
+
4363
+ # unmerge the end vertices of e
4364
+ D.delete_vertex(source)
4365
+ D.add_edges(saved_edges)
4366
+
4367
+ def _singleton_in_branching():
4368
+ r"""
4369
+ Return a DiGraph containing only ``source`` and no edges.
4370
+ """
4371
+ D = DiGraph()
4372
+ D.add_vertex(source)
4373
+ yield D
4374
+
4375
+ if not self.has_vertex(source):
4376
+ raise ValueError("vertex ({0}) is not a vertex of the digraph".format(source))
4377
+
4378
+ # check if self.order == 1
4379
+ if self.order() == 1:
4380
+ return _singleton_in_branching()
4381
+
4382
+ # check if the source can access to every other vertex
4383
+ if spanning:
4384
+ depth = self.order() - 1
4385
+ if len(list(self.depth_first_search(source, neighbors=self.neighbor_in_iterator))) < self.order():
4386
+ raise ValueError("no spanning in branching to vertex ({0}) exist".format(source))
4387
+ else:
4388
+ depth = len(list(self.depth_first_search(source, neighbors=self.neighbor_in_iterator))) - 1
4389
+ # if vertex is isolated
4390
+ if not depth:
4391
+ return _singleton_in_branching()
4392
+
4393
+ # We build a copy of self in which each edge has a distinct label.
4394
+ # On the way, we remove loops and edges incoming to source.
4395
+ D = DiGraph(multiedges=True, loops=True)
4396
+ list_edges = list(self.edges(sort=False))
4397
+ for i, (u, v, _) in enumerate(list_edges):
4398
+ if u != v and u != source:
4399
+ D.add_edge(u, v, (i,))
4400
+ list_merged_edges = set()
4401
+ return _rec_in_branchings(depth)
4402
+
4403
+ # Aliases to functions defined in other modules
4404
+ from sage.graphs.comparability import is_transitive
4405
+ from sage.graphs.base.static_sparse_graph import tarjan_strongly_connected_components as strongly_connected_components
4406
+ from sage.graphs.connectivity import is_strongly_connected
4407
+ from sage.graphs.connectivity import strongly_connected_components_digraph
4408
+ from sage.graphs.connectivity import strongly_connected_components_subgraphs
4409
+ from sage.graphs.connectivity import strongly_connected_component_containing_vertex
4410
+ from sage.graphs.connectivity import strong_articulation_points