passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl

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