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.
- passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
- passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
- passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
- passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
- passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
- passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
- passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
- sage/all__sagemath_graphs.py +39 -0
- sage/combinat/abstract_tree.py +2723 -0
- sage/combinat/all__sagemath_graphs.py +34 -0
- sage/combinat/binary_tree.py +5306 -0
- sage/combinat/cluster_algebra_quiver/all.py +22 -0
- sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
- sage/combinat/cluster_algebra_quiver/interact.py +124 -0
- sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
- sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
- sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
- sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
- sage/combinat/designs/MOLS_handbook_data.py +570 -0
- sage/combinat/designs/all.py +58 -0
- sage/combinat/designs/bibd.py +1655 -0
- sage/combinat/designs/block_design.py +1071 -0
- sage/combinat/designs/covering_array.py +269 -0
- sage/combinat/designs/covering_design.py +530 -0
- sage/combinat/designs/database.py +5615 -0
- sage/combinat/designs/design_catalog.py +122 -0
- sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/designs_pyx.pxd +21 -0
- sage/combinat/designs/designs_pyx.pyx +993 -0
- sage/combinat/designs/difference_family.py +3951 -0
- sage/combinat/designs/difference_matrices.py +279 -0
- sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
- sage/combinat/designs/ext_rep.py +1064 -0
- sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
- sage/combinat/designs/group_divisible_designs.py +361 -0
- sage/combinat/designs/incidence_structures.py +2357 -0
- sage/combinat/designs/latin_squares.py +581 -0
- sage/combinat/designs/orthogonal_arrays.py +2244 -0
- sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
- sage/combinat/designs/resolvable_bibd.py +815 -0
- sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
- sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/subhypergraph_search.pyx +530 -0
- sage/combinat/designs/twographs.py +306 -0
- sage/combinat/finite_state_machine.py +14874 -0
- sage/combinat/finite_state_machine_generators.py +2006 -0
- sage/combinat/graph_path.py +448 -0
- sage/combinat/interval_posets.py +3908 -0
- sage/combinat/nu_tamari_lattice.py +269 -0
- sage/combinat/ordered_tree.py +1446 -0
- sage/combinat/posets/all.py +46 -0
- sage/combinat/posets/bubble_shuffle.py +247 -0
- sage/combinat/posets/cartesian_product.py +493 -0
- sage/combinat/posets/d_complete.py +182 -0
- sage/combinat/posets/elements.py +273 -0
- sage/combinat/posets/forest.py +30 -0
- sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +174 -0
- sage/combinat/posets/hasse_diagram.py +3672 -0
- sage/combinat/posets/hochschild_lattice.py +158 -0
- sage/combinat/posets/incidence_algebras.py +794 -0
- sage/combinat/posets/lattices.py +5117 -0
- sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/linear_extension_iterator.pyx +292 -0
- sage/combinat/posets/linear_extensions.py +1037 -0
- sage/combinat/posets/mobile.py +275 -0
- sage/combinat/posets/moebius_algebra.py +776 -0
- sage/combinat/posets/poset_examples.py +2178 -0
- sage/combinat/posets/posets.py +9360 -0
- sage/combinat/rooted_tree.py +1070 -0
- sage/combinat/shard_order.py +239 -0
- sage/combinat/tamari_lattices.py +384 -0
- sage/combinat/yang_baxter_graph.py +923 -0
- sage/databases/all__sagemath_graphs.py +1 -0
- sage/databases/knotinfo_db.py +1231 -0
- sage/ext_data/all__sagemath_graphs.py +1 -0
- sage/ext_data/graphs/graph_plot_js.html +330 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/graphs/all.py +42 -0
- sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/asteroidal_triples.pyx +320 -0
- sage/graphs/base/all.py +1 -0
- sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/boost_graph.pxd +106 -0
- sage/graphs/base/boost_graph.pyx +3045 -0
- sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/c_graph.pxd +106 -0
- sage/graphs/base/c_graph.pyx +5096 -0
- sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/dense_graph.pxd +28 -0
- sage/graphs/base/dense_graph.pyx +801 -0
- sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/graph_backends.pxd +5 -0
- sage/graphs/base/graph_backends.pyx +797 -0
- sage/graphs/base/overview.py +85 -0
- sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/sparse_graph.pxd +90 -0
- sage/graphs/base/sparse_graph.pyx +1653 -0
- sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_dense_graph.pxd +5 -0
- sage/graphs/base/static_dense_graph.pyx +1032 -0
- sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_backend.pxd +27 -0
- sage/graphs/base/static_sparse_backend.pyx +1583 -0
- sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_graph.pxd +37 -0
- sage/graphs/base/static_sparse_graph.pyx +1375 -0
- sage/graphs/bipartite_graph.py +2732 -0
- sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/centrality.pyx +1038 -0
- sage/graphs/cographs.py +519 -0
- sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/comparability.pyx +851 -0
- sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/connectivity.pxd +157 -0
- sage/graphs/connectivity.pyx +4813 -0
- sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/convexity_properties.pxd +16 -0
- sage/graphs/convexity_properties.pyx +870 -0
- sage/graphs/digraph.py +4754 -0
- sage/graphs/digraph_generators.py +1993 -0
- sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/distances_all_pairs.pxd +12 -0
- sage/graphs/distances_all_pairs.pyx +2938 -0
- sage/graphs/domination.py +1363 -0
- sage/graphs/dot2tex_utils.py +100 -0
- sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/edge_connectivity.pyx +1215 -0
- sage/graphs/generators/all.py +1 -0
- sage/graphs/generators/basic.py +1769 -0
- sage/graphs/generators/chessboard.py +538 -0
- sage/graphs/generators/classical_geometries.py +1611 -0
- sage/graphs/generators/degree_sequence.py +235 -0
- sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generators/distance_regular.pyx +2846 -0
- sage/graphs/generators/families.py +4759 -0
- sage/graphs/generators/intersection.py +565 -0
- sage/graphs/generators/platonic_solids.py +262 -0
- sage/graphs/generators/random.py +2623 -0
- sage/graphs/generators/smallgraphs.py +5741 -0
- sage/graphs/generators/world_map.py +724 -0
- sage/graphs/generic_graph.py +26867 -0
- sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generic_graph_pyx.pxd +34 -0
- sage/graphs/generic_graph_pyx.pyx +1673 -0
- sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/genus.pyx +622 -0
- sage/graphs/graph.py +9645 -0
- sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_coloring.pyx +2284 -0
- sage/graphs/graph_database.py +1177 -0
- sage/graphs/graph_decompositions/all.py +1 -0
- sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
- sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
- sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
- sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +508 -0
- sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
- sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
- sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
- sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
- sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
- sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
- sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
- sage/graphs/graph_editor.py +82 -0
- sage/graphs/graph_generators.py +3314 -0
- sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_generators_pyx.pyx +95 -0
- sage/graphs/graph_input.py +812 -0
- sage/graphs/graph_latex.py +2064 -0
- sage/graphs/graph_list.py +410 -0
- sage/graphs/graph_plot.py +1756 -0
- sage/graphs/graph_plot_js.py +338 -0
- sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/hyperbolicity.pyx +1704 -0
- sage/graphs/hypergraph_generators.py +364 -0
- sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/independent_sets.pxd +13 -0
- sage/graphs/independent_sets.pyx +402 -0
- sage/graphs/isgci.py +1033 -0
- sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +489 -0
- sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/line_graph.pyx +743 -0
- sage/graphs/lovasz_theta.py +77 -0
- sage/graphs/matching.py +1633 -0
- sage/graphs/matching_covered_graph.py +3590 -0
- sage/graphs/orientations.py +1489 -0
- sage/graphs/partial_cube.py +459 -0
- sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/path_enumeration.pyx +2040 -0
- sage/graphs/pq_trees.py +1129 -0
- sage/graphs/print_graphs.py +201 -0
- sage/graphs/schnyder.py +865 -0
- sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/spanning_tree.pyx +1457 -0
- sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/strongly_regular_db.pyx +3340 -0
- sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/traversals.pxd +9 -0
- sage/graphs/traversals.pyx +1872 -0
- sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/trees.pxd +15 -0
- sage/graphs/trees.pyx +310 -0
- sage/graphs/tutte_polynomial.py +713 -0
- sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/views.pyx +794 -0
- sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/weakly_chordal.pyx +604 -0
- sage/groups/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
- sage/knots/all.py +6 -0
- sage/knots/free_knotinfo_monoid.py +507 -0
- sage/knots/gauss_code.py +291 -0
- sage/knots/knot.py +682 -0
- sage/knots/knot_table.py +284 -0
- sage/knots/knotinfo.py +2900 -0
- sage/knots/link.py +4715 -0
- sage/sandpiles/all.py +13 -0
- sage/sandpiles/examples.py +225 -0
- sage/sandpiles/sandpile.py +6365 -0
- sage/topology/all.py +22 -0
- sage/topology/cell_complex.py +1214 -0
- sage/topology/cubical_complex.py +1976 -0
- sage/topology/delta_complex.py +1806 -0
- sage/topology/filtered_simplicial_complex.py +744 -0
- sage/topology/moment_angle_complex.py +823 -0
- sage/topology/simplicial_complex.py +5160 -0
- sage/topology/simplicial_complex_catalog.py +92 -0
- sage/topology/simplicial_complex_examples.py +1680 -0
- sage/topology/simplicial_complex_homset.py +205 -0
- sage/topology/simplicial_complex_morphism.py +836 -0
- sage/topology/simplicial_set.py +4102 -0
- sage/topology/simplicial_set_catalog.py +55 -0
- sage/topology/simplicial_set_constructions.py +2954 -0
- sage/topology/simplicial_set_examples.py +865 -0
- 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
|