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