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
@@ -0,0 +1,1504 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ r"""
3
+ Orientations
4
+
5
+ This module implements several methods to compute orientations of undirected
6
+ graphs subject to specific constraints (e.g., acyclic, strongly connected,
7
+ etc.). It also implements some iterators over all these orientations.
8
+
9
+ **This module contains the following methods**
10
+
11
+ .. csv-table::
12
+ :class: contentstable
13
+ :widths: 30, 70
14
+ :delim: |
15
+
16
+ :meth:`orient` | Return an oriented version of `G` according the input function `f`.
17
+ :meth:`orientations` | Return an iterator over orientations of `G`.
18
+ :meth:`acyclic_orientations` | Return an iterator over all acyclic orientations of an undirected graph `G`.
19
+ :meth:`strong_orientation` | Return a strongly connected orientation of the graph `G`.
20
+ :meth:`strong_orientations_iterator` | Return an iterator over all strong orientations of a graph `G`
21
+ :meth:`random_orientation` | Return a random orientation of a graph `G`
22
+ :meth:`minimum_outdegree_orientation` | Return an orientation of `G` with the smallest possible maximum outdegree.
23
+ :meth:`bounded_outdegree_orientation` | Return an orientation of `G` such that every vertex `v` has out-degree less than `b(v)`.
24
+ :meth:`eulerian_orientation` | Return an Eulerian orientation of the graph `G`.
25
+
26
+ Authors
27
+ -------
28
+
29
+ - Kolja Knauer, Petru Valicov (2017-01-10) -- initial version
30
+
31
+
32
+ Methods
33
+ -------
34
+ """
35
+ # ****************************************************************************
36
+ # Copyright (C) 2017 Kolja Knauer <kolja.knauer@gmail.com>
37
+ # 2017 Petru Valicov <petru.valicov@lirmm.fr>
38
+ # 2017-2024 David Coudert <david.coudert@inria.fr>
39
+ #
40
+ # This program is free software: you can redistribute it and/or modify
41
+ # it under the terms of the GNU General Public License as published by
42
+ # the Free Software Foundation, either version 2 of the License, or
43
+ # (at your option) any later version.
44
+ # https://www.gnu.org/licenses/
45
+ # ****************************************************************************
46
+
47
+ from copy import copy
48
+ from sage.graphs.digraph import DiGraph
49
+ from sage.graphs.generic_graph import _weight_if_real, _weight_1
50
+
51
+
52
+ def _initialize_digraph(G, edges, name=None, weighted=None, sparse=None,
53
+ data_structure=None, immutable=None, hash_labels=None):
54
+ r"""
55
+ Helper method to return a directed graph built from ``G``.
56
+
57
+ This method returns a digraph with the same set of vertices than the input
58
+ graph ``G`` and with specified edges. The data structure can be
59
+ specified. Furthermore, all attributes of the graph are copied to the
60
+ returned digraph.
61
+
62
+ INPUT:
63
+
64
+ - ``G`` -- a graph
65
+
66
+ - ``edges`` -- iterable; the edges of the digraph to return
67
+
68
+ - ``name`` -- string (default: ``None``); the name of the digraph to
69
+ return. By default (``None``), the returned digraph has the same name as
70
+ the input graph ``G``.
71
+
72
+ - ``weighted`` -- boolean (default: ``None``); weightedness for the oriented
73
+ digraph. By default (``None``), the graph and its orientation will behave
74
+ the same.
75
+
76
+ - ``sparse`` -- boolean (default: ``None``); ``sparse=True`` is an alias for
77
+ ``data_structure="sparse"``, and ``sparse=False`` is an alias for
78
+ ``data_structure="dense"``. Only used when ``data_structure=None``.
79
+
80
+ - ``data_structure`` -- string (default: ``None``); one of ``'sparse'``,
81
+ ``'static_sparse'``, or ``'dense'``. See the documentation of
82
+ :class:`DiGraph`.
83
+
84
+ - ``immutable`` -- boolean (default: ``None``); whether to create a
85
+ mutable/immutable digraph. Only used when ``data_structure=None``.
86
+
87
+ * ``immutable=None`` (default) means that the graph and its orientation
88
+ will behave the same way.
89
+
90
+ * ``immutable=True`` is a shortcut for ``data_structure='static_sparse'``
91
+
92
+ * ``immutable=False`` means that the created digraph is mutable. When used
93
+ to orient an immutable graph, the data structure used is ``'sparse'``
94
+ unless anything else is specified.
95
+
96
+ - ``hash_labels`` -- boolean (default: ``None``); whether to include edge
97
+ labels during hashing of the oriented digraph. This parameter defaults to
98
+ ``True`` if the graph is weighted. This parameter is ignored when
99
+ parameter ``immutable`` is not ``True``. Beware that trying to hash
100
+ unhashable labels will raise an error.
101
+
102
+ EXAMPLES::
103
+
104
+ sage: from sage.graphs.orientations import _initialize_digraph
105
+ sage: G = Graph([(1, 2)], immutable=True, loops=True, multiedges=True)
106
+ sage: D = _initialize_digraph(G, [])
107
+ sage: D.is_immutable()
108
+ True
109
+ sage: D.allows_loops()
110
+ True
111
+ sage: D.allows_multiple_edges()
112
+ True
113
+ sage: D.edges()
114
+ []
115
+ sage: D.add_edge((2, 3))
116
+ Traceback (most recent call last):
117
+ ...
118
+ ValueError: graph is immutable; please change a copy instead (use function copy())
119
+ sage: G = Graph([(1, 2)])
120
+ sage: D = _initialize_digraph(G, [])
121
+ sage: D.vertices()
122
+ [1, 2]
123
+ sage: D.edges()
124
+ []
125
+ sage: D.add_edge((2, 3)); D.edges()
126
+ [(2, 3, None)]
127
+
128
+ TESTS::
129
+
130
+ sage: from sage.graphs.orientations import _initialize_digraph
131
+ sage: _initialize_digraph(Graph(), [], data_structure='sparse', immutable=False)
132
+ Traceback (most recent call last):
133
+ ...
134
+ ValueError: you cannot define 'immutable' or 'sparse' when 'data_structure' has a value
135
+ sage: _initialize_digraph(Graph(), [], data_structure='sparse', sparse=True)
136
+ Traceback (most recent call last):
137
+ ...
138
+ ValueError: you cannot define 'immutable' or 'sparse' when 'data_structure' has a value
139
+ """
140
+ # Which data structure should be used ?
141
+ if data_structure is not None:
142
+ # data_structure is already defined so there is nothing left to do
143
+ # here. Did the user try to define too much ?
144
+ if immutable is not None or sparse is not None:
145
+ raise ValueError("you cannot define 'immutable' or 'sparse' "
146
+ "when 'data_structure' has a value")
147
+ # At this point, data_structure is None.
148
+ elif immutable is True:
149
+ data_structure = 'static_sparse'
150
+ if sparse is False:
151
+ raise ValueError("there is no dense immutable backend at the moment")
152
+ elif immutable is False:
153
+ # If the user requests a mutable digraph and input is immutable, we
154
+ # choose the 'sparse' cgraph backend. Unless the user explicitly
155
+ # asked for something different.
156
+ if G.is_immutable():
157
+ data_structure = 'dense' if sparse is False else 'sparse'
158
+ # At this point, data_structure and immutable are None.
159
+ elif sparse is True:
160
+ data_structure = "sparse"
161
+ elif sparse is False:
162
+ data_structure = "dense"
163
+
164
+ if data_structure is None:
165
+ from sage.graphs.base.dense_graph import DenseGraphBackend
166
+ from sage.graphs.base.sparse_graph import SparseGraphBackend
167
+ if isinstance(G._backend, DenseGraphBackend):
168
+ data_structure = "dense"
169
+ elif isinstance(G._backend, SparseGraphBackend):
170
+ data_structure = "sparse"
171
+ else:
172
+ data_structure = "static_sparse"
173
+
174
+ if name is None:
175
+ name = G.name()
176
+ if weighted is None:
177
+ weighted = G.weighted()
178
+ if hash_labels is None:
179
+ hash_labels = G._hash_labels
180
+
181
+ D = DiGraph(data=[G, edges],
182
+ format='vertices_and_edges',
183
+ data_structure=data_structure,
184
+ multiedges=G.allows_multiple_edges(),
185
+ loops=G.allows_loops(),
186
+ weighted=weighted,
187
+ pos=copy(G.get_pos()),
188
+ name=name,
189
+ hash_labels=hash_labels)
190
+
191
+ D.set_vertices(G.get_vertices())
192
+
193
+ attributes_to_copy = ('_assoc', '_embedding')
194
+ for attr in attributes_to_copy:
195
+ if hasattr(G, attr):
196
+ copy_attr = {}
197
+ old_attr = getattr(G, attr)
198
+ if isinstance(old_attr, dict):
199
+ for v, value in old_attr.items():
200
+ try:
201
+ copy_attr[v] = value.copy()
202
+ except AttributeError:
203
+ copy_attr[v] = copy(value)
204
+ setattr(D, attr, copy_attr)
205
+ else:
206
+ setattr(D, attr, copy(old_attr))
207
+
208
+ return D
209
+
210
+
211
+ def orient(G, f, weighted=None, data_structure=None, sparse=None,
212
+ immutable=None, hash_labels=None):
213
+ r"""
214
+ Return an oriented version of `G` according the input function `f`.
215
+
216
+ INPUT:
217
+
218
+ - ``G`` -- an undirected graph
219
+
220
+ - ``f`` -- a function that inputs an edge and outputs an orientation of this
221
+ edge
222
+
223
+ - ``weighted`` -- boolean (default: ``None``); weightedness for the oriented
224
+ digraph. By default (``None``), the graph and its orientation will behave
225
+ the same.
226
+
227
+ - ``sparse`` -- boolean (default: ``None``); ``sparse=True`` is an alias for
228
+ ``data_structure="sparse"``, and ``sparse=False`` is an alias for
229
+ ``data_structure="dense"``. Only used when ``data_structure=None``.
230
+
231
+ - ``data_structure`` -- string (default: ``None``); one of ``'sparse'``,
232
+ ``'static_sparse'``, or ``'dense'``. See the documentation of
233
+ :class:`DiGraph`.
234
+
235
+ - ``immutable`` -- boolean (default: ``None``); whether to create a
236
+ mutable/immutable digraph. Only used when ``data_structure=None``.
237
+
238
+ * ``immutable=None`` (default) means that the graph and its orientation
239
+ will behave the same way.
240
+
241
+ * ``immutable=True`` is a shortcut for ``data_structure='static_sparse'``
242
+
243
+ * ``immutable=False`` means that the created digraph is mutable. When used
244
+ to orient an immutable graph, the data structure used is ``'sparse'``
245
+ unless anything else is specified.
246
+
247
+ - ``hash_labels`` -- boolean (default: ``None``); whether to include edge
248
+ labels during hashing of the oriented digraph. This parameter defaults to
249
+ ``True`` if the graph is weighted. This parameter is ignored when
250
+ parameter ``immutable`` is not ``True``. Beware that trying to hash
251
+ unhashable labels will raise an error.
252
+
253
+ OUTPUT: a :class:`DiGraph` object
254
+
255
+ .. NOTE::
256
+
257
+ This method behaves similarly to method
258
+ :meth:`~sage.graphs.generic_graph.GenericGraph.copy`. That is, the
259
+ returned digraph uses the same data structure by default, unless the
260
+ user asks to use another data structure, and the attributes of the input
261
+ graph are copied.
262
+
263
+ EXAMPLES::
264
+
265
+ sage: G = graphs.CycleGraph(4); G
266
+ Cycle graph: Graph on 4 vertices
267
+ sage: D = G.orient(lambda e:e if e[0] < e[1] else (e[1], e[0], e[2])); D
268
+ Orientation of Cycle graph: Digraph on 4 vertices
269
+ sage: sorted(D.edges(labels=False))
270
+ [(0, 1), (0, 3), (1, 2), (2, 3)]
271
+
272
+ TESTS:
273
+
274
+ We make sure that one can get an immutable orientation by providing the
275
+ ``data_structure`` optional argument::
276
+
277
+ sage: def foo(e):
278
+ ....: return e if e[0] < e[1] else (e[1], e[0], e[2])
279
+ sage: G = graphs.CycleGraph(4)
280
+ sage: D = G.orient(foo, data_structure='static_sparse')
281
+ sage: D.is_immutable()
282
+ True
283
+ sage: D = G.orient(foo, immutable=True)
284
+ sage: D.is_immutable()
285
+ True
286
+
287
+ Bad input::
288
+
289
+ sage: G.orient(foo, data_structure='sparse', sparse=False)
290
+ Traceback (most recent call last):
291
+ ...
292
+ ValueError: you cannot define 'immutable' or 'sparse' when 'data_structure' has a value
293
+ sage: G.orient(foo, data_structure='sparse', immutable=True)
294
+ Traceback (most recent call last):
295
+ ...
296
+ ValueError: you cannot define 'immutable' or 'sparse' when 'data_structure' has a value
297
+ sage: G.orient(foo, immutable=True, sparse=False)
298
+ Traceback (most recent call last):
299
+ ...
300
+ ValueError: there is no dense immutable backend at the moment
301
+
302
+ Which backend? ::
303
+
304
+ sage: G.orient(foo, data_structure='sparse')._backend
305
+ <sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
306
+ sage: G.orient(foo, data_structure='dense')._backend
307
+ <sage.graphs.base.dense_graph.DenseGraphBackend object at ...>
308
+ sage: G.orient(foo, data_structure='static_sparse')._backend
309
+ <sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...>
310
+ sage: G.orient(foo, immutable=True)._backend
311
+ <sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...>
312
+ sage: G.orient(foo, immutable=True, sparse=True)._backend
313
+ <sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...>
314
+ sage: G.orient(foo, immutable=False, sparse=True)._backend
315
+ <sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
316
+ sage: G.orient(foo, immutable=False, sparse=False)._backend
317
+ <sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
318
+ sage: G.orient(foo, data_structure=None, immutable=None, sparse=True)._backend
319
+ <sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
320
+ sage: G.orient(foo, data_structure=None, immutable=None, sparse=False)._backend
321
+ <sage.graphs.base.dense_graph.DenseGraphBackend object at ...>
322
+ sage: G.orient(foo, data_structure=None, immutable=None, sparse=None)._backend
323
+ <sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
324
+ sage: H = Graph(data_structure='dense')
325
+ sage: H.orient(foo, data_structure=None, immutable=None, sparse=None)._backend
326
+ <sage.graphs.base.dense_graph.DenseGraphBackend object at ...>
327
+ """
328
+ edges = (f(e) for e in G.edge_iterator())
329
+ name = f"Orientation of {G.name()}"
330
+ return _initialize_digraph(G, edges, name=name, weighted=weighted,
331
+ data_structure=data_structure, sparse=sparse,
332
+ immutable=immutable, hash_labels=hash_labels)
333
+
334
+
335
+ def orientations(G, data_structure=None, sparse=None):
336
+ r"""
337
+ Return an iterator over orientations of `G`.
338
+
339
+ An *orientation* of an undirected graph is a directed graph such that
340
+ every edge is assigned a direction. Hence there are `2^s` oriented
341
+ digraphs for a simple graph with `s` edges.
342
+
343
+ INPUT:
344
+
345
+ - ``G`` -- an undirected graph
346
+
347
+ - ``data_structure`` -- one of ``'sparse'``, ``'static_sparse'``, or
348
+ ``'dense'``; see the documentation of :class:`Graph` or :class:`DiGraph`;
349
+ default is the data structure of `G`
350
+
351
+ - ``sparse`` -- boolean (default: ``None``); ``sparse=True`` is an alias for
352
+ ``data_structure="sparse"``, and ``sparse=False`` is an alias for
353
+ ``data_structure="dense"``. By default (``None``), guess the most suitable
354
+ data structure.
355
+
356
+ .. WARNING::
357
+
358
+ This always considers multiple edges of graphs as distinguishable, and
359
+ hence, may have repeated digraphs.
360
+
361
+ .. SEEALSO::
362
+
363
+ - :meth:`~sage.graphs.graph.Graph.strong_orientation`
364
+ - :meth:`~sage.graphs.orientations.strong_orientations_iterator`
365
+ - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.nauty_directg`
366
+ - :meth:`~sage.graphs.orientations.random_orientation`
367
+
368
+ EXAMPLES::
369
+
370
+ sage: G = Graph([[1,2,3], [(1, 2, 'a'), (1, 3, 'b')]], format='vertices_and_edges')
371
+ sage: it = G.orientations()
372
+ sage: D = next(it)
373
+ sage: D.edges(sort=True)
374
+ [(1, 2, 'a'), (1, 3, 'b')]
375
+ sage: D = next(it)
376
+ sage: D.edges(sort=True)
377
+ [(1, 2, 'a'), (3, 1, 'b')]
378
+
379
+ TESTS::
380
+
381
+ sage: G = Graph()
382
+ sage: D = [g for g in G.orientations()]
383
+ sage: len(D)
384
+ 1
385
+ sage: D[0]
386
+ Digraph on 0 vertices
387
+
388
+ sage: G = Graph(5)
389
+ sage: it = G.orientations()
390
+ sage: D = next(it)
391
+ sage: D.size()
392
+ 0
393
+
394
+ sage: G = Graph([[1,2,'a'], [1,2,'b']], multiedges=True)
395
+ sage: len(list(G.orientations()))
396
+ 4
397
+
398
+ sage: G = Graph([[1,2], [1,1]], loops=True)
399
+ sage: len(list(G.orientations()))
400
+ 2
401
+
402
+ sage: G = Graph([[1,2],[2,3]])
403
+ sage: next(G.orientations())
404
+ Digraph on 3 vertices
405
+ sage: G = graphs.PetersenGraph()
406
+ sage: next(G.orientations())
407
+ An orientation of Petersen graph: Digraph on 10 vertices
408
+
409
+ An orientation must have the same ground set of vertices as the original
410
+ graph (:issue:`24366`)::
411
+
412
+ sage: G = Graph(1)
413
+ sage: next(G.orientations())
414
+ Digraph on 1 vertex
415
+
416
+ Which backend? ::
417
+
418
+ sage: next(G.orientations(data_structure='sparse', sparse=True))._backend
419
+ Traceback (most recent call last):
420
+ ...
421
+ ValueError: you cannot define 'immutable' or 'sparse' when 'data_structure' has a value
422
+ sage: next(G.orientations(sparse=True))._backend
423
+ <sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
424
+ sage: next(G.orientations(sparse=False))._backend
425
+ <sage.graphs.base.dense_graph.DenseGraphBackend object at ...>
426
+ sage: next(G.orientations())._backend
427
+ <sage.graphs.base.sparse_graph.SparseGraphBackend object at ...>
428
+ sage: G = Graph(1, data_structure='dense')
429
+ sage: next(G.orientations())._backend
430
+ <sage.graphs.base.dense_graph.DenseGraphBackend object at ...>
431
+ sage: G = Graph(1, data_structure='static_sparse')
432
+ sage: next(G.orientations())._backend
433
+ <sage.graphs.base.static_sparse_backend.StaticSparseBackend object at ...>
434
+
435
+ Check that the embedding is copied::
436
+
437
+ sage: G = Graph([(0, 1), (0, 2), (1, 2)])
438
+ sage: embedding = {0: [1, 2], 1: [2, 0], 2: [0, 1]}
439
+ sage: G.set_embedding(embedding)
440
+ sage: next(G.orientations()).get_embedding() == embedding
441
+ True
442
+ sage: G = Graph()
443
+ sage: G.set_embedding({})
444
+ sage: next(G.orientations()).get_embedding() == {}
445
+ True
446
+ """
447
+ name = G.name()
448
+ if name:
449
+ name = 'An orientation of ' + name
450
+
451
+ if not G.size():
452
+ yield _initialize_digraph(G, [], name=name,
453
+ data_structure=data_structure, sparse=sparse)
454
+ return
455
+
456
+ E = [[(u, v, label), (v, u, label)] if u != v else [(u, v, label)]
457
+ for u, v, label in G.edge_iterator()]
458
+ from itertools import product
459
+ for edges in product(*E):
460
+ yield _initialize_digraph(G, edges, name=name,
461
+ data_structure=data_structure, sparse=sparse)
462
+
463
+
464
+ def acyclic_orientations(G):
465
+ r"""
466
+ Return an iterator over all acyclic orientations of an undirected graph `G`.
467
+
468
+ ALGORITHM:
469
+
470
+ The algorithm is based on [Sq1998]_.
471
+ It presents an efficient algorithm for listing the acyclic orientations of a
472
+ graph. The algorithm is shown to require O(n) time per acyclic orientation
473
+ generated, making it the most efficient known algorithm for generating acyclic
474
+ orientations.
475
+
476
+ The function uses a recursive approach to generate acyclic orientations of the
477
+ graph. It reorders the vertices and edges of the graph, creating a new graph
478
+ with updated labels. Then, it iteratively generates acyclic orientations by
479
+ considering subsets of edges and checking whether they form upsets in a
480
+ corresponding poset.
481
+
482
+ INPUT:
483
+
484
+ - ``G`` -- an undirected graph
485
+
486
+ OUTPUT: an iterator over all acyclic orientations of the input graph
487
+
488
+ .. NOTE::
489
+
490
+ The function assumes that the input graph is undirected and the edges
491
+ are unlabelled.
492
+
493
+ EXAMPLES:
494
+
495
+ To count the number of acyclic orientations for a graph::
496
+
497
+ sage: g = Graph([(0, 3), (0, 4), (3, 4), (1, 3), (1, 2), (2, 3), (2, 4)])
498
+ sage: it = g.acyclic_orientations()
499
+ sage: len(list(it))
500
+ 54
501
+
502
+ Test for arbitrary vertex labels::
503
+
504
+ sage: g_str = Graph([('abc', 'def'), ('ghi', 'def'), ('xyz', 'abc'),
505
+ ....: ('xyz', 'uvw'), ('uvw', 'abc'), ('uvw', 'ghi')])
506
+ sage: it = g_str.acyclic_orientations()
507
+ sage: len(list(it))
508
+ 42
509
+
510
+ Check that the method returns properly relabeled acyclic digraphs::
511
+
512
+ sage: g = Graph([(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)])
513
+ sage: orientations = set([frozenset(d.edges(labels=false)) for d in g.acyclic_orientations()])
514
+ sage: len(orientations)
515
+ 18
516
+ sage: all(d.is_directed_acyclic() for d in g.acyclic_orientations())
517
+ True
518
+
519
+ TESTS:
520
+
521
+ To count the number of acyclic orientations for a graph with 0 vertices::
522
+
523
+ sage: list(Graph().acyclic_orientations())
524
+ []
525
+
526
+ To count the number of acyclic orientations for a graph with 1 vertex::
527
+
528
+ sage: list(Graph(1).acyclic_orientations())
529
+ []
530
+
531
+ To count the number of acyclic orientations for a graph with 2 vertices::
532
+
533
+ sage: list(Graph(2).acyclic_orientations())
534
+ []
535
+
536
+ Acyclic orientations of a complete graph::
537
+
538
+ sage: g = graphs.CompleteGraph(5)
539
+ sage: it = g.acyclic_orientations()
540
+ sage: len(list(it))
541
+ 120
542
+
543
+ Graph with one edge::
544
+
545
+ sage: list(Graph([(0, 1)]).acyclic_orientations())
546
+ [Digraph on 2 vertices, Digraph on 2 vertices]
547
+
548
+ Graph with two edges::
549
+
550
+ sage: len(list(Graph([(0, 1), (1, 2)]).acyclic_orientations()))
551
+ 4
552
+
553
+ Cycle graph::
554
+
555
+ sage: len(list(Graph([(0, 1), (1, 2), (2, 0)]).acyclic_orientations()))
556
+ 6
557
+ """
558
+ if not G.size():
559
+ # A graph without edge cannot be oriented
560
+ return
561
+
562
+ from sage.rings.infinity import Infinity
563
+ from sage.combinat.subset import Subsets
564
+
565
+ def reorder_vertices(G):
566
+ n = G.order()
567
+ ko = n
568
+ k = n
569
+ G_copy = G.copy()
570
+ vertex_labels = {v: None for v in G_copy.vertices()}
571
+
572
+ while G_copy.size() > 0:
573
+ min_val = float('inf')
574
+ uv = None
575
+ for u, v, _ in G_copy.edges():
576
+ du = G_copy.degree(u)
577
+ dv = G_copy.degree(v)
578
+ val = (du + dv) / (du * dv)
579
+ if val < min_val:
580
+ min_val = val
581
+ uv = (u, v)
582
+
583
+ if uv:
584
+ u, v = uv
585
+ vertex_labels[u] = ko
586
+ vertex_labels[v] = ko - 1
587
+ G_copy.delete_vertex(u)
588
+ G_copy.delete_vertex(v)
589
+ ko -= 2
590
+
591
+ if G_copy.size() == 0:
592
+ break
593
+
594
+ for vertex, label in vertex_labels.items():
595
+ if label is None:
596
+ vertex_labels[vertex] = ko
597
+ ko -= 1
598
+
599
+ return vertex_labels
600
+
601
+ def order_edges(G, vertex_labels):
602
+ n = len(vertex_labels)
603
+ m = 1
604
+ edge_labels = {}
605
+
606
+ for j in range(2, n + 1):
607
+ for i in range(1, j):
608
+ if G.has_edge(i, j):
609
+ edge_labels[(i, j)] = m
610
+ m += 1
611
+
612
+ return edge_labels
613
+
614
+ def is_upset_of_poset(Poset, subset, keys):
615
+ for (u, v) in subset:
616
+ for (w, x) in keys:
617
+ if (Poset[(u, v), (w, x)] == 1 and (w, x) not in subset):
618
+ return False
619
+ return True
620
+
621
+ def generate_orientations(globO, starting_of_Ek, m, k, keys):
622
+ # Creating a poset
623
+ Poset = {}
624
+ for i in range(starting_of_Ek, m - 1):
625
+ for j in range(starting_of_Ek, m - 1):
626
+ u, v = keys[i]
627
+ w, x = keys[j]
628
+ Poset[(u, v), (w, x)] = 0
629
+
630
+ # Create a new graph to determine reachable vertices
631
+ new_G = DiGraph()
632
+
633
+ # Process vertices up to starting_of_Ek
634
+ new_G.add_edges([(v, u) if globO[(u, v)] == 1 else (u, v) for u, v in keys[:starting_of_Ek]])
635
+
636
+ # Process vertices starting from starting_of_Ek
637
+ new_G.add_vertices([u for u, _ in keys[starting_of_Ek:]] + [v for _, v in keys[starting_of_Ek:]])
638
+
639
+ if (globO[(k-1, k)] == 1):
640
+ new_G.add_edge(k, k - 1)
641
+ else:
642
+ new_G.add_edge(k-1, k)
643
+
644
+ for i in range(starting_of_Ek, m - 1):
645
+ for j in range(starting_of_Ek, m - 1):
646
+ u, v = keys[i]
647
+ w, x = keys[j]
648
+ # w should be reachable from u and v should be reachable from x
649
+ if w in new_G.depth_first_search(u) and v in new_G.depth_first_search(x):
650
+ Poset[(u, v), (w, x)] = 1
651
+
652
+ # For each subset of the base set of E_k, check if it is an upset or not
653
+ upsets = []
654
+ for subset in Subsets(keys[starting_of_Ek:m-1]):
655
+ if (is_upset_of_poset(Poset, subset, keys[starting_of_Ek:m-1])):
656
+ upsets.append(list(subset))
657
+
658
+ for upset in upsets:
659
+ for i in range(starting_of_Ek, m - 1):
660
+ u, v = keys[i]
661
+ if (u, v) in upset:
662
+ globO[(u, v)] = 1
663
+ else:
664
+ globO[(u, v)] = 0
665
+
666
+ yield globO.copy()
667
+
668
+ def helper(G, globO, m, k):
669
+ keys = list(globO.keys())
670
+ keys = keys[0:m]
671
+
672
+ if m <= 0:
673
+ yield {}
674
+ return
675
+
676
+ starting_of_Ek = 0
677
+ for (u, v) in keys:
678
+ if u >= k - 1 or v >= k - 1:
679
+ break
680
+ else:
681
+ starting_of_Ek += 1
682
+
683
+ # s is the size of E_k
684
+ s = m - 1 - starting_of_Ek
685
+
686
+ # Recursively generate acyclic orientations
687
+ orientations_G_small = helper(G, globO, starting_of_Ek, k - 2)
688
+
689
+ # For each orientation of G_k-2, yield acyclic orientations
690
+ for alpha in orientations_G_small:
691
+ for (u, v) in alpha:
692
+ globO[(u, v)] = alpha[(u, v)]
693
+
694
+ # Orienting H_k as 1
695
+ globO[(k-1, k)] = 1
696
+ yield from generate_orientations(globO, starting_of_Ek, m, k, keys)
697
+
698
+ # Orienting H_k as 0
699
+ globO[(k-1, k)] = 0
700
+ yield from generate_orientations(globO, starting_of_Ek, m, k, keys)
701
+
702
+ # Reorder vertices based on the logic in reorder_vertices function
703
+ vertex_labels = reorder_vertices(G)
704
+
705
+ # Create a new graph with updated vertex labels using SageMath, Assuming the graph edges are unlabelled
706
+ new_G = G.relabel(perm=vertex_labels, inplace=False)
707
+
708
+ G = new_G
709
+
710
+ # Order the edges based on the logic in order_edges function
711
+ edge_labels = order_edges(G, vertex_labels)
712
+
713
+ # Create globO array
714
+ globO = {uv: 0 for uv in edge_labels}
715
+
716
+ m = len(edge_labels)
717
+ k = len(vertex_labels)
718
+ orientations = helper(G, globO, m, k)
719
+
720
+ # Create a mapping between original and new vertex labels
721
+ reverse_vertex_labels = {label: vertex for vertex, label in vertex_labels.items()}
722
+
723
+ # Iterate over acyclic orientations and create relabeled graphs
724
+ for orientation in orientations:
725
+ edges = ((u, v) if label else (v, u) for (u, v), label in orientation.items())
726
+ D = _initialize_digraph(G, edges)
727
+ D.relabel(perm=reverse_vertex_labels, inplace=True)
728
+ yield D
729
+
730
+
731
+ def strong_orientation(G):
732
+ r"""
733
+ Return a strongly connected orientation of the graph `G`.
734
+
735
+ An orientation of an undirected graph is a digraph obtained by giving an
736
+ unique direction to each of its edges. An orientation is said to be strong
737
+ if there is a directed path between each pair of vertices. See also the
738
+ :wikipedia:`Strongly_connected_component`.
739
+
740
+ If the graph is 2-edge-connected, a strongly connected orientation can be
741
+ found in linear time. If the given graph is not 2-connected, the orientation
742
+ returned will ensure that each 2-connected component has a strongly
743
+ connected orientation.
744
+
745
+ INPUT:
746
+
747
+ - ``G`` -- an undirected graph
748
+
749
+ OUTPUT: a digraph representing an orientation of the current graph
750
+
751
+ .. NOTE::
752
+
753
+ - This method assumes that the input the graph is connected.
754
+ - The time complexity is `O(n+m)` for ``SparseGraph`` and `O(n^2)` for
755
+ ``DenseGraph`` .
756
+
757
+ .. SEEALSO::
758
+
759
+ - :meth:`~sage.graphs.graph.Graph.orientations`
760
+ - :meth:`~sage.graphs.orientations.strong_orientations_iterator`
761
+ - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.nauty_directg`
762
+ - :meth:`~sage.graphs.orientations.random_orientation`
763
+
764
+ EXAMPLES:
765
+
766
+ For a 2-regular graph, a strong orientation gives to each vertex an
767
+ out-degree equal to 1::
768
+
769
+ sage: g = graphs.CycleGraph(5)
770
+ sage: g.strong_orientation().out_degree()
771
+ [1, 1, 1, 1, 1]
772
+
773
+ The Petersen Graph is 2-edge connected. It then has a strongly connected
774
+ orientation::
775
+
776
+ sage: g = graphs.PetersenGraph()
777
+ sage: o = g.strong_orientation()
778
+ sage: len(o.strongly_connected_components())
779
+ 1
780
+
781
+ The same goes for the CubeGraph in any dimension::
782
+
783
+ sage: all(len(graphs.CubeGraph(i).strong_orientation().strongly_connected_components()) == 1
784
+ ....: for i in range(2,6))
785
+ True
786
+
787
+ A multigraph also has a strong orientation::
788
+
789
+ sage: g = Graph([(0, 1), (0, 2), (1, 2)] * 2, multiedges=True)
790
+ sage: g.strong_orientation()
791
+ Multi-digraph on 3 vertices
792
+ """
793
+ from sage.graphs.base.dense_graph import DenseGraphBackend
794
+ if isinstance(G._backend, DenseGraphBackend):
795
+ data_structure = "dense"
796
+ else:
797
+ data_structure = "sparse"
798
+ d = _initialize_digraph(G, [], data_structure=data_structure)
799
+
800
+ i = 0
801
+
802
+ # The algorithm works through a depth-first search. Any edge used in the
803
+ # depth-first search is oriented in the direction in which it has been
804
+ # used. All the other edges are oriented backward
805
+
806
+ v = next(G.vertex_iterator())
807
+ seen = {}
808
+ i = 1
809
+
810
+ # Time at which the vertices have been discovered
811
+ seen[v] = i
812
+
813
+ # indicates the stack of edges to explore
814
+ next_ = G.edges_incident(v)
815
+
816
+ while next_:
817
+ e = next_.pop()
818
+
819
+ # Ignore loops
820
+ if e[0] == e[1]:
821
+ continue
822
+
823
+ # We assume e[0] to be a `seen` vertex
824
+ e = e if seen.get(e[0], False) is not False else (e[1], e[0], e[2])
825
+
826
+ # If we discovered a new vertex
827
+ if seen.get(e[1], False) is False:
828
+ d.add_edge(e)
829
+ next_.extend(ee for ee in G.edges_incident(e[1])
830
+ if ((e[0], e[1]) != (ee[0], ee[1])) and ((e[0], e[1]) != (ee[1], ee[0])))
831
+ i += 1
832
+ seen[e[1]] = i
833
+
834
+ # Else, we orient the edges backward
835
+ else:
836
+ if seen[e[0]] < seen[e[1]]:
837
+ d.add_edge(e[1], e[0], e[2])
838
+ else:
839
+ d.add_edge(e)
840
+
841
+ # Case of multiple edges. If another edge has already been inserted, we add
842
+ # the new one in the opposite direction.
843
+ tmp = None
844
+ for e in G.multiple_edges():
845
+ if tmp == (e[0], e[1]):
846
+ if d.has_edge(e[0], e[1]):
847
+ d.add_edge(e[1], e[0], e[2])
848
+ else:
849
+ d.add_edge(e)
850
+ tmp = (e[0], e[1])
851
+
852
+ return d
853
+
854
+
855
+ def strong_orientations_iterator(G):
856
+ r"""
857
+ Return an iterator over all strong orientations of a graph `G`.
858
+
859
+ A strong orientation of a graph is an orientation of its edges such that the
860
+ obtained digraph is strongly connected (i.e. there exist a directed path
861
+ between each pair of vertices). According to Robbins' theorem (see the
862
+ :wikipedia:`Robbins_theorem`), the graphs that have strong orientations are
863
+ exactly the 2-edge-connected graphs (i.e., the bridgeless graphs).
864
+
865
+ ALGORITHM:
866
+
867
+ It is an adaptation of the algorithm published in [CGMRV16]_.
868
+ It runs in `O(mn)` amortized time, where `m` is the number of edges and
869
+ `n` is the number of vertices. The amortized time can be improved to `O(m)`
870
+ with a more involved method.
871
+ In this function, first the graph is preprocessed and a spanning tree is
872
+ generated. Then every orientation of the non-tree edges of the graph can be
873
+ extended to at least one new strong orientation by orienting properly
874
+ the edges of the spanning tree (this property is proved in [CGMRV16]_).
875
+ Therefore, this function generates all partial orientations of the non-tree
876
+ edges and then launches a helper function corresponding to the generation
877
+ algorithm described in [CGMRV16]_.
878
+ In order to avoid trivial symmetries, the orientation of an arbitrary edge
879
+ is fixed before the start of the enumeration process.
880
+
881
+ INPUT:
882
+
883
+ - ``G`` -- an undirected graph
884
+
885
+ OUTPUT: an iterator which will produce all strong orientations of this graph
886
+
887
+ .. NOTE::
888
+
889
+ Works only for simple graphs (no multiple edges).
890
+ To avoid symmetries an orientation of an arbitrary edge is fixed.
891
+
892
+ .. SEEALSO::
893
+
894
+ - :meth:`~Graph.orientations`
895
+ - :meth:`~Graph.strong_orientation`
896
+ - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.nauty_directg`
897
+ - :meth:`~sage.graphs.orientations.random_orientation`
898
+
899
+ EXAMPLES:
900
+
901
+ A cycle has one possible (non-symmetric) strong orientation::
902
+
903
+ sage: g = graphs.CycleGraph(4)
904
+ sage: it = g.strong_orientations_iterator()
905
+ sage: len(list(it))
906
+ 1
907
+
908
+ A tree cannot be strongly oriented::
909
+
910
+ sage: g = graphs.RandomTree(10)
911
+ sage: len(list(g.strong_orientations_iterator()))
912
+ 0
913
+
914
+ Neither can be a disconnected graph::
915
+
916
+ sage: g = graphs.CompleteGraph(6)
917
+ sage: g.add_vertex(7)
918
+ sage: len(list(g.strong_orientations_iterator()))
919
+ 0
920
+
921
+ TESTS:
922
+
923
+ sage: g = graphs.CompleteGraph(2)
924
+ sage: len(list(g.strong_orientations_iterator()))
925
+ 0
926
+
927
+ sage: g = graphs.CubeGraph(3)
928
+ sage: b = True
929
+ sage: for orientedGraph in g.strong_orientations_iterator():
930
+ ....: if not orientedGraph.is_strongly_connected():
931
+ ....: b = False
932
+ sage: b
933
+ True
934
+
935
+ The total number of strong orientations of a graph can be counted using
936
+ the Tutte polynomial evaluated at points (0,2)::
937
+
938
+ sage: g = graphs.PetersenGraph()
939
+ sage: nr1 = len(list(g.strong_orientations_iterator()))
940
+ sage: nr2 = g.tutte_polynomial()(0,2)
941
+ sage: nr1 == nr2/2 # The Tutte polynomial counts also the symmetrical orientations
942
+ True
943
+ """
944
+ # if the graph has a bridge or is disconnected,
945
+ # then it cannot be strongly oriented
946
+ if G.order() < 3 or not G.is_connected() or any(G.bridges(labels=False)):
947
+ return
948
+
949
+ V = list(G)
950
+
951
+ # compute an arbitrary spanning tree of the undirected graph
952
+ T = G.subgraph(vertices=G, edges=G.min_spanning_tree(), inplace=False)
953
+ treeEdges = list(T.edges(labels=False, sort=False))
954
+ A = [edge for edge in G.edge_iterator(labels=False) if not T.has_edge(edge)]
955
+
956
+ # Initialize a digraph with the edges of the spanning tree doubly oriented
957
+ Dg = T.to_directed(sparse=True)
958
+ Dg.add_edges(A)
959
+
960
+ # initialization of the first binary word 00...0
961
+ # corresponding to the current orientation of the non-tree edges
962
+ existingAedges = [0] * len(A)
963
+
964
+ # Generate all orientations for non-tree edges (using Gray code)
965
+ # Each of these orientations can be extended to a strong orientation
966
+ # of G by orienting properly the tree-edges
967
+ previousWord = 0
968
+
969
+ # the orientation of one edge is fixed so we consider one edge less
970
+ nr = 2**(len(A) - 1)
971
+ for i in range(nr):
972
+ word = (i >> 1) ^ i
973
+ bitChanged = word ^ previousWord
974
+
975
+ bit = 0
976
+ while bitChanged > 1:
977
+ bitChanged >>= 1
978
+ bit += 1
979
+
980
+ previousWord = word
981
+ if not existingAedges[bit]:
982
+ Dg.reverse_edge(A[bit])
983
+ existingAedges[bit] = 1
984
+ else:
985
+ Dg.reverse_edge(A[bit][1], A[bit][0])
986
+ existingAedges[bit] = 0
987
+ # launch the algorithm for enumeration of the solutions
988
+ yield from _strong_orientations_of_a_mixed_graph(Dg, V, treeEdges)
989
+
990
+
991
+ def _strong_orientations_of_a_mixed_graph(Dg, V, E):
992
+ r"""
993
+ Helper function for the generation of all strong orientations.
994
+
995
+ Generates all strong orientations of a given partially directed graph
996
+ (also called mixed graph). The algorithm finds bound edges i.e undirected
997
+ edges whose orientation is forced and tries all possible orientations for
998
+ the other edges. See [CGMRV16]_ for more details.
999
+
1000
+ INPUT:
1001
+
1002
+ - ``Dg`` -- the mixed graph. The undirected edges are doubly oriented
1003
+
1004
+ - ``V`` -- the set of vertices
1005
+
1006
+ - ``E`` -- the set of undirected edges (they are oriented in both ways);
1007
+ no labels are allowed
1008
+
1009
+ OUTPUT: an iterator which will produce all strong orientations of the input
1010
+ partially directed graph
1011
+
1012
+ EXAMPLES::
1013
+
1014
+ sage: from sage.graphs.orientations import _strong_orientations_of_a_mixed_graph
1015
+ sage: g = graphs.CycleGraph(5)
1016
+ sage: Dg = DiGraph(g) # all edges of g will be doubly oriented
1017
+ sage: it = _strong_orientations_of_a_mixed_graph(Dg, list(g), list(g.edges(labels=False, sort=False)))
1018
+ sage: len(list(it)) # there are two orientations of this multigraph
1019
+ 2
1020
+ """
1021
+ length = len(E)
1022
+ i = 0
1023
+ boundEdges = []
1024
+ while i < length:
1025
+ u, v = E[i]
1026
+ Dg.delete_edge(u, v)
1027
+ if v not in Dg.depth_first_search(u):
1028
+ # del E[i] in constant time
1029
+ E[i] = E[-1]
1030
+ E.pop()
1031
+ length -= 1
1032
+ Dg.add_edge(u, v)
1033
+ Dg.delete_edge(v, u)
1034
+ boundEdges.append((v, u))
1035
+ else:
1036
+ Dg.add_edge(u, v)
1037
+ Dg.delete_edge(v, u)
1038
+ if u not in Dg.depth_first_search(v):
1039
+ # del E[i] in constant time
1040
+ E[i] = E[-1]
1041
+ E.pop()
1042
+ length -= 1
1043
+ boundEdges.append((u, v))
1044
+ Dg.delete_edge(u, v)
1045
+ else:
1046
+ i += 1
1047
+ Dg.add_edge(v, u)
1048
+
1049
+ # if true the obtained orientation is strong
1050
+ if not E:
1051
+ yield Dg.copy()
1052
+ else:
1053
+ u, v = E.pop()
1054
+ Dg.delete_edge(v, u)
1055
+ for orientation in _strong_orientations_of_a_mixed_graph(Dg, V, E):
1056
+ yield orientation
1057
+ Dg.add_edge(v, u)
1058
+ Dg.delete_edge(u, v)
1059
+ for orientation in _strong_orientations_of_a_mixed_graph(Dg, V, E):
1060
+ yield orientation
1061
+ Dg.add_edge(u, v)
1062
+ E.append((u, v))
1063
+ Dg.add_edges(boundEdges)
1064
+ E.extend(boundEdges)
1065
+
1066
+
1067
+ def random_orientation(G):
1068
+ r"""
1069
+ Return a random orientation of a graph `G`.
1070
+
1071
+ An *orientation* of an undirected graph is a directed graph such that every
1072
+ edge is assigned a direction. Hence there are `2^m` oriented digraphs for a
1073
+ simple graph with `m` edges.
1074
+
1075
+ INPUT:
1076
+
1077
+ - ``G`` -- a Graph
1078
+
1079
+ EXAMPLES::
1080
+
1081
+ sage: from sage.graphs.orientations import random_orientation
1082
+ sage: G = graphs.PetersenGraph()
1083
+ sage: D = random_orientation(G)
1084
+ sage: D.order() == G.order(), D.size() == G.size()
1085
+ (True, True)
1086
+
1087
+ TESTS:
1088
+
1089
+ Giving anything else than a Graph::
1090
+
1091
+ sage: random_orientation([])
1092
+ Traceback (most recent call last):
1093
+ ...
1094
+ ValueError: the input parameter must be a Graph
1095
+
1096
+ .. SEEALSO::
1097
+
1098
+ - :meth:`~Graph.orientations`
1099
+ - :meth:`~Graph.strong_orientation`
1100
+ - :meth:`~sage.graphs.orientations.strong_orientations_iterator`
1101
+ - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.nauty_directg`
1102
+ """
1103
+ from sage.graphs.graph import Graph
1104
+ if not isinstance(G, Graph):
1105
+ raise ValueError("the input parameter must be a Graph")
1106
+
1107
+ from sage.misc.prandom import getrandbits
1108
+ rbits = getrandbits(G.size())
1109
+ edges = []
1110
+ for u, v, l in G.edge_iterator():
1111
+ edges.append((u, v, l) if rbits % 2 else (v, u, l))
1112
+ rbits >>= 1
1113
+
1114
+ return _initialize_digraph(G, edges, name=f"Random orientation of {G.name()}")
1115
+
1116
+
1117
+ def minimum_outdegree_orientation(G, use_edge_labels=False, solver=None, verbose=0,
1118
+ *, integrality_tolerance=1e-3):
1119
+ r"""
1120
+ Return an orientation of `G` with the smallest possible maximum outdegree.
1121
+
1122
+ Given a Graph `G`, it is polynomial to compute an orientation `D` of the
1123
+ edges of `G` such that the maximum out-degree in `D` is minimized. This
1124
+ problem, though, is NP-complete in the weighted case [AMOZ2006]_.
1125
+
1126
+ INPUT:
1127
+
1128
+ - ``G`` -- an undirected graph
1129
+
1130
+ - ``use_edge_labels`` -- boolean (default: ``False``)
1131
+
1132
+ - When set to ``True``, uses edge labels as weights to compute the
1133
+ orientation and assumes a weight of `1` when there is no value available
1134
+ for a given edge.
1135
+
1136
+ - When set to ``False`` (default), gives a weight of 1 to all the edges.
1137
+
1138
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
1139
+ Programming (MILP) solver to be used. If set to ``None``, the default one
1140
+ is used. For more information on MILP solvers and which default solver is
1141
+ used, see the method :meth:`solve
1142
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1143
+ :class:`MixedIntegerLinearProgram
1144
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1145
+
1146
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity. Set to 0
1147
+ by default, which means quiet.
1148
+
1149
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
1150
+ over an inexact base ring;
1151
+ see :meth:`MixedIntegerLinearProgram.get_values`.
1152
+
1153
+ EXAMPLES:
1154
+
1155
+ Given a complete bipartite graph `K_{n,m}`, the maximum out-degree of an
1156
+ optimal orientation is `\left\lceil \frac {nm} {n+m}\right\rceil`::
1157
+
1158
+ sage: g = graphs.CompleteBipartiteGraph(3,4)
1159
+ sage: o = g.minimum_outdegree_orientation() # needs sage.numerical.mip
1160
+ sage: max(o.out_degree()) == integer_ceil((4*3)/(3+4)) # needs sage.numerical.mip
1161
+ True
1162
+
1163
+ Show the influence of edge labels on the solution::
1164
+
1165
+ sage: # needs sage.numerical.mip
1166
+ sage: g = graphs.PetersenGraph()
1167
+ sage: o = g.minimum_outdegree_orientation(use_edge_labels=False)
1168
+ sage: max(o.out_degree())
1169
+ 2
1170
+ sage: _ = [g.set_edge_label(u, v, 1) for u, v in g.edge_iterator(labels=False)]
1171
+ sage: o = g.minimum_outdegree_orientation(use_edge_labels=True)
1172
+ sage: max(o.out_degree())
1173
+ 2
1174
+ sage: g.set_edge_label(0, 1, 100)
1175
+ sage: o = g.minimum_outdegree_orientation(use_edge_labels=True)
1176
+ sage: max(o.out_degree())
1177
+ 3
1178
+
1179
+ TESTS::
1180
+
1181
+ sage: from sage.graphs.orientations import minimum_outdegree_orientation
1182
+ sage: minimum_outdegree_orientation(DiGraph()) # needs sage.numerical.mip
1183
+ Traceback (most recent call last):
1184
+ ...
1185
+ ValueError: Cannot compute an orientation of a DiGraph.
1186
+ Please convert it to a Graph if you really mean it.
1187
+ """
1188
+ G._scream_if_not_simple()
1189
+ if G.is_directed():
1190
+ raise ValueError("Cannot compute an orientation of a DiGraph. "
1191
+ "Please convert it to a Graph if you really mean it.")
1192
+
1193
+ if use_edge_labels:
1194
+ def weight(e):
1195
+ label = G.edge_label(e[0], e[1])
1196
+ return _weight_if_real(label)
1197
+ else:
1198
+ weight = _weight_1
1199
+
1200
+ from sage.numerical.mip import MixedIntegerLinearProgram
1201
+
1202
+ p = MixedIntegerLinearProgram(maximization=False, solver=solver)
1203
+ degree = p.new_variable(nonnegative=True)
1204
+
1205
+ # The orientation of an edge is boolean and indicates whether the edge uv
1206
+ # goes from u to v ( equal to 0 ) or from v to u ( equal to 1)
1207
+ orientation = p.new_variable(binary=True)
1208
+
1209
+ # Whether an edge adjacent to a vertex u counts positively or negatively. To
1210
+ # do so, we first fix an arbitrary extremity per edge uv.
1211
+ ext = {frozenset(e): e[0] for e in G.edge_iterator(labels=False)}
1212
+
1213
+ def outgoing(u, e, variable):
1214
+ if u == ext[frozenset(e)]:
1215
+ return variable
1216
+ return 1 - variable
1217
+
1218
+ for u in G:
1219
+ p.add_constraint(p.sum(weight(e) * outgoing(u, e, orientation[frozenset(e)])
1220
+ for e in G.edge_iterator(vertices=[u], labels=False))
1221
+ - degree['max'], max=0)
1222
+
1223
+ p.set_objective(degree['max'])
1224
+
1225
+ p.solve(log=verbose)
1226
+
1227
+ orientation = p.get_values(orientation, convert=bool, tolerance=integrality_tolerance)
1228
+
1229
+ # Return the resulting orientation
1230
+ return G.orient(lambda e: e if orientation[frozenset(e[:2])] else (e[1], e[0], e[2]))
1231
+
1232
+
1233
+ def bounded_outdegree_orientation(G, bound, solver=None, verbose=False,
1234
+ *, integrality_tolerance=1e-3):
1235
+ r"""
1236
+ Return an orientation of `G` such that every vertex `v` has out-degree less
1237
+ than `b(v)`.
1238
+
1239
+ INPUT:
1240
+
1241
+ - ``G`` -- an undirected graph
1242
+
1243
+ - ``bound`` -- maximum bound on the out-degree. Can be of three
1244
+ different types :
1245
+
1246
+ * An integer `k`. In this case, computes an orientation whose maximum
1247
+ out-degree is less than `k`.
1248
+
1249
+ * A dictionary associating to each vertex its associated maximum
1250
+ out-degree.
1251
+
1252
+ * A function associating to each vertex its associated maximum
1253
+ out-degree.
1254
+
1255
+ - ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
1256
+ Programming (MILP) solver to be used. If set to ``None``, the default one
1257
+ is used. For more information on MILP solvers and which default solver is
1258
+ used, see the method :meth:`solve
1259
+ <sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1260
+ :class:`MixedIntegerLinearProgram
1261
+ <sage.numerical.mip.MixedIntegerLinearProgram>`.
1262
+
1263
+ - ``verbose`` -- integer (default: 0); sets the level of verbosity. Set to 0
1264
+ by default, which means quiet.
1265
+
1266
+ - ``integrality_tolerance`` -- float; parameter for use with MILP solvers
1267
+ over an inexact base ring;
1268
+ see :meth:`MixedIntegerLinearProgram.get_values`.
1269
+
1270
+ OUTPUT:
1271
+
1272
+ A DiGraph representing the orientation if it exists.
1273
+ A :exc:`ValueError` exception is raised otherwise.
1274
+
1275
+ ALGORITHM:
1276
+
1277
+ The problem is solved through a maximum flow :
1278
+
1279
+ Given a graph `G`, we create a ``DiGraph`` `D` defined on `E(G)\cup V(G)\cup
1280
+ \{s,t\}`. We then link `s` to all of `V(G)` (these edges having a capacity
1281
+ equal to the bound associated to each element of `V(G)`), and all the
1282
+ elements of `E(G)` to `t` . We then link each `v \in V(G)` to each of its
1283
+ incident edges in `G`. A maximum integer flow of value `|E(G)|` corresponds
1284
+ to an admissible orientation of `G`. Otherwise, none exists.
1285
+
1286
+ EXAMPLES:
1287
+
1288
+ There is always an orientation of a graph `G` such that a vertex `v` has
1289
+ out-degree at most `\lceil \frac {d(v)} 2 \rceil`::
1290
+
1291
+ sage: g = graphs.RandomGNP(40, .4)
1292
+ sage: b = lambda v: integer_ceil(g.degree(v)/2)
1293
+ sage: D = g.bounded_outdegree_orientation(b)
1294
+ sage: all(D.out_degree(v) <= b(v) for v in g)
1295
+ True
1296
+
1297
+ Chvatal's graph, being 4-regular, can be oriented in such a way that its
1298
+ maximum out-degree is 2::
1299
+
1300
+ sage: g = graphs.ChvatalGraph()
1301
+ sage: D = g.bounded_outdegree_orientation(2)
1302
+ sage: max(D.out_degree())
1303
+ 2
1304
+
1305
+ For any graph `G`, it is possible to compute an orientation such that
1306
+ the maximum out-degree is at most the maximum average degree of `G`
1307
+ divided by 2. Anything less, though, is impossible.
1308
+
1309
+ sage: g = graphs.RandomGNP(40, .4)
1310
+ sage: mad = g.maximum_average_degree() # needs sage.numerical.mip
1311
+
1312
+ Hence this is possible ::
1313
+
1314
+ sage: d = g.bounded_outdegree_orientation(integer_ceil(mad/2)) # needs sage.numerical.mip
1315
+
1316
+ While this is not::
1317
+
1318
+ sage: try: # needs sage.numerical.mip
1319
+ ....: g.bounded_outdegree_orientation(integer_ceil(mad/2-1))
1320
+ ....: print("Error")
1321
+ ....: except ValueError:
1322
+ ....: pass
1323
+
1324
+ The bounds can be specified in different ways::
1325
+
1326
+ sage: g = graphs.PetersenGraph()
1327
+ sage: b = lambda v: integer_ceil(g.degree(v)/2)
1328
+ sage: D = g.bounded_outdegree_orientation(b)
1329
+ sage: b_dict = {u: b(u) for u in g}
1330
+ sage: D = g.bounded_outdegree_orientation(b_dict)
1331
+ sage: unique_bound = 2
1332
+ sage: D = g.bounded_outdegree_orientation(unique_bound)
1333
+
1334
+ TESTS:
1335
+
1336
+ As previously for random graphs, but more intensively::
1337
+
1338
+ sage: for i in range(30): # long time (up to 6s on sage.math, 2012)
1339
+ ....: g = graphs.RandomGNP(40, .4)
1340
+ ....: b = lambda v: integer_ceil(g.degree(v)/2)
1341
+ ....: D = g.bounded_outdegree_orientation(b)
1342
+ ....: if not (
1343
+ ....: all( D.out_degree(v) <= b(v) for v in g ) or
1344
+ ....: D.size() != g.size()):
1345
+ ....: print("Something wrong happened")
1346
+
1347
+ Empty graph::
1348
+
1349
+ sage: Graph().bounded_outdegree_orientation(b)
1350
+ Digraph on 0 vertices
1351
+ """
1352
+ G._scream_if_not_simple()
1353
+ n = G.order()
1354
+
1355
+ if not n:
1356
+ return DiGraph()
1357
+
1358
+ vertices = list(G)
1359
+ vertices_id = {y: x for x, y in enumerate(vertices)}
1360
+
1361
+ b = {}
1362
+
1363
+ # Checking the input type. We make a dictionary out of it
1364
+ if isinstance(bound, dict):
1365
+ b = bound
1366
+ else:
1367
+ try:
1368
+ b = dict(zip(vertices, map(bound, vertices)))
1369
+ except TypeError:
1370
+ b = dict(zip(vertices, [bound]*n))
1371
+
1372
+ d = DiGraph()
1373
+
1374
+ # Adding the edges (s,v) and ((u,v),t)
1375
+ d.add_edges(('s', vertices_id[v], b[v]) for v in vertices)
1376
+
1377
+ d.add_edges(((vertices_id[u], vertices_id[v]), 't', 1)
1378
+ for u, v in G.edges(sort=False, labels=None))
1379
+
1380
+ # each v is linked to its incident edges
1381
+
1382
+ for u, v in G.edge_iterator(labels=None):
1383
+ u, v = vertices_id[u], vertices_id[v]
1384
+ d.add_edge(u, (u, v), 1)
1385
+ d.add_edge(v, (u, v), 1)
1386
+
1387
+ # Solving the maximum flow
1388
+ value, flow = d.flow('s', 't', value_only=False, integer=True,
1389
+ use_edge_labels=True, solver=solver, verbose=verbose,
1390
+ integrality_tolerance=integrality_tolerance)
1391
+
1392
+ if value != G.size():
1393
+ raise ValueError("No orientation exists for the given bound")
1394
+
1395
+ # The flow graph may not contain all the vertices, if they are
1396
+ # not part of the flow...
1397
+ edges = ((vertices[u], vertices[vv if vv != u else uu])
1398
+ for u in (x for x in range(n) if x in flow)
1399
+ for uu, vv in flow.neighbors_out(u))
1400
+
1401
+ return _initialize_digraph(G, edges)
1402
+
1403
+
1404
+ def eulerian_orientation(G):
1405
+ r"""
1406
+ Return an Eulerian orientation of the graph `G`.
1407
+
1408
+ An Eulerian graph being a graph such that any vertex has an even degree, an
1409
+ Eulerian orientation of a graph is an orientation of its edges such that
1410
+ each vertex `v` verifies `d^+(v)=d^-(v)=d(v)/2`, where `d^+` and `d^-`
1411
+ respectively represent the out-degree and the in-degree of a vertex.
1412
+
1413
+ If the graph is not Eulerian, the orientation verifies for any vertex `v`
1414
+ that `| d^+(v)-d^-(v) | \leq 1`.
1415
+
1416
+ INPUT:
1417
+
1418
+ - ``G`` -- an undirected graph
1419
+
1420
+ ALGORITHM:
1421
+
1422
+ This algorithm is a random walk through the edges of the graph, which
1423
+ orients the edges according to the walk. When a vertex is reached which has
1424
+ no non-oriented edge (this vertex must have odd degree), the walk resumes at
1425
+ another vertex of odd degree, if any.
1426
+
1427
+ This algorithm has time complexity in `O(n+m)` for ``SparseGraph`` and
1428
+ `O(n^2)` for ``DenseGraph``, where `m` is the number of edges in the graph
1429
+ and `n` is the number of vertices in the graph.
1430
+
1431
+ EXAMPLES:
1432
+
1433
+ The CubeGraph with parameter 4, which is regular of even degree, has an
1434
+ Eulerian orientation such that `d^+ = d^-`::
1435
+
1436
+ sage: g = graphs.CubeGraph(4)
1437
+ sage: g.degree()
1438
+ [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
1439
+ sage: o = g.eulerian_orientation()
1440
+ sage: o.in_degree()
1441
+ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
1442
+ sage: o.out_degree()
1443
+ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
1444
+
1445
+ Secondly, the Petersen Graph, which is 3 regular has an orientation such
1446
+ that the difference between `d^+` and `d^-` is at most 1::
1447
+
1448
+ sage: g = graphs.PetersenGraph()
1449
+ sage: o = g.eulerian_orientation()
1450
+ sage: o.in_degree()
1451
+ [2, 2, 2, 2, 2, 1, 1, 1, 1, 1]
1452
+ sage: o.out_degree()
1453
+ [1, 1, 1, 1, 1, 2, 2, 2, 2, 2]
1454
+
1455
+ TESTS::
1456
+
1457
+ sage: E0 = Graph(); E4 = Graph(4) # See trac #21741
1458
+ sage: E0.eulerian_orientation()
1459
+ Digraph on 0 vertices
1460
+ sage: E4.eulerian_orientation()
1461
+ Digraph on 4 vertices
1462
+ """
1463
+ d = _initialize_digraph(G, [], sparse=True, immutable=False)
1464
+
1465
+ if not G.size():
1466
+ return d
1467
+
1468
+ g = copy(G)
1469
+
1470
+ # list of vertices of odd degree
1471
+ odd = [x for x in g.vertex_iterator() if g.degree(x) % 2]
1472
+
1473
+ # Picks the first vertex, which is preferably an odd one
1474
+ if odd:
1475
+ v = odd.pop()
1476
+ else:
1477
+ v = next(g.edge_iterator(labels=None))[0]
1478
+ odd.append(v)
1479
+ # Stops when there is no edge left
1480
+ while True:
1481
+
1482
+ # If there is an edge adjacent to the current one
1483
+ if g.degree(v):
1484
+ e = next(g.edge_iterator(v))
1485
+ g.delete_edge(e)
1486
+ if e[0] != v:
1487
+ e = (e[1], e[0], e[2])
1488
+ d.add_edge(e)
1489
+ v = e[1]
1490
+
1491
+ # The current vertex is isolated
1492
+ else:
1493
+ odd.remove(v)
1494
+
1495
+ # jumps to another odd vertex if possible
1496
+ if odd:
1497
+ v = odd.pop()
1498
+ # Else jumps to an even vertex which is not isolated
1499
+ elif g.size():
1500
+ v = next(g.edge_iterator())[0]
1501
+ odd.append(v)
1502
+ # If there is none, we are done !
1503
+ else:
1504
+ return d