passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
- passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
- passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
- passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
- passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
- passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
- passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
- sage/all__sagemath_graphs.py +39 -0
- sage/combinat/abstract_tree.py +2723 -0
- sage/combinat/all__sagemath_graphs.py +34 -0
- sage/combinat/binary_tree.py +5306 -0
- sage/combinat/cluster_algebra_quiver/all.py +22 -0
- sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
- sage/combinat/cluster_algebra_quiver/interact.py +124 -0
- sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
- sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
- sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
- sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
- sage/combinat/designs/MOLS_handbook_data.py +570 -0
- sage/combinat/designs/all.py +58 -0
- sage/combinat/designs/bibd.py +1655 -0
- sage/combinat/designs/block_design.py +1071 -0
- sage/combinat/designs/covering_array.py +269 -0
- sage/combinat/designs/covering_design.py +530 -0
- sage/combinat/designs/database.py +5615 -0
- sage/combinat/designs/design_catalog.py +122 -0
- sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/designs_pyx.pxd +21 -0
- sage/combinat/designs/designs_pyx.pyx +993 -0
- sage/combinat/designs/difference_family.py +3951 -0
- sage/combinat/designs/difference_matrices.py +279 -0
- sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
- sage/combinat/designs/ext_rep.py +1064 -0
- sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
- sage/combinat/designs/group_divisible_designs.py +361 -0
- sage/combinat/designs/incidence_structures.py +2357 -0
- sage/combinat/designs/latin_squares.py +581 -0
- sage/combinat/designs/orthogonal_arrays.py +2244 -0
- sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
- sage/combinat/designs/resolvable_bibd.py +815 -0
- sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
- sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/subhypergraph_search.pyx +530 -0
- sage/combinat/designs/twographs.py +306 -0
- sage/combinat/finite_state_machine.py +14874 -0
- sage/combinat/finite_state_machine_generators.py +2006 -0
- sage/combinat/graph_path.py +448 -0
- sage/combinat/interval_posets.py +3908 -0
- sage/combinat/nu_tamari_lattice.py +269 -0
- sage/combinat/ordered_tree.py +1446 -0
- sage/combinat/posets/all.py +46 -0
- sage/combinat/posets/bubble_shuffle.py +247 -0
- sage/combinat/posets/cartesian_product.py +493 -0
- sage/combinat/posets/d_complete.py +182 -0
- sage/combinat/posets/elements.py +273 -0
- sage/combinat/posets/forest.py +30 -0
- sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +174 -0
- sage/combinat/posets/hasse_diagram.py +3672 -0
- sage/combinat/posets/hochschild_lattice.py +158 -0
- sage/combinat/posets/incidence_algebras.py +794 -0
- sage/combinat/posets/lattices.py +5117 -0
- sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/linear_extension_iterator.pyx +292 -0
- sage/combinat/posets/linear_extensions.py +1037 -0
- sage/combinat/posets/mobile.py +275 -0
- sage/combinat/posets/moebius_algebra.py +776 -0
- sage/combinat/posets/poset_examples.py +2178 -0
- sage/combinat/posets/posets.py +9360 -0
- sage/combinat/rooted_tree.py +1070 -0
- sage/combinat/shard_order.py +239 -0
- sage/combinat/tamari_lattices.py +384 -0
- sage/combinat/yang_baxter_graph.py +923 -0
- sage/databases/all__sagemath_graphs.py +1 -0
- sage/databases/knotinfo_db.py +1231 -0
- sage/ext_data/all__sagemath_graphs.py +1 -0
- sage/ext_data/graphs/graph_plot_js.html +330 -0
- sage/ext_data/kenzo/CP2.txt +45 -0
- sage/ext_data/kenzo/CP3.txt +349 -0
- sage/ext_data/kenzo/CP4.txt +4774 -0
- sage/ext_data/kenzo/README.txt +49 -0
- sage/ext_data/kenzo/S4.txt +20 -0
- sage/graphs/all.py +42 -0
- sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/asteroidal_triples.pyx +320 -0
- sage/graphs/base/all.py +1 -0
- sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/boost_graph.pxd +106 -0
- sage/graphs/base/boost_graph.pyx +3045 -0
- sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/c_graph.pxd +106 -0
- sage/graphs/base/c_graph.pyx +5096 -0
- sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/dense_graph.pxd +28 -0
- sage/graphs/base/dense_graph.pyx +801 -0
- sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/graph_backends.pxd +5 -0
- sage/graphs/base/graph_backends.pyx +797 -0
- sage/graphs/base/overview.py +85 -0
- sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/sparse_graph.pxd +90 -0
- sage/graphs/base/sparse_graph.pyx +1653 -0
- sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_dense_graph.pxd +5 -0
- sage/graphs/base/static_dense_graph.pyx +1032 -0
- sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_backend.pxd +27 -0
- sage/graphs/base/static_sparse_backend.pyx +1583 -0
- sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_graph.pxd +37 -0
- sage/graphs/base/static_sparse_graph.pyx +1375 -0
- sage/graphs/bipartite_graph.py +2732 -0
- sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/centrality.pyx +1038 -0
- sage/graphs/cographs.py +519 -0
- sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/comparability.pyx +851 -0
- sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/connectivity.pxd +157 -0
- sage/graphs/connectivity.pyx +4813 -0
- sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/convexity_properties.pxd +16 -0
- sage/graphs/convexity_properties.pyx +870 -0
- sage/graphs/digraph.py +4754 -0
- sage/graphs/digraph_generators.py +1993 -0
- sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/distances_all_pairs.pxd +12 -0
- sage/graphs/distances_all_pairs.pyx +2938 -0
- sage/graphs/domination.py +1363 -0
- sage/graphs/dot2tex_utils.py +100 -0
- sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/edge_connectivity.pyx +1215 -0
- sage/graphs/generators/all.py +1 -0
- sage/graphs/generators/basic.py +1769 -0
- sage/graphs/generators/chessboard.py +538 -0
- sage/graphs/generators/classical_geometries.py +1611 -0
- sage/graphs/generators/degree_sequence.py +235 -0
- sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generators/distance_regular.pyx +2846 -0
- sage/graphs/generators/families.py +4759 -0
- sage/graphs/generators/intersection.py +565 -0
- sage/graphs/generators/platonic_solids.py +262 -0
- sage/graphs/generators/random.py +2623 -0
- sage/graphs/generators/smallgraphs.py +5741 -0
- sage/graphs/generators/world_map.py +724 -0
- sage/graphs/generic_graph.py +26867 -0
- sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/generic_graph_pyx.pxd +34 -0
- sage/graphs/generic_graph_pyx.pyx +1673 -0
- sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/genus.pyx +622 -0
- sage/graphs/graph.py +9645 -0
- sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_coloring.pyx +2284 -0
- sage/graphs/graph_database.py +1177 -0
- sage/graphs/graph_decompositions/all.py +1 -0
- sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
- sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
- sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
- sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +508 -0
- sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
- sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
- sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
- sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
- sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
- sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
- sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
- sage/graphs/graph_editor.py +82 -0
- sage/graphs/graph_generators.py +3314 -0
- sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_generators_pyx.pyx +95 -0
- sage/graphs/graph_input.py +812 -0
- sage/graphs/graph_latex.py +2064 -0
- sage/graphs/graph_list.py +410 -0
- sage/graphs/graph_plot.py +1756 -0
- sage/graphs/graph_plot_js.py +338 -0
- sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/hyperbolicity.pyx +1704 -0
- sage/graphs/hypergraph_generators.py +364 -0
- sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/independent_sets.pxd +13 -0
- sage/graphs/independent_sets.pyx +402 -0
- sage/graphs/isgci.py +1033 -0
- sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +489 -0
- sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/line_graph.pyx +743 -0
- sage/graphs/lovasz_theta.py +77 -0
- sage/graphs/matching.py +1633 -0
- sage/graphs/matching_covered_graph.py +3590 -0
- sage/graphs/orientations.py +1489 -0
- sage/graphs/partial_cube.py +459 -0
- sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/path_enumeration.pyx +2040 -0
- sage/graphs/pq_trees.py +1129 -0
- sage/graphs/print_graphs.py +201 -0
- sage/graphs/schnyder.py +865 -0
- sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/spanning_tree.pyx +1457 -0
- sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/strongly_regular_db.pyx +3340 -0
- sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/traversals.pxd +9 -0
- sage/graphs/traversals.pyx +1872 -0
- sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/trees.pxd +15 -0
- sage/graphs/trees.pyx +310 -0
- sage/graphs/tutte_polynomial.py +713 -0
- sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/views.pyx +794 -0
- sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/graphs/weakly_chordal.pyx +604 -0
- sage/groups/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
- sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
- sage/knots/all.py +6 -0
- sage/knots/free_knotinfo_monoid.py +507 -0
- sage/knots/gauss_code.py +291 -0
- sage/knots/knot.py +682 -0
- sage/knots/knot_table.py +284 -0
- sage/knots/knotinfo.py +2900 -0
- sage/knots/link.py +4715 -0
- sage/sandpiles/all.py +13 -0
- sage/sandpiles/examples.py +225 -0
- sage/sandpiles/sandpile.py +6365 -0
- sage/topology/all.py +22 -0
- sage/topology/cell_complex.py +1214 -0
- sage/topology/cubical_complex.py +1976 -0
- sage/topology/delta_complex.py +1806 -0
- sage/topology/filtered_simplicial_complex.py +744 -0
- sage/topology/moment_angle_complex.py +823 -0
- sage/topology/simplicial_complex.py +5160 -0
- sage/topology/simplicial_complex_catalog.py +92 -0
- sage/topology/simplicial_complex_examples.py +1680 -0
- sage/topology/simplicial_complex_homset.py +205 -0
- sage/topology/simplicial_complex_morphism.py +836 -0
- sage/topology/simplicial_set.py +4102 -0
- sage/topology/simplicial_set_catalog.py +55 -0
- sage/topology/simplicial_set_constructions.py +2954 -0
- sage/topology/simplicial_set_examples.py +865 -0
- sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,1872 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
# cython: binding=True
|
3
|
+
# distutils: language = c++
|
4
|
+
# distutils: extra_compile_args = -std=c++11
|
5
|
+
r"""
|
6
|
+
Graph traversals
|
7
|
+
|
8
|
+
**This module implements the following graph traversals**
|
9
|
+
|
10
|
+
.. csv-table::
|
11
|
+
:class: contentstable
|
12
|
+
:widths: 30, 70
|
13
|
+
:delim: |
|
14
|
+
|
15
|
+
:meth:`~lex_BFS` | Perform a lexicographic breadth first search (LexBFS) on the graph.
|
16
|
+
:meth:`~lex_DFS` | Perform a lexicographic depth first search (LexDFS) on the graph.
|
17
|
+
:meth:`~lex_UP` | Perform a lexicographic UP search (LexUP) on the graph.
|
18
|
+
:meth:`~lex_DOWN` | Perform a lexicographic DOWN search (LexDOWN) on the graph.
|
19
|
+
:meth:`~lex_M` | Return an ordering of the vertices according the LexM graph traversal.
|
20
|
+
:meth:`~lex_M_slow` | Return an ordering of the vertices according the LexM graph traversal.
|
21
|
+
:meth:`~lex_M_fast` | Return an ordering of the vertices according the LexM graph traversal.
|
22
|
+
:meth:`~maximum_cardinality_search` | Return an ordering of the vertices according a maximum cardinality search.
|
23
|
+
:meth:`~maximum_cardinality_search_M` | Return the ordering and the edges of the triangulation produced by MCS-M.
|
24
|
+
|
25
|
+
|
26
|
+
ALGORITHM:
|
27
|
+
|
28
|
+
For :meth:`~lex_BFS` with ``algorithm="slow"``, :meth:`~lex_DFS`,
|
29
|
+
:meth:`~lex_UP` and :meth:`~lex_DOWN` the same generic implementation is used.
|
30
|
+
It corresponds to an implementation the generic algorithm described in
|
31
|
+
"Algorithm 1" of [Mil2017]_.
|
32
|
+
|
33
|
+
This algorithm maintains for each vertex left in the graph a lexicographic label
|
34
|
+
corresponding to the vertices already removed. The vertex of maximal
|
35
|
+
lexicographic label is then removed, and the lexicographic labels of its
|
36
|
+
neighbors are updated. Depending on how the update is done, it corresponds to
|
37
|
+
LexBFS, LexUP, LexDFS or LexDOWN: during the `i`-th iteration of the algorithm
|
38
|
+
`n-i` (for LexBFS and LexDOWN) or `i` (for LexDFS and LexUP) is appended (for
|
39
|
+
LexBFS and LexUP) or prepended (for LexDFS and LexDOWN) to the lexicographic
|
40
|
+
labels of all neighbors of the selected vertex that are left in the graph.
|
41
|
+
|
42
|
+
The time complexity of the algorithm is `O(mn)` for ``SparseGraph`` and
|
43
|
+
`O(\max\{mn, n^2\})` for ``DenseGraph``, where `n` is the number of vertices
|
44
|
+
and `m` is the number of edges.
|
45
|
+
|
46
|
+
See [CK2008]_ and [Mil2017]_ for more details on the algorithm and graphs
|
47
|
+
searching.
|
48
|
+
|
49
|
+
Methods
|
50
|
+
-------
|
51
|
+
"""
|
52
|
+
# ****************************************************************************
|
53
|
+
# Copyright (C) 2019 Georgios Giapitzakis Tzintanos <giorgosgiapis@mail.com>
|
54
|
+
# David Coudert <david.coudert@inria.fr>
|
55
|
+
# 2024 Cyril Bouvier <cyril.bouvier@lirmm.fr>
|
56
|
+
#
|
57
|
+
# This program is free software: you can redistribute it and/or modify
|
58
|
+
# it under the terms of the GNU General Public License as published by
|
59
|
+
# the Free Software Foundation, either version 2 of the License, or
|
60
|
+
# (at your option) any later version.
|
61
|
+
# https://www.gnu.org/licenses/
|
62
|
+
# ****************************************************************************
|
63
|
+
|
64
|
+
from collections import deque
|
65
|
+
|
66
|
+
from libc.stdint cimport uint32_t
|
67
|
+
from libcpp.vector cimport vector
|
68
|
+
from cysignals.signals cimport sig_on, sig_off
|
69
|
+
from memory_allocator cimport MemoryAllocator
|
70
|
+
|
71
|
+
from sage.data_structures.pairing_heap cimport PairingHeap_of_n_integers
|
72
|
+
from sage.graphs.base.c_graph cimport CGraph, CGraphBackend
|
73
|
+
from sage.graphs.base.static_sparse_backend cimport StaticSparseCGraph
|
74
|
+
from sage.graphs.base.static_sparse_backend cimport StaticSparseBackend
|
75
|
+
from sage.graphs.base.static_sparse_graph cimport init_short_digraph
|
76
|
+
from sage.graphs.base.static_sparse_graph cimport free_short_digraph
|
77
|
+
from sage.graphs.graph_decompositions.slice_decomposition cimport \
|
78
|
+
extended_lex_BFS
|
79
|
+
|
80
|
+
|
81
|
+
def _lex_order_common(G, algo, reverse, tree, initial_vertex):
|
82
|
+
r"""
|
83
|
+
Perform a lexicographic search (LexBFS, LexUP, LexDFS or LexDOWN) on the
|
84
|
+
graph.
|
85
|
+
|
86
|
+
INPUT:
|
87
|
+
|
88
|
+
- ``G`` -- a sage graph
|
89
|
+
|
90
|
+
- ``algo`` -- string; the name of the actual algorithm among:
|
91
|
+
|
92
|
+
- ``"lex_BFS"``
|
93
|
+
|
94
|
+
- ``"lex_UP"``
|
95
|
+
|
96
|
+
- ``"lex_DFS"``
|
97
|
+
|
98
|
+
- ``"lex_DOWN"``
|
99
|
+
|
100
|
+
- ``reverse`` -- whether to return the vertices in discovery order, or the
|
101
|
+
reverse
|
102
|
+
|
103
|
+
- ``tree`` -- whether to return the discovery directed tree (each vertex
|
104
|
+
being linked to the one that saw it last)
|
105
|
+
|
106
|
+
- ``initial_vertex`` -- the first vertex to consider, can be None
|
107
|
+
|
108
|
+
.. NOTE::
|
109
|
+
|
110
|
+
Loops and multiple edges are ignored and directed graphs are considered
|
111
|
+
as undirected graphs.
|
112
|
+
|
113
|
+
ALGORITHM:
|
114
|
+
|
115
|
+
See the documentation of the :mod:`~sage.graphs.traversals` module.
|
116
|
+
|
117
|
+
TESTS:
|
118
|
+
|
119
|
+
Lex ordering of a graph on one vertex::
|
120
|
+
|
121
|
+
sage: Graph(1).lex_BFS(tree=True, algorithm="slow")
|
122
|
+
([0], Digraph on 1 vertex)
|
123
|
+
sage: Graph(1).lex_UP(tree=True)
|
124
|
+
([0], Digraph on 1 vertex)
|
125
|
+
sage: Graph(1).lex_DFS(tree=True)
|
126
|
+
([0], Digraph on 1 vertex)
|
127
|
+
sage: Graph(1).lex_DOWN(tree=True)
|
128
|
+
([0], Digraph on 1 vertex)
|
129
|
+
|
130
|
+
Lex ordering of an empty (di)graph is an empty sequence::
|
131
|
+
|
132
|
+
sage: g = Graph()
|
133
|
+
sage: g.lex_BFS(algorithm="slow")
|
134
|
+
[]
|
135
|
+
sage: g.lex_BFS(algorithm="slow", tree=True)
|
136
|
+
([], Digraph on 0 vertices)
|
137
|
+
sage: g.lex_UP()
|
138
|
+
[]
|
139
|
+
sage: g.lex_UP(tree=True)
|
140
|
+
([], Digraph on 0 vertices)
|
141
|
+
sage: g.lex_DFS()
|
142
|
+
[]
|
143
|
+
sage: g.lex_DFS(tree=True)
|
144
|
+
([], Digraph on 0 vertices)
|
145
|
+
sage: g.lex_DOWN()
|
146
|
+
[]
|
147
|
+
sage: g.lex_DFS(tree=True)
|
148
|
+
([], Digraph on 0 vertices)
|
149
|
+
|
150
|
+
Lex UP ordering of a symmetric digraph should be the same as the Lex UP
|
151
|
+
ordering of the corresponding undirected graph::
|
152
|
+
|
153
|
+
sage: G = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)])
|
154
|
+
sage: H = DiGraph(G)
|
155
|
+
sage: G.lex_BFS(algorithm="slow") == H.lex_BFS(algorithm="slow")
|
156
|
+
True
|
157
|
+
sage: G.lex_UP() == H.lex_UP()
|
158
|
+
True
|
159
|
+
sage: G.lex_DFS() == H.lex_DFS()
|
160
|
+
True
|
161
|
+
sage: G.lex_DOWN() == H.lex_DOWN()
|
162
|
+
True
|
163
|
+
|
164
|
+
``initial_vertex`` should be a valid graph vertex::
|
165
|
+
|
166
|
+
sage: G = graphs.CompleteGraph(6)
|
167
|
+
sage: G.lex_BFS(initial_vertex='foo', algorithm="slow")
|
168
|
+
Traceback (most recent call last):
|
169
|
+
...
|
170
|
+
ValueError: 'foo' is not a graph vertex
|
171
|
+
sage: G.lex_UP(initial_vertex='foo')
|
172
|
+
Traceback (most recent call last):
|
173
|
+
...
|
174
|
+
ValueError: 'foo' is not a graph vertex
|
175
|
+
sage: G.lex_DFS(initial_vertex='foo')
|
176
|
+
Traceback (most recent call last):
|
177
|
+
...
|
178
|
+
ValueError: 'foo' is not a graph vertex
|
179
|
+
sage: G.lex_DOWN(initial_vertex='foo')
|
180
|
+
Traceback (most recent call last):
|
181
|
+
...
|
182
|
+
ValueError: 'foo' is not a graph vertex
|
183
|
+
"""
|
184
|
+
if initial_vertex is not None and initial_vertex not in G:
|
185
|
+
raise ValueError(f"'{initial_vertex}' is not a graph vertex")
|
186
|
+
|
187
|
+
if algo not in ("lex_BFS", "lex_UP", "lex_DFS", "lex_DOWN"):
|
188
|
+
raise ValueError(f"unknown algorithm '{algo}'")
|
189
|
+
|
190
|
+
cdef size_t n = G.order()
|
191
|
+
cdef list sigma = []
|
192
|
+
cdef dict predecessor = {}
|
193
|
+
|
194
|
+
cdef bint right = algo in ("lex_BFS", "lex_UP")
|
195
|
+
cdef bint decr = algo in ("lex_BFS", "lex_DOWN")
|
196
|
+
|
197
|
+
cdef size_t cur_label = n if decr else -1
|
198
|
+
cdef int label_incr = -1 if decr else 1
|
199
|
+
|
200
|
+
# Perform the search
|
201
|
+
lexicographic_label = {u: deque() for u in G}
|
202
|
+
if initial_vertex is not None:
|
203
|
+
# append or appendleft does not matter here, as the deque is empty
|
204
|
+
lexicographic_label[initial_vertex].append(cur_label)
|
205
|
+
while lexicographic_label:
|
206
|
+
u = max(lexicographic_label, key=lexicographic_label.get)
|
207
|
+
lexicographic_label.pop(u)
|
208
|
+
sigma.append(u)
|
209
|
+
cur_label += label_incr
|
210
|
+
for v in G.neighbor_iterator(u): # graphs are considered undirected
|
211
|
+
if v in lexicographic_label:
|
212
|
+
if right:
|
213
|
+
lexicographic_label[v].append(cur_label)
|
214
|
+
else:
|
215
|
+
lexicographic_label[v].appendleft(cur_label)
|
216
|
+
predecessor[v] = u
|
217
|
+
|
218
|
+
if reverse:
|
219
|
+
sigma.reverse()
|
220
|
+
|
221
|
+
if tree:
|
222
|
+
from sage.graphs.digraph import DiGraph
|
223
|
+
edges = predecessor.items()
|
224
|
+
g = DiGraph([G, edges], format='vertices_and_edges', sparse=True)
|
225
|
+
return sigma, g
|
226
|
+
return sigma
|
227
|
+
|
228
|
+
|
229
|
+
def _is_valid_lex_BFS_order(G, L):
|
230
|
+
r"""
|
231
|
+
Check whether ``L`` is a valid LexBFS ordering of the vertices of ``G``.
|
232
|
+
|
233
|
+
Given two vertices `a` and `b` of `G = (V, E)`, we write `a < b` if `a` has
|
234
|
+
a smaller label than `b`, and so if `a` is after `b` in the ordering `L`.
|
235
|
+
It is proved in [DNB1996]_ that any LexBFS ordering satisfies that,
|
236
|
+
if `a < b < c` and `ac \in E` and `bc \not\in E`, then there exists `d\in V`
|
237
|
+
such that `c < d`, `db \in E` and `da \not\in E`.
|
238
|
+
|
239
|
+
INPUT:
|
240
|
+
|
241
|
+
- ``G`` -- a sage Graph
|
242
|
+
|
243
|
+
- ``L`` -- list; an ordering of the vertices of `G`
|
244
|
+
|
245
|
+
OUTPUT:
|
246
|
+
|
247
|
+
- ``True`` if ``L`` is a LexBFS ordering of ``G``; ``False`` otherwise
|
248
|
+
|
249
|
+
.. NOTE::
|
250
|
+
|
251
|
+
Loops and multiple edges are ignored for LexBFS ordering and directed
|
252
|
+
graphs are considered as undirected graphs.
|
253
|
+
|
254
|
+
.. SEEALSO::
|
255
|
+
|
256
|
+
* :wikipedia:`Lexicographic_breadth-first_search`
|
257
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_BFS` -- perform a
|
258
|
+
lexicographic depth first search (LexBFS) on the graph
|
259
|
+
|
260
|
+
TESTS::
|
261
|
+
|
262
|
+
sage: from sage.graphs.traversals import _is_valid_lex_BFS_order
|
263
|
+
sage: G = graphs.PathGraph(3)
|
264
|
+
sage: _is_valid_lex_BFS_order(G, [0, 1, 2])
|
265
|
+
True
|
266
|
+
sage: _is_valid_lex_BFS_order(G, [0, 2, 1])
|
267
|
+
False
|
268
|
+
|
269
|
+
sage: G = DiGraph("I?O@??A?CCA?A??C??")
|
270
|
+
sage: _is_valid_lex_BFS_order(G, [0, 7, 1, 2, 3, 4, 5, 8, 6, 9])
|
271
|
+
False
|
272
|
+
sage: _is_valid_lex_BFS_order(G, G.lex_BFS())
|
273
|
+
True
|
274
|
+
sage: H = G.to_undirected()
|
275
|
+
sage: _is_valid_lex_BFS_order(H, G.lex_BFS())
|
276
|
+
True
|
277
|
+
sage: _is_valid_lex_BFS_order(G, H.lex_BFS())
|
278
|
+
True
|
279
|
+
"""
|
280
|
+
# Convert G to a simple undirected graph
|
281
|
+
if G.has_loops() or G.has_multiple_edges() or G.is_directed():
|
282
|
+
G = G.to_simple(immutable=False, to_undirected=True)
|
283
|
+
|
284
|
+
cdef int n = G.order()
|
285
|
+
|
286
|
+
if set(L) != set(G):
|
287
|
+
return False
|
288
|
+
|
289
|
+
cdef dict L_inv = {u: i for i, u in enumerate(L)}
|
290
|
+
cdef int pos_a, pos_b, pos_c
|
291
|
+
|
292
|
+
for pos_a in range(n - 1, -1, -1):
|
293
|
+
a = L[pos_a]
|
294
|
+
for c in G.neighbor_iterator(a):
|
295
|
+
pos_c = L_inv[c]
|
296
|
+
if pos_c > pos_a:
|
297
|
+
continue
|
298
|
+
for pos_b in range(pos_c + 1, pos_a):
|
299
|
+
b = L[pos_b]
|
300
|
+
if G.has_edge(c, b):
|
301
|
+
continue
|
302
|
+
if any(L_inv[d] < pos_c and not G.has_edge(d, a)
|
303
|
+
for d in G.neighbor_iterator(b)):
|
304
|
+
# The condition is satisfied for a < b < c
|
305
|
+
continue
|
306
|
+
return False
|
307
|
+
return True
|
308
|
+
|
309
|
+
|
310
|
+
def lex_BFS(G, reverse=False, tree=False, initial_vertex=None, algorithm="fast"):
|
311
|
+
r"""
|
312
|
+
Perform a lexicographic breadth first search (LexBFS) on the graph.
|
313
|
+
|
314
|
+
INPUT:
|
315
|
+
|
316
|
+
- ``G`` -- a sage graph
|
317
|
+
|
318
|
+
- ``reverse`` -- boolean (default: ``False``); whether to return the
|
319
|
+
vertices in discovery order, or the reverse
|
320
|
+
|
321
|
+
- ``tree`` -- boolean (default: ``False``); whether to return the
|
322
|
+
discovery directed tree (each vertex being linked to the one that saw
|
323
|
+
it last)
|
324
|
+
|
325
|
+
- ``initial_vertex`` -- (default: ``None``) the first vertex to
|
326
|
+
consider
|
327
|
+
|
328
|
+
- ``algorithm`` -- string (default: ``'fast'``); algorithm to use among:
|
329
|
+
|
330
|
+
- ``'slow'`` -- it use the generic algorithm for all the lexicographic
|
331
|
+
searchs. See the documentation of the :mod:`~sage.graphs.traversals`
|
332
|
+
module for more details.
|
333
|
+
|
334
|
+
- ``'fast'`` -- this algorithm uses the notion of *partition refinement*
|
335
|
+
to determine the position of the vertices in the ordering. The time
|
336
|
+
complexity of this algorithm is in `O(n + m)`, and our implementation
|
337
|
+
follows that complexity for ``SparseGraph``. For ``DenseGraph``,
|
338
|
+
the complexity is `O(n^2)`. See [HMPV2000]_ and [TCHP2008]_ for more
|
339
|
+
details. This algorithm is also used to compute slice decompositions of
|
340
|
+
undirected graphs, a more thorough description can be found in the
|
341
|
+
documentation of the
|
342
|
+
:mod:`~sage.graphs.graph_decompositions.slice_decomposition` module.
|
343
|
+
|
344
|
+
.. NOTE::
|
345
|
+
|
346
|
+
Loops and multiple edges are ignored during the computation of
|
347
|
+
``lex_BFS`` and directed graphs are converted to undirected graphs.
|
348
|
+
|
349
|
+
.. SEEALSO::
|
350
|
+
|
351
|
+
* :wikipedia:`Lexicographic_breadth-first_search`
|
352
|
+
* :mod:`~sage.graphs.graph_decompositions.slice_decomposition` module
|
353
|
+
and :meth:`~sage.graphs.graph.Graph.slice_decomposition` -- compute a
|
354
|
+
slice decomposition of the graph using an extended lex BFS algorithm
|
355
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_DFS` -- perform a
|
356
|
+
lexicographic depth first search (LexDFS) on the graph
|
357
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_UP` -- perform a
|
358
|
+
lexicographic UP search (LexUP) on the graph
|
359
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_DOWN` -- perform a
|
360
|
+
lexicographic DOWN search (LexDOWN) on the graph
|
361
|
+
|
362
|
+
EXAMPLES:
|
363
|
+
|
364
|
+
A Lex BFS is obviously an ordering of the vertices::
|
365
|
+
|
366
|
+
sage: g = graphs.CompleteGraph(6)
|
367
|
+
sage: set(g.lex_BFS()) == set(g)
|
368
|
+
True
|
369
|
+
|
370
|
+
Lex BFS ordering of the 3-sun graph::
|
371
|
+
|
372
|
+
sage: g = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)])
|
373
|
+
sage: g.lex_BFS()
|
374
|
+
[1, 2, 3, 5, 4, 6]
|
375
|
+
|
376
|
+
The method also works for directed graphs::
|
377
|
+
|
378
|
+
sage: G = DiGraph([(1, 2), (2, 3), (1, 3)])
|
379
|
+
sage: correct_anwsers = [[2, 1, 3], [2, 3, 1]]
|
380
|
+
sage: G.lex_BFS(initial_vertex=2, algorithm='slow') in correct_anwsers
|
381
|
+
True
|
382
|
+
sage: G.lex_BFS(initial_vertex=2, algorithm='fast') in correct_anwsers
|
383
|
+
True
|
384
|
+
|
385
|
+
For a Chordal Graph, a reversed Lex BFS is a Perfect Elimination Order::
|
386
|
+
|
387
|
+
sage: g = graphs.PathGraph(3).lexicographic_product(graphs.CompleteGraph(2))
|
388
|
+
sage: g.lex_BFS(reverse=True)
|
389
|
+
[(2, 1), (2, 0), (1, 1), (1, 0), (0, 1), (0, 0)]
|
390
|
+
|
391
|
+
And the vertices at the end of the tree of discovery are, for chordal
|
392
|
+
graphs, simplicial vertices (their neighborhood is a complete graph)::
|
393
|
+
|
394
|
+
sage: g = graphs.ClawGraph().lexicographic_product(graphs.CompleteGraph(2))
|
395
|
+
sage: v = g.lex_BFS()[-1]
|
396
|
+
sage: peo, tree = g.lex_BFS(initial_vertex = v, tree=True)
|
397
|
+
sage: leaves = [v for v in tree if tree.in_degree(v) ==0]
|
398
|
+
sage: all(g.subgraph(g.neighbors(v)).is_clique() for v in leaves)
|
399
|
+
True
|
400
|
+
|
401
|
+
Different orderings for different traversals::
|
402
|
+
|
403
|
+
sage: # needs sage.combinat
|
404
|
+
sage: G = digraphs.DeBruijn(2,3)
|
405
|
+
sage: G.lex_BFS(initial_vertex='000', algorithm='fast')
|
406
|
+
['000', '001', '100', '010', '011', '110', '101', '111']
|
407
|
+
sage: G.lex_BFS(initial_vertex='000', algorithm='slow')
|
408
|
+
['000', '001', '100', '010', '011', '110', '101', '111']
|
409
|
+
sage: G.lex_DFS(initial_vertex='000')
|
410
|
+
['000', '001', '100', '010', '101', '110', '011', '111']
|
411
|
+
sage: G.lex_UP(initial_vertex='000')
|
412
|
+
['000', '001', '010', '101', '110', '111', '011', '100']
|
413
|
+
sage: G.lex_DOWN(initial_vertex='000')
|
414
|
+
['000', '001', '100', '011', '010', '110', '111', '101']
|
415
|
+
|
416
|
+
TESTS:
|
417
|
+
|
418
|
+
Computed orderings are valid::
|
419
|
+
|
420
|
+
sage: from sage.graphs.traversals import _is_valid_lex_BFS_order
|
421
|
+
sage: G = graphs.RandomChordalGraph(15)
|
422
|
+
sage: v0 = ZZ.random_element(G.order())
|
423
|
+
sage: L = G.lex_BFS(initial_vertex=v0, algorithm='fast')
|
424
|
+
sage: _is_valid_lex_BFS_order(G, L)
|
425
|
+
True
|
426
|
+
sage: L = G.lex_BFS(initial_vertex=v0, algorithm='slow')
|
427
|
+
sage: _is_valid_lex_BFS_order(G, L)
|
428
|
+
True
|
429
|
+
sage: G = digraphs.RandomDirectedGNP(15, .3)
|
430
|
+
sage: v0 = ZZ.random_element(G.order())
|
431
|
+
sage: L = G.lex_BFS(initial_vertex=v0, algorithm='fast')
|
432
|
+
sage: _is_valid_lex_BFS_order(G, L)
|
433
|
+
True
|
434
|
+
sage: L = G.lex_BFS(initial_vertex=v0, algorithm='slow')
|
435
|
+
sage: _is_valid_lex_BFS_order(G, L)
|
436
|
+
True
|
437
|
+
|
438
|
+
Lex BFS ordering of a graph on one vertex::
|
439
|
+
|
440
|
+
sage: Graph(1).lex_BFS(algorithm="fast", tree=True)
|
441
|
+
([0], Digraph on 1 vertex)
|
442
|
+
|
443
|
+
Lex BFS ordering of an empty (di)graph is an empty sequence::
|
444
|
+
|
445
|
+
sage: g = Graph()
|
446
|
+
sage: g.lex_BFS(algorithm="fast")
|
447
|
+
[]
|
448
|
+
sage: g.lex_BFS(algorithm="fast", tree=True)
|
449
|
+
([], Digraph on 0 vertices)
|
450
|
+
|
451
|
+
Lex BFS ordering of a symmetric digraph should be the same as the Lex BFS
|
452
|
+
ordering of the corresponding undirected graph::
|
453
|
+
|
454
|
+
sage: G = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)])
|
455
|
+
sage: H = DiGraph(G)
|
456
|
+
sage: G.lex_BFS(algorithm="fast") == H.lex_BFS(algorithm="fast")
|
457
|
+
True
|
458
|
+
|
459
|
+
``initial_vertex`` should be a valid graph vertex::
|
460
|
+
|
461
|
+
sage: G = graphs.CompleteGraph(6)
|
462
|
+
sage: G.lex_BFS(algorithm="fast", initial_vertex='foo')
|
463
|
+
Traceback (most recent call last):
|
464
|
+
...
|
465
|
+
ValueError: 'foo' is not a graph vertex
|
466
|
+
|
467
|
+
Check that :issue:`39934` is fixed::
|
468
|
+
|
469
|
+
sage: G = Graph(1, immutable=True)
|
470
|
+
sage: G.lex_BFS(algorithm='slow')
|
471
|
+
[0]
|
472
|
+
sage: G.lex_BFS(algorithm='fast')
|
473
|
+
[0]
|
474
|
+
"""
|
475
|
+
if initial_vertex is not None and initial_vertex not in G:
|
476
|
+
raise ValueError(f"'{initial_vertex}' is not a graph vertex")
|
477
|
+
|
478
|
+
if algorithm == "slow":
|
479
|
+
return _lex_order_common(G, "lex_BFS", reverse, tree, initial_vertex)
|
480
|
+
|
481
|
+
if algorithm != "fast":
|
482
|
+
raise ValueError(f"unknown algorithm '{algorithm}'")
|
483
|
+
|
484
|
+
# For algorithm "fast" we need to convert G to an undirected graph
|
485
|
+
if G.is_directed():
|
486
|
+
G = G.to_undirected()
|
487
|
+
|
488
|
+
# Initialize variables needed by the fast algorithm
|
489
|
+
cdef CGraphBackend Gbackend = <CGraphBackend> G._backend
|
490
|
+
cdef CGraph cg = Gbackend.cg()
|
491
|
+
cdef vector[int] sigma_int
|
492
|
+
cdef vector[int] pred
|
493
|
+
# Temporary variables
|
494
|
+
cdef int vi, i, initial_v_int
|
495
|
+
|
496
|
+
# Perform Lex BFS
|
497
|
+
if initial_vertex is not None:
|
498
|
+
# we already checked that initial_vertex is in G
|
499
|
+
initial_v_int = Gbackend.get_vertex(initial_vertex)
|
500
|
+
else:
|
501
|
+
initial_v_int = -1
|
502
|
+
sig_on()
|
503
|
+
extended_lex_BFS(cg, sigma_int, NULL, initial_v_int, &pred, NULL, NULL)
|
504
|
+
sig_off()
|
505
|
+
cdef list sigma = [Gbackend.vertex_label(vi) for vi in sigma_int]
|
506
|
+
cdef dict predecessor = {u: sigma[i] for u, i in zip(sigma, pred) if i != -1}
|
507
|
+
|
508
|
+
if reverse:
|
509
|
+
sigma.reverse()
|
510
|
+
|
511
|
+
if tree:
|
512
|
+
from sage.graphs.digraph import DiGraph
|
513
|
+
edges = predecessor.items()
|
514
|
+
g = DiGraph([G, edges], format='vertices_and_edges', sparse=True)
|
515
|
+
return sigma, g
|
516
|
+
return sigma
|
517
|
+
|
518
|
+
|
519
|
+
def lex_UP(G, reverse=False, tree=False, initial_vertex=None):
|
520
|
+
r"""
|
521
|
+
Perform a lexicographic UP search (LexUP) on the graph.
|
522
|
+
|
523
|
+
INPUT:
|
524
|
+
|
525
|
+
- ``G`` -- a sage graph
|
526
|
+
|
527
|
+
- ``reverse`` -- boolean (default: ``False``); whether to return the
|
528
|
+
vertices in discovery order, or the reverse
|
529
|
+
|
530
|
+
- ``tree`` -- boolean (default: ``False``); whether to return the
|
531
|
+
discovery directed tree (each vertex being linked to the one that saw
|
532
|
+
it last)
|
533
|
+
|
534
|
+
- ``initial_vertex`` -- (default: ``None``) the first vertex to
|
535
|
+
consider
|
536
|
+
|
537
|
+
.. NOTE::
|
538
|
+
|
539
|
+
Loops and multiple edges are ignored during the computation of
|
540
|
+
``lex_UP`` and directed graphs are converted to undirected graphs.
|
541
|
+
|
542
|
+
.. SEEALSO::
|
543
|
+
|
544
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_BFS` -- perform a
|
545
|
+
lexicographic breadth first search (LexBFS) on the graph
|
546
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_DFS` -- perform a
|
547
|
+
lexicographic depth first search (LexDFS) on the graph
|
548
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_DOWN` -- perform a
|
549
|
+
lexicographic DOWN search (LexDOWN) on the graph
|
550
|
+
|
551
|
+
ALGORITHM:
|
552
|
+
|
553
|
+
See the documentation of the :mod:`~sage.graphs.traversals` module.
|
554
|
+
|
555
|
+
EXAMPLES:
|
556
|
+
|
557
|
+
A Lex UP is obviously an ordering of the vertices::
|
558
|
+
|
559
|
+
sage: g = graphs.CompleteGraph(6)
|
560
|
+
sage: set(g.lex_UP()) == set(g)
|
561
|
+
True
|
562
|
+
|
563
|
+
Lex UP ordering of the 3-sun graph::
|
564
|
+
|
565
|
+
sage: g = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)])
|
566
|
+
sage: g.lex_UP()
|
567
|
+
[1, 2, 4, 5, 6, 3]
|
568
|
+
|
569
|
+
The method also works for directed graphs::
|
570
|
+
|
571
|
+
sage: G = DiGraph([(1, 2), (2, 3), (1, 3)])
|
572
|
+
sage: correct_anwsers = [[2, 1, 3], [2, 3, 1]]
|
573
|
+
sage: G.lex_UP(initial_vertex=2) in correct_anwsers
|
574
|
+
True
|
575
|
+
|
576
|
+
Different orderings for different traversals::
|
577
|
+
|
578
|
+
sage: # needs sage.combinat
|
579
|
+
sage: G = digraphs.DeBruijn(2,3)
|
580
|
+
sage: G.lex_BFS(initial_vertex='000')
|
581
|
+
['000', '001', '100', '010', '011', '110', '101', '111']
|
582
|
+
sage: G.lex_DFS(initial_vertex='000')
|
583
|
+
['000', '001', '100', '010', '101', '110', '011', '111']
|
584
|
+
sage: G.lex_UP(initial_vertex='000')
|
585
|
+
['000', '001', '010', '101', '110', '111', '011', '100']
|
586
|
+
sage: G.lex_DOWN(initial_vertex='000')
|
587
|
+
['000', '001', '100', '011', '010', '110', '111', '101']
|
588
|
+
"""
|
589
|
+
return _lex_order_common(G, "lex_UP", reverse, tree, initial_vertex)
|
590
|
+
|
591
|
+
|
592
|
+
def lex_DFS(G, reverse=False, tree=False, initial_vertex=None):
|
593
|
+
r"""
|
594
|
+
Perform a lexicographic depth first search (LexDFS) on the graph.
|
595
|
+
|
596
|
+
INPUT:
|
597
|
+
|
598
|
+
- ``G`` -- a sage graph
|
599
|
+
|
600
|
+
- ``reverse`` -- boolean (default: ``False``); whether to return the
|
601
|
+
vertices in discovery order, or the reverse
|
602
|
+
|
603
|
+
- ``tree`` -- boolean (default: ``False``); whether to return the
|
604
|
+
discovery directed tree (each vertex being linked to the one that saw
|
605
|
+
it last)
|
606
|
+
|
607
|
+
- ``initial_vertex`` -- (default: ``None``) the first vertex to
|
608
|
+
consider
|
609
|
+
|
610
|
+
.. NOTE::
|
611
|
+
|
612
|
+
Loops and multiple edges are ignored during the computation of
|
613
|
+
``lex_DFS`` and directed graphs are converted to undirected graphs.
|
614
|
+
|
615
|
+
.. SEEALSO::
|
616
|
+
|
617
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_BFS` -- perform a
|
618
|
+
lexicographic breadth first search (LexBFS) on the graph
|
619
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_UP` -- perform a
|
620
|
+
lexicographic UP search (LexUP) on the graph
|
621
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_DOWN` -- perform a
|
622
|
+
lexicographic DOWN search (LexDOWN) on the graph
|
623
|
+
|
624
|
+
ALGORITHM:
|
625
|
+
|
626
|
+
See the documentation of the :mod:`~sage.graphs.traversals` module.
|
627
|
+
|
628
|
+
EXAMPLES:
|
629
|
+
|
630
|
+
A Lex DFS is obviously an ordering of the vertices::
|
631
|
+
|
632
|
+
sage: g = graphs.CompleteGraph(6)
|
633
|
+
sage: set(g.lex_DFS()) == set(g)
|
634
|
+
True
|
635
|
+
|
636
|
+
Lex DFS ordering of the 3-sun graph::
|
637
|
+
|
638
|
+
sage: g = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)])
|
639
|
+
sage: g.lex_DFS()
|
640
|
+
[1, 2, 3, 5, 6, 4]
|
641
|
+
|
642
|
+
The method also works for directed graphs::
|
643
|
+
|
644
|
+
sage: G = DiGraph([(1, 2), (2, 3), (1, 3)])
|
645
|
+
sage: correct_anwsers = [[2, 1, 3], [2, 3, 1]]
|
646
|
+
sage: G.lex_DFS(initial_vertex=2) in correct_anwsers
|
647
|
+
True
|
648
|
+
|
649
|
+
Different orderings for different traversals::
|
650
|
+
|
651
|
+
sage: # needs sage.combinat
|
652
|
+
sage: G = digraphs.DeBruijn(2,3)
|
653
|
+
sage: G.lex_BFS(initial_vertex='000')
|
654
|
+
['000', '001', '100', '010', '011', '110', '101', '111']
|
655
|
+
sage: G.lex_DFS(initial_vertex='000')
|
656
|
+
['000', '001', '100', '010', '101', '110', '011', '111']
|
657
|
+
sage: G.lex_UP(initial_vertex='000')
|
658
|
+
['000', '001', '010', '101', '110', '111', '011', '100']
|
659
|
+
sage: G.lex_DOWN(initial_vertex='000')
|
660
|
+
['000', '001', '100', '011', '010', '110', '111', '101']
|
661
|
+
"""
|
662
|
+
return _lex_order_common(G, "lex_DFS", reverse, tree, initial_vertex)
|
663
|
+
|
664
|
+
|
665
|
+
def lex_DOWN(G, reverse=False, tree=False, initial_vertex=None):
|
666
|
+
r"""
|
667
|
+
Perform a lexicographic DOWN search (LexDOWN) on the graph.
|
668
|
+
|
669
|
+
INPUT:
|
670
|
+
|
671
|
+
- ``G`` -- a sage graph
|
672
|
+
|
673
|
+
- ``reverse`` -- boolean (default: ``False``); whether to return the
|
674
|
+
vertices in discovery order, or the reverse
|
675
|
+
|
676
|
+
- ``tree`` -- boolean (default: ``False``); whether to return the
|
677
|
+
discovery directed tree (each vertex being linked to the one that saw
|
678
|
+
it)
|
679
|
+
|
680
|
+
- ``initial_vertex`` -- (default: ``None``) the first vertex to
|
681
|
+
consider
|
682
|
+
|
683
|
+
.. NOTE::
|
684
|
+
|
685
|
+
Loops and multiple edges are ignored during the computation of
|
686
|
+
``lex_DOWN`` and directed graphs are converted to undirected graphs.
|
687
|
+
|
688
|
+
.. SEEALSO::
|
689
|
+
|
690
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_BFS` -- perform a
|
691
|
+
lexicographic breadth first search (LexBFS) on the graph
|
692
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_DFS` -- perform a
|
693
|
+
lexicographic depth first search (LexDFS) on the graph
|
694
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.lex_UP` -- perform a
|
695
|
+
lexicographic UP search (LexUP) on the graph
|
696
|
+
|
697
|
+
ALGORITHM:
|
698
|
+
|
699
|
+
See the documentation of the :mod:`~sage.graphs.traversals` module.
|
700
|
+
|
701
|
+
EXAMPLES:
|
702
|
+
|
703
|
+
A Lex DOWN is obviously an ordering of the vertices::
|
704
|
+
|
705
|
+
sage: g = graphs.CompleteGraph(6)
|
706
|
+
sage: set(g.lex_DOWN()) == set(g)
|
707
|
+
True
|
708
|
+
|
709
|
+
Lex DOWN ordering of the 3-sun graph::
|
710
|
+
|
711
|
+
sage: g = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)])
|
712
|
+
sage: g.lex_DOWN()
|
713
|
+
[1, 2, 3, 4, 6, 5]
|
714
|
+
|
715
|
+
The method also works for directed graphs::
|
716
|
+
|
717
|
+
sage: G = DiGraph([(1, 2), (2, 3), (1, 3)])
|
718
|
+
sage: correct_anwsers = [[2, 1, 3], [2, 3, 1]]
|
719
|
+
sage: G.lex_DOWN(initial_vertex=2) in correct_anwsers
|
720
|
+
True
|
721
|
+
|
722
|
+
Different orderings for different traversals::
|
723
|
+
|
724
|
+
sage: # needs sage.combinat
|
725
|
+
sage: G = digraphs.DeBruijn(2,3)
|
726
|
+
sage: G.lex_BFS(initial_vertex='000')
|
727
|
+
['000', '001', '100', '010', '011', '110', '101', '111']
|
728
|
+
sage: G.lex_DFS(initial_vertex='000')
|
729
|
+
['000', '001', '100', '010', '101', '110', '011', '111']
|
730
|
+
sage: G.lex_UP(initial_vertex='000')
|
731
|
+
['000', '001', '010', '101', '110', '111', '011', '100']
|
732
|
+
sage: G.lex_DOWN(initial_vertex='000')
|
733
|
+
['000', '001', '100', '011', '010', '110', '111', '101']
|
734
|
+
"""
|
735
|
+
return _lex_order_common(G, "lex_DOWN", reverse, tree, initial_vertex)
|
736
|
+
|
737
|
+
|
738
|
+
def lex_M(self, triangulation=False, labels=False, initial_vertex=None, algorithm=None):
|
739
|
+
r"""
|
740
|
+
Return an ordering of the vertices according the LexM graph traversal.
|
741
|
+
|
742
|
+
LexM is a lexicographic ordering scheme that is a special type of
|
743
|
+
breadth-first-search. LexM can also produce a triangulation of the
|
744
|
+
given graph. This functionality is implemented in this method. For
|
745
|
+
more details on the algorithms used see Sections 4 (``'lex_M_slow'``)
|
746
|
+
and 5.3 (``'lex_M_fast'``) of [RTL76]_.
|
747
|
+
|
748
|
+
.. NOTE::
|
749
|
+
|
750
|
+
This method works only for undirected graphs.
|
751
|
+
|
752
|
+
INPUT:
|
753
|
+
|
754
|
+
- ``triangulation`` -- boolean (default: ``False``); whether to return a
|
755
|
+
list of edges that need to be added in order to triangulate the graph
|
756
|
+
|
757
|
+
- ``labels`` -- boolean (default: ``False``); whether to return the labels
|
758
|
+
assigned to each vertex
|
759
|
+
|
760
|
+
- ``initial_vertex`` -- (default: ``None``); the first vertex to consider
|
761
|
+
|
762
|
+
- ``algorithm`` -- string (default: ``None``); one of the following
|
763
|
+
algorithms:
|
764
|
+
|
765
|
+
- ``'lex_M_slow'``: slower implementation of LexM traversal
|
766
|
+
|
767
|
+
- ``'lex_M_fast'``: faster implementation of LexM traversal (works only
|
768
|
+
when ``labels`` is set to ``False``)
|
769
|
+
|
770
|
+
- ``None``: Sage chooses the best algorithm: ``'lex_M_slow'`` if
|
771
|
+
``labels`` is set to ``True``, ``'lex_M_fast'`` otherwise.
|
772
|
+
|
773
|
+
OUTPUT:
|
774
|
+
|
775
|
+
Depending on the values of the parameters ``triangulation`` and ``labels``
|
776
|
+
the method will return one or more of the following (in that order):
|
777
|
+
|
778
|
+
- an ordering of vertices of the graph according to LexM ordering scheme
|
779
|
+
|
780
|
+
- the labels assigned to each vertex
|
781
|
+
|
782
|
+
- a list of edges that when added to the graph will triangulate it
|
783
|
+
|
784
|
+
EXAMPLES:
|
785
|
+
|
786
|
+
LexM produces an ordering of the vertices::
|
787
|
+
|
788
|
+
sage: g = graphs.CompleteGraph(6)
|
789
|
+
sage: ord = g.lex_M(algorithm='lex_M_fast')
|
790
|
+
sage: len(ord) == g.order()
|
791
|
+
True
|
792
|
+
sage: set(ord) == set(g.vertices(sort=False))
|
793
|
+
True
|
794
|
+
sage: ord = g.lex_M(algorithm='lex_M_slow')
|
795
|
+
sage: len(ord) == g.order()
|
796
|
+
True
|
797
|
+
sage: set(ord) == set(g.vertices(sort=False))
|
798
|
+
True
|
799
|
+
|
800
|
+
Both algorithms produce a valid LexM ordering `\alpha` (i.e the neighbors of
|
801
|
+
`\alpha(i)` in `G[\{\alpha(i), ..., \alpha(n)\}]` induce a clique)::
|
802
|
+
|
803
|
+
sage: from sage.graphs.traversals import is_valid_lex_M_order
|
804
|
+
sage: G = graphs.PetersenGraph()
|
805
|
+
sage: ord, F = G.lex_M(triangulation=True, algorithm='lex_M_slow')
|
806
|
+
sage: is_valid_lex_M_order(G, ord, F)
|
807
|
+
True
|
808
|
+
sage: ord, F = G.lex_M(triangulation=True, algorithm='lex_M_fast')
|
809
|
+
sage: is_valid_lex_M_order(G, ord, F)
|
810
|
+
True
|
811
|
+
|
812
|
+
LexM produces a triangulation of given graph::
|
813
|
+
|
814
|
+
sage: G = graphs.PetersenGraph()
|
815
|
+
sage: _, F = G.lex_M(triangulation=True)
|
816
|
+
sage: H = Graph(F, format='list_of_edges')
|
817
|
+
sage: H.is_chordal()
|
818
|
+
True
|
819
|
+
|
820
|
+
LexM ordering of the 3-sun graph::
|
821
|
+
|
822
|
+
sage: g = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)])
|
823
|
+
sage: g.lex_M()
|
824
|
+
[6, 4, 5, 3, 2, 1]
|
825
|
+
|
826
|
+
The ordering depends on the initial vertex::
|
827
|
+
|
828
|
+
sage: G = graphs.HouseGraph()
|
829
|
+
sage: G.lex_M(algorithm='lex_M_slow', initial_vertex=0)
|
830
|
+
[4, 3, 2, 1, 0]
|
831
|
+
sage: G.lex_M(algorithm='lex_M_slow', initial_vertex=2)
|
832
|
+
[1, 4, 3, 0, 2]
|
833
|
+
sage: G.lex_M(algorithm='lex_M_fast', initial_vertex=0)
|
834
|
+
[4, 3, 2, 1, 0]
|
835
|
+
sage: G.lex_M(algorithm='lex_M_fast', initial_vertex=2)
|
836
|
+
[1, 4, 3, 0, 2]
|
837
|
+
|
838
|
+
TESTS:
|
839
|
+
|
840
|
+
``'lex_M_fast'`` cannot return labels::
|
841
|
+
|
842
|
+
sage: Graph().lex_M(labels=True, algorithm='lex_M_fast')
|
843
|
+
Traceback (most recent call last):
|
844
|
+
...
|
845
|
+
ValueError: 'lex_M_fast' cannot return labels assigned to vertices
|
846
|
+
|
847
|
+
The method works only for undirected graphs::
|
848
|
+
|
849
|
+
sage: from sage.graphs.traversals import lex_M
|
850
|
+
sage: lex_M(DiGraph())
|
851
|
+
Traceback (most recent call last):
|
852
|
+
...
|
853
|
+
ValueError: input graph must be undirected
|
854
|
+
|
855
|
+
LexM ordering of empty graph::
|
856
|
+
|
857
|
+
sage: G = Graph()
|
858
|
+
sage: G.lex_M()
|
859
|
+
[]
|
860
|
+
|
861
|
+
Parameter ``algorithm`` must be either ``'lex_M_slow'``,
|
862
|
+
``'lex_M_fast'`` or ``None``::
|
863
|
+
|
864
|
+
sage: G = graphs.CompleteGraph(6)
|
865
|
+
sage: G.lex_M(algorithm='Bob')
|
866
|
+
Traceback (most recent call last):
|
867
|
+
...
|
868
|
+
ValueError: unknown algorithm 'Bob'
|
869
|
+
|
870
|
+
``initial_vertex`` should be a valid graph vertex::
|
871
|
+
|
872
|
+
sage: Graph().lex_M(initial_vertex='foo')
|
873
|
+
Traceback (most recent call last):
|
874
|
+
...
|
875
|
+
ValueError: 'foo' is not a graph vertex
|
876
|
+
"""
|
877
|
+
if initial_vertex is not None and initial_vertex not in self:
|
878
|
+
raise ValueError("'{}' is not a graph vertex".format(initial_vertex))
|
879
|
+
|
880
|
+
if self.is_directed():
|
881
|
+
raise ValueError("input graph must be undirected")
|
882
|
+
|
883
|
+
if not algorithm:
|
884
|
+
if labels:
|
885
|
+
algorithm = "lex_M_slow"
|
886
|
+
else:
|
887
|
+
algorithm = "lex_M_fast"
|
888
|
+
|
889
|
+
elif algorithm not in ["lex_M_slow", "lex_M_fast"]:
|
890
|
+
raise ValueError("unknown algorithm '{}'".format(algorithm))
|
891
|
+
|
892
|
+
if algorithm == "lex_M_slow":
|
893
|
+
return lex_M_slow(self, triangulation=triangulation, labels=labels, initial_vertex=initial_vertex)
|
894
|
+
if labels:
|
895
|
+
raise ValueError("'{}' cannot return labels assigned to vertices".format(algorithm))
|
896
|
+
return lex_M_fast(self, triangulation=triangulation, initial_vertex=initial_vertex)
|
897
|
+
|
898
|
+
|
899
|
+
def lex_M_slow(G, triangulation=False, labels=False, initial_vertex=None):
|
900
|
+
r"""
|
901
|
+
Return an ordering of the vertices according the LexM graph traversal.
|
902
|
+
|
903
|
+
LexM is a lexicographic ordering scheme that is a special type of
|
904
|
+
breadth-first-search. This function implements the algorithm described in
|
905
|
+
Section 4 of [RTL76]_.
|
906
|
+
|
907
|
+
During the search, the vertices are numbered from `n` to `1`. Let
|
908
|
+
`\alpha(i)` denote the vertex numbered `i` and let `\alpha^{-1}(u)` denote
|
909
|
+
the number assigned to `u`. Each vertex `u` has also a label, denoted by
|
910
|
+
`label(u)`, consisting of a list of numbers selected from `[1,n]` and
|
911
|
+
ordered in decreasing order. Given two labels `L_1=[p_1, p_2,\ldots, p_k]`
|
912
|
+
and `L_1=[q_1, q_2,\ldots, q_l]`, we define `L_1<L_2` if, for some `j`,
|
913
|
+
`p_i==q_i` for `i=1,\ldots,j-1` and `p_j<q_j`, or if `p_i==q_i` for
|
914
|
+
`i=1,\ldots,k` and `k<l`. Observe that this is exactly how Python compares
|
915
|
+
two lists.
|
916
|
+
|
917
|
+
.. NOTE::
|
918
|
+
|
919
|
+
This method works only for undirected graphs.
|
920
|
+
|
921
|
+
INPUT:
|
922
|
+
|
923
|
+
- ``G`` -- a sage graph
|
924
|
+
|
925
|
+
- ``triangulation`` -- boolean (default: ``False``); whether to return the
|
926
|
+
triangulation of the graph produced by the method
|
927
|
+
|
928
|
+
- ``labels`` -- boolean (default: ``False``); whether to return the labels
|
929
|
+
assigned to each vertex
|
930
|
+
|
931
|
+
- ``initial_vertex`` -- (default: ``None``) the first vertex to
|
932
|
+
consider. If not specified, an arbitrary vertex is chosen.
|
933
|
+
|
934
|
+
OUTPUT:
|
935
|
+
|
936
|
+
Depending on the values of the parameters ``triangulation`` and ``labels``
|
937
|
+
the method will return one or more of the following (in that order):
|
938
|
+
|
939
|
+
- the ordering of vertices of `G`
|
940
|
+
|
941
|
+
- the labels assigned to each vertex
|
942
|
+
|
943
|
+
- a list of edges that when added to `G` will produce a triangulation of `G`
|
944
|
+
|
945
|
+
EXAMPLES:
|
946
|
+
|
947
|
+
A LexM ordering is obviously an ordering of the vertices::
|
948
|
+
|
949
|
+
sage: from sage.graphs.traversals import lex_M_slow
|
950
|
+
sage: g = graphs.CompleteGraph(6)
|
951
|
+
sage: len(lex_M_slow(g)) == g.order()
|
952
|
+
True
|
953
|
+
|
954
|
+
LexM ordering and label assignments on the vertices of the 3-sun graph::
|
955
|
+
|
956
|
+
sage: from sage.graphs.traversals import lex_M_slow
|
957
|
+
sage: g = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)])
|
958
|
+
sage: lex_M_slow(g, labels=True)
|
959
|
+
([6, 4, 5, 3, 2, 1],
|
960
|
+
{1: [], 2: [5], 3: [5, 4], 4: [4, 2], 5: [4, 3], 6: [3, 2]})
|
961
|
+
|
962
|
+
LexM produces a triangulation of given graph::
|
963
|
+
|
964
|
+
sage: from sage.graphs.traversals import lex_M_slow
|
965
|
+
sage: G = graphs.PetersenGraph()
|
966
|
+
sage: _, F = lex_M_slow(G, triangulation=True)
|
967
|
+
sage: H = G.copy()
|
968
|
+
sage: H.add_edges(F)
|
969
|
+
sage: H.is_chordal()
|
970
|
+
True
|
971
|
+
|
972
|
+
TESTS:
|
973
|
+
|
974
|
+
LexM ordering of empty graph::
|
975
|
+
|
976
|
+
sage: from sage.graphs.traversals import lex_M_slow
|
977
|
+
sage: G = Graph()
|
978
|
+
sage: lex_M_slow(G)
|
979
|
+
[]
|
980
|
+
|
981
|
+
The method works only for undirected graphs::
|
982
|
+
|
983
|
+
sage: from sage.graphs.traversals import lex_M_slow
|
984
|
+
sage: G = digraphs.Circuit(15)
|
985
|
+
sage: lex_M_slow(G)
|
986
|
+
Traceback (most recent call last):
|
987
|
+
...
|
988
|
+
ValueError: input graph must be undirected
|
989
|
+
|
990
|
+
``initial_vertex`` should be a valid graph vertex::
|
991
|
+
|
992
|
+
sage: G = graphs.CompleteGraph(6)
|
993
|
+
sage: from sage.graphs.traversals import lex_M_slow
|
994
|
+
sage: lex_M_slow(G, initial_vertex='foo')
|
995
|
+
Traceback (most recent call last):
|
996
|
+
...
|
997
|
+
ValueError: 'foo' is not a graph vertex
|
998
|
+
"""
|
999
|
+
if initial_vertex is not None and initial_vertex not in G:
|
1000
|
+
raise ValueError("'{}' is not a graph vertex".format(initial_vertex))
|
1001
|
+
|
1002
|
+
if G.is_directed():
|
1003
|
+
raise ValueError("input graph must be undirected")
|
1004
|
+
|
1005
|
+
# ==>Initialization
|
1006
|
+
# Assign empty label to all vertices of G and empty list to F
|
1007
|
+
cdef list unnumbered_vertices = list(G)
|
1008
|
+
cdef int n = G.order()
|
1009
|
+
cdef list alpha = [0] * n
|
1010
|
+
cdef dict label = {v: [] for v in unnumbered_vertices}
|
1011
|
+
cdef list F = []
|
1012
|
+
cdef int i
|
1013
|
+
cdef set active, reach
|
1014
|
+
|
1015
|
+
if initial_vertex is not None:
|
1016
|
+
i = unnumbered_vertices.index(initial_vertex)
|
1017
|
+
unnumbered_vertices[0], unnumbered_vertices[i] = unnumbered_vertices[i], unnumbered_vertices[0]
|
1018
|
+
|
1019
|
+
for i in range(n-1, -1, -1):
|
1020
|
+
# Select: pick an unnumbered vertex u with largest label
|
1021
|
+
u = unnumbered_vertices[0]
|
1022
|
+
for v in unnumbered_vertices[1:]:
|
1023
|
+
if label[u] < label[v]:
|
1024
|
+
u = v
|
1025
|
+
|
1026
|
+
unnumbered_vertices.remove(u)
|
1027
|
+
alpha[i] = u
|
1028
|
+
|
1029
|
+
# Update: for each vertex v in unnumbered_vertices such that there is a
|
1030
|
+
# chain u = w_1, w_2, ..., w_{p+1} = v with w_j unnumbered and
|
1031
|
+
# label(w_j) < label(v) for all j in {2,...,p}. If so, we add i to the
|
1032
|
+
# label of v and add edge {u,v} to F.
|
1033
|
+
for v in unnumbered_vertices:
|
1034
|
+
|
1035
|
+
# We check if there is a chain u = w_1, w_2, ..., w_{p+1} = v with
|
1036
|
+
# w_j unnumbered and label(w_j) < label(v) for all j in {2, ..., p}
|
1037
|
+
active = set([w for w in unnumbered_vertices if label[w] < label[v]])
|
1038
|
+
active.add(v)
|
1039
|
+
reach = set([u])
|
1040
|
+
while active and reach and v not in reach:
|
1041
|
+
w = reach.pop()
|
1042
|
+
for x in G.neighbor_iterator(w):
|
1043
|
+
if x in active:
|
1044
|
+
reach.add(x)
|
1045
|
+
active.discard(x)
|
1046
|
+
|
1047
|
+
if v in reach:
|
1048
|
+
label[v].append(i)
|
1049
|
+
if triangulation:
|
1050
|
+
F.append((u, v))
|
1051
|
+
|
1052
|
+
if triangulation and labels:
|
1053
|
+
return alpha, label, F
|
1054
|
+
elif triangulation:
|
1055
|
+
return alpha, F
|
1056
|
+
elif labels:
|
1057
|
+
return alpha, label
|
1058
|
+
return alpha
|
1059
|
+
|
1060
|
+
|
1061
|
+
def lex_M_fast(G, triangulation=False, initial_vertex=None):
|
1062
|
+
r"""
|
1063
|
+
Return an ordering of the vertices according the LexM graph traversal.
|
1064
|
+
|
1065
|
+
LexM is a lexicographic ordering scheme that is a special type of
|
1066
|
+
breadth-first-search. This function implements the algorithm described in
|
1067
|
+
Section 5.3 of [RTL76]_.
|
1068
|
+
|
1069
|
+
Note that instead of using labels `1, 2, \ldots, k` and adding `1/2`, we
|
1070
|
+
use labels `2, 4, \ldots, k` and add `1`, thus avoiding to use floats or
|
1071
|
+
rationals.
|
1072
|
+
|
1073
|
+
.. NOTE::
|
1074
|
+
|
1075
|
+
This method works only for undirected graphs.
|
1076
|
+
|
1077
|
+
INPUT:
|
1078
|
+
|
1079
|
+
- ``G`` -- a sage graph
|
1080
|
+
|
1081
|
+
- ``triangulation`` -- boolean (default: ``False``); whether to return the
|
1082
|
+
triangulation of given graph produced by the method
|
1083
|
+
|
1084
|
+
- ``initial_vertex`` -- (default: ``None``) the first vertex to consider
|
1085
|
+
|
1086
|
+
OUTPUT:
|
1087
|
+
|
1088
|
+
This method will return an ordering of the vertices of ``G`` according to
|
1089
|
+
the LexM ordering scheme. Furthermore, if ``triangulation`` is set to
|
1090
|
+
``True`` the method also returns a list of edges ``F`` such that when added
|
1091
|
+
to ``G`` the resulting graph is a triangulation of ``G``.
|
1092
|
+
|
1093
|
+
EXAMPLES:
|
1094
|
+
|
1095
|
+
A LexM ordering is obviously an ordering of the vertices::
|
1096
|
+
|
1097
|
+
sage: from sage.graphs.traversals import lex_M_fast
|
1098
|
+
sage: g = graphs.CompleteGraph(6)
|
1099
|
+
sage: len(lex_M_fast(g)) == g.order()
|
1100
|
+
True
|
1101
|
+
|
1102
|
+
LexM ordering of the 3-sun graph::
|
1103
|
+
|
1104
|
+
sage: from sage.graphs.traversals import lex_M_fast
|
1105
|
+
sage: g = Graph([(1, 2), (1, 3), (2, 3), (2, 4), (2, 5), (3, 5), (3, 6), (4, 5), (5, 6)])
|
1106
|
+
sage: lex_M_fast(g)
|
1107
|
+
[6, 4, 5, 3, 2, 1]
|
1108
|
+
|
1109
|
+
LexM produces a triangulation of given graph::
|
1110
|
+
|
1111
|
+
sage: from sage.graphs.traversals import lex_M_fast
|
1112
|
+
sage: G = graphs.PetersenGraph()
|
1113
|
+
sage: _, F = lex_M_fast(G, triangulation=True)
|
1114
|
+
sage: H = G.copy()
|
1115
|
+
sage: H.add_edges(F)
|
1116
|
+
sage: H.is_chordal()
|
1117
|
+
True
|
1118
|
+
|
1119
|
+
TESTS:
|
1120
|
+
|
1121
|
+
LexM ordering of empty graph::
|
1122
|
+
|
1123
|
+
sage: from sage.graphs.traversals import lex_M_fast
|
1124
|
+
sage: G = Graph()
|
1125
|
+
sage: lex_M_fast(G)
|
1126
|
+
[]
|
1127
|
+
|
1128
|
+
The method works only for undirected graphs::
|
1129
|
+
|
1130
|
+
sage: from sage.graphs.traversals import lex_M_fast
|
1131
|
+
sage: G = digraphs.Circuit(15)
|
1132
|
+
sage: lex_M_fast(G)
|
1133
|
+
Traceback (most recent call last):
|
1134
|
+
...
|
1135
|
+
ValueError: input graph must be undirected
|
1136
|
+
|
1137
|
+
``initial_vertex`` should be a valid graph vertex::
|
1138
|
+
|
1139
|
+
sage: G = graphs.CompleteGraph(6)
|
1140
|
+
sage: from sage.graphs.traversals import lex_M_fast
|
1141
|
+
sage: lex_M_fast(G, initial_vertex='foo')
|
1142
|
+
Traceback (most recent call last):
|
1143
|
+
...
|
1144
|
+
ValueError: 'foo' is not a graph vertex
|
1145
|
+
|
1146
|
+
Immutable graphs::
|
1147
|
+
|
1148
|
+
sage: from sage.graphs.traversals import lex_M_fast
|
1149
|
+
sage: G = graphs.RandomGNP(10, .7)
|
1150
|
+
sage: G._backend
|
1151
|
+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
|
1152
|
+
sage: H = Graph(G, immutable=True)
|
1153
|
+
sage: H._backend
|
1154
|
+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
|
1155
|
+
sage: lex_M_fast(G) == lex_M_fast(H)
|
1156
|
+
True
|
1157
|
+
"""
|
1158
|
+
if initial_vertex is not None and initial_vertex not in G:
|
1159
|
+
raise ValueError("'{}' is not a graph vertex".format(initial_vertex))
|
1160
|
+
|
1161
|
+
if G.is_directed():
|
1162
|
+
raise ValueError("input graph must be undirected")
|
1163
|
+
|
1164
|
+
# ==> Initialization
|
1165
|
+
|
1166
|
+
cdef int i, j, k, v, w, z
|
1167
|
+
|
1168
|
+
cdef list int_to_v
|
1169
|
+
cdef StaticSparseCGraph cg
|
1170
|
+
cdef short_digraph sd
|
1171
|
+
if isinstance(G, StaticSparseBackend):
|
1172
|
+
cg = <StaticSparseCGraph> G._cg
|
1173
|
+
sd = <short_digraph> cg.g
|
1174
|
+
int_to_v = cg._vertex_to_labels
|
1175
|
+
else:
|
1176
|
+
int_to_v = list(G)
|
1177
|
+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_v)
|
1178
|
+
|
1179
|
+
cdef uint32_t* p_tmp
|
1180
|
+
cdef uint32_t* p_end
|
1181
|
+
|
1182
|
+
cdef int n = G.order()
|
1183
|
+
|
1184
|
+
cdef list unnumbered_vertices = list(range(n))
|
1185
|
+
|
1186
|
+
if initial_vertex is not None:
|
1187
|
+
# We put the initial vertex at the first place
|
1188
|
+
i = int_to_v.index(initial_vertex)
|
1189
|
+
unnumbered_vertices[0], unnumbered_vertices[i] = unnumbered_vertices[i], unnumbered_vertices[0]
|
1190
|
+
|
1191
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
1192
|
+
cdef int* label = <int*>mem.allocarray(n, sizeof(int))
|
1193
|
+
cdef int* alpha = <int*>mem.allocarray(n, sizeof(int))
|
1194
|
+
cdef int* alphainv = <int*>mem.allocarray(n, sizeof(int))
|
1195
|
+
cdef bint* reached = <bint*>mem.allocarray(n, sizeof(bint))
|
1196
|
+
|
1197
|
+
for i in range(n):
|
1198
|
+
label[i] = 2
|
1199
|
+
alpha[i] = 0
|
1200
|
+
alphainv[i] = 0
|
1201
|
+
reached[i] = False
|
1202
|
+
|
1203
|
+
cdef list F = list()
|
1204
|
+
cdef dict reach
|
1205
|
+
|
1206
|
+
k = 2
|
1207
|
+
for i in range(n - 1, -1, -1):
|
1208
|
+
|
1209
|
+
# Select: pick an unnumbered vertex v with label(v)==k and assign it
|
1210
|
+
# number i
|
1211
|
+
for v in unnumbered_vertices:
|
1212
|
+
if label[v] == k:
|
1213
|
+
alpha[i] = v
|
1214
|
+
alphainv[v] = i
|
1215
|
+
reached[v] = True
|
1216
|
+
unnumbered_vertices.remove(v)
|
1217
|
+
break
|
1218
|
+
else:
|
1219
|
+
raise ValueError('unable to find an unnumbered vertex v with label[v] == k')
|
1220
|
+
|
1221
|
+
# Mark all unnumbered vertices unreached
|
1222
|
+
for w in unnumbered_vertices:
|
1223
|
+
reached[w] = False
|
1224
|
+
|
1225
|
+
reach = dict()
|
1226
|
+
for j in range(2, k + 1, 2):
|
1227
|
+
reach[j] = set()
|
1228
|
+
|
1229
|
+
p_tmp = sd.neighbors[v]
|
1230
|
+
p_end = sd.neighbors[v + 1]
|
1231
|
+
while p_tmp < p_end:
|
1232
|
+
w = p_tmp[0]
|
1233
|
+
p_tmp += 1
|
1234
|
+
if alphainv[w]:
|
1235
|
+
continue
|
1236
|
+
reach[label[w]].add(w)
|
1237
|
+
reached[w] = True
|
1238
|
+
label[w] += 1
|
1239
|
+
if triangulation:
|
1240
|
+
F.append((int_to_v[v], int_to_v[w]))
|
1241
|
+
|
1242
|
+
# Search
|
1243
|
+
for j in range(2, k + 1, 2):
|
1244
|
+
while reach[j]:
|
1245
|
+
w = reach[j].pop()
|
1246
|
+
p_tmp = sd.neighbors[w]
|
1247
|
+
p_end = sd.neighbors[w + 1]
|
1248
|
+
while p_tmp < p_end:
|
1249
|
+
z = p_tmp[0]
|
1250
|
+
p_tmp += 1
|
1251
|
+
if reached[z]:
|
1252
|
+
continue
|
1253
|
+
reached[z] = True
|
1254
|
+
if label[z] > j:
|
1255
|
+
reach[label[z]].add(z)
|
1256
|
+
label[z] += 1
|
1257
|
+
if triangulation:
|
1258
|
+
F.append((int_to_v[v], int_to_v[z]))
|
1259
|
+
else:
|
1260
|
+
reach[j].add(z)
|
1261
|
+
|
1262
|
+
if unnumbered_vertices:
|
1263
|
+
# Sort: sort unnumbered vertices by label(w) value
|
1264
|
+
order = sorted((label[w], w) for w in unnumbered_vertices)
|
1265
|
+
|
1266
|
+
# Reassign labels as integers from 2 to k, redefining k appropriately
|
1267
|
+
k = 2
|
1268
|
+
l, _ = order[0]
|
1269
|
+
for ll, w in order:
|
1270
|
+
if l != ll:
|
1271
|
+
l = ll
|
1272
|
+
k += 2
|
1273
|
+
label[w] = k
|
1274
|
+
|
1275
|
+
if not isinstance(G, StaticSparseBackend):
|
1276
|
+
free_short_digraph(sd)
|
1277
|
+
|
1278
|
+
cdef list ordering = [int_to_v[alpha[i]] for i in range(n)]
|
1279
|
+
|
1280
|
+
if triangulation:
|
1281
|
+
return ordering, F
|
1282
|
+
return ordering
|
1283
|
+
|
1284
|
+
|
1285
|
+
def is_valid_lex_M_order(G, alpha, F):
|
1286
|
+
r"""
|
1287
|
+
Check whether the ordering alpha and the triangulation F are valid for G.
|
1288
|
+
|
1289
|
+
Given the graph `G = (V, E)` with vertex set `V` and edge set `E`, and the
|
1290
|
+
set `F` of edges of a triangulation of `G`, let `H = (V, E\cup F)`.
|
1291
|
+
By induction one can see that for every `i \in \{1, ..., n - 1\}` the
|
1292
|
+
neighbors of `\alpha(i)` in `H[\{\alpha(i), ..., \alpha(n)\}]` induce a
|
1293
|
+
clique. The ordering `\alpha` is a perfect elimination ordering of `H`, so
|
1294
|
+
`H` is chordal. See [RTL76]_ for more details.
|
1295
|
+
|
1296
|
+
INPUT:
|
1297
|
+
|
1298
|
+
- ``G`` -- a Graph
|
1299
|
+
|
1300
|
+
- ``alpha`` -- list; an ordering of the vertices of `G`
|
1301
|
+
|
1302
|
+
- ``F`` -- an iterable of edges given either as ``(u, v)`` or ``(u, v,
|
1303
|
+
label)``, the edges of the triangulation of `G`
|
1304
|
+
|
1305
|
+
|
1306
|
+
TESTS::
|
1307
|
+
|
1308
|
+
sage: from sage.graphs.traversals import lex_M_slow, is_valid_lex_M_order
|
1309
|
+
sage: G = graphs.PetersenGraph()
|
1310
|
+
sage: alpha, F = lex_M_slow(G, triangulation=True)
|
1311
|
+
sage: is_valid_lex_M_order(G, alpha, F)
|
1312
|
+
True
|
1313
|
+
sage: H = Graph(G.edges(sort=False))
|
1314
|
+
sage: H.add_edges(F)
|
1315
|
+
sage: H.is_chordal()
|
1316
|
+
True
|
1317
|
+
sage: from sage.graphs.traversals import lex_M_fast
|
1318
|
+
sage: alpha, F = lex_M_fast(G, triangulation=True)
|
1319
|
+
sage: is_valid_lex_M_order(G, alpha, F)
|
1320
|
+
True
|
1321
|
+
sage: H = Graph(G.edges(sort=False))
|
1322
|
+
sage: H.add_edges(F)
|
1323
|
+
sage: H.is_chordal()
|
1324
|
+
True
|
1325
|
+
"""
|
1326
|
+
H = G.copy()
|
1327
|
+
H.add_edges(F)
|
1328
|
+
s_alpha = set(alpha)
|
1329
|
+
for u in alpha:
|
1330
|
+
K = H.subgraph(H.neighbors(u))
|
1331
|
+
s_alpha.discard(u)
|
1332
|
+
K.delete_vertices([v for v in K if v not in s_alpha])
|
1333
|
+
if not K.is_clique():
|
1334
|
+
return False
|
1335
|
+
return True
|
1336
|
+
|
1337
|
+
|
1338
|
+
def maximum_cardinality_search(G, reverse=False, tree=False, initial_vertex=None):
|
1339
|
+
r"""
|
1340
|
+
Return an ordering of the vertices according a maximum cardinality search.
|
1341
|
+
|
1342
|
+
Maximum cardinality search (MCS) is a graph traversal introduced in
|
1343
|
+
[TY1984]_. It starts by assigning an arbitrary vertex (or the specified
|
1344
|
+
``initial_vertex``) of `G` the last position in the ordering `\alpha`. Every
|
1345
|
+
vertex keeps a weight equal to the number of its already processed neighbors
|
1346
|
+
(i.e., already added to `\alpha`), and a vertex of largest such number is
|
1347
|
+
chosen at each step `i` to be placed in position `n - i` in `\alpha`. This
|
1348
|
+
ordering can be computed in time `O(n + m)`.
|
1349
|
+
|
1350
|
+
Time complexity is `O(n+m)` for ``SparseGraph`` and `O(n^2)` for
|
1351
|
+
``DenseGraph`` where `n` is the number of vertices and `m` is the number of
|
1352
|
+
edges.
|
1353
|
+
|
1354
|
+
When the graph is chordal, the ordering returned by MCS is a *perfect
|
1355
|
+
elimination ordering*, like :meth:`~sage.graphs.traversals.lex_BFS`. So
|
1356
|
+
this ordering can be used to recognize chordal graphs. See [He2006]_ for
|
1357
|
+
more details.
|
1358
|
+
|
1359
|
+
.. NOTE::
|
1360
|
+
|
1361
|
+
The current implementation is for connected graphs only.
|
1362
|
+
|
1363
|
+
INPUT:
|
1364
|
+
|
1365
|
+
- ``G`` -- a Sage graph
|
1366
|
+
|
1367
|
+
- ``reverse`` -- boolean (default: ``False``); whether to return the
|
1368
|
+
vertices in discovery order, or the reverse
|
1369
|
+
|
1370
|
+
- ``tree`` -- boolean (default: ``False``); whether to also return the
|
1371
|
+
discovery directed tree (each vertex being linked to the one that saw
|
1372
|
+
it for the first time)
|
1373
|
+
|
1374
|
+
- ``initial_vertex`` -- (default: ``None``) the first vertex to consider
|
1375
|
+
|
1376
|
+
OUTPUT:
|
1377
|
+
|
1378
|
+
By default, return the ordering `\alpha` as a list. When ``tree`` is
|
1379
|
+
``True``, the method returns a tuple `(\alpha, T)`, where `T` is a directed
|
1380
|
+
tree with the same set of vertices as `G` and a directed edge from `u` to `v`
|
1381
|
+
if `u` was the first vertex to see `v`.
|
1382
|
+
|
1383
|
+
EXAMPLES:
|
1384
|
+
|
1385
|
+
When specified, the ``initial_vertex`` is placed at the end of the ordering,
|
1386
|
+
unless parameter ``reverse`` is ``True``, in which case it is placed at the
|
1387
|
+
beginning::
|
1388
|
+
|
1389
|
+
sage: G = graphs.PathGraph(4)
|
1390
|
+
sage: G.maximum_cardinality_search(initial_vertex=0)
|
1391
|
+
[3, 2, 1, 0]
|
1392
|
+
sage: G.maximum_cardinality_search(initial_vertex=1)
|
1393
|
+
[3, 2, 0, 1]
|
1394
|
+
sage: G.maximum_cardinality_search(initial_vertex=2)
|
1395
|
+
[0, 3, 1, 2]
|
1396
|
+
sage: G.maximum_cardinality_search(initial_vertex=3)
|
1397
|
+
[0, 1, 2, 3]
|
1398
|
+
sage: G.maximum_cardinality_search(initial_vertex=3, reverse=True)
|
1399
|
+
[3, 2, 1, 0]
|
1400
|
+
|
1401
|
+
Returning the discovery tree::
|
1402
|
+
|
1403
|
+
sage: G = graphs.PathGraph(4)
|
1404
|
+
sage: _, T = G.maximum_cardinality_search(tree=True, initial_vertex=0)
|
1405
|
+
sage: T.order(), T.size()
|
1406
|
+
(4, 3)
|
1407
|
+
sage: T.edges(labels=False, sort=True)
|
1408
|
+
[(1, 0), (2, 1), (3, 2)]
|
1409
|
+
sage: _, T = G.maximum_cardinality_search(tree=True, initial_vertex=3)
|
1410
|
+
sage: T.edges(labels=False, sort=True)
|
1411
|
+
[(0, 1), (1, 2), (2, 3)]
|
1412
|
+
|
1413
|
+
TESTS::
|
1414
|
+
|
1415
|
+
sage: Graph().maximum_cardinality_search()
|
1416
|
+
[]
|
1417
|
+
sage: Graph(1).maximum_cardinality_search()
|
1418
|
+
[0]
|
1419
|
+
sage: Graph(2).maximum_cardinality_search()
|
1420
|
+
Traceback (most recent call last):
|
1421
|
+
...
|
1422
|
+
ValueError: the input graph is not connected
|
1423
|
+
sage: graphs.PathGraph(2).maximum_cardinality_search(initial_vertex=17)
|
1424
|
+
Traceback (most recent call last):
|
1425
|
+
...
|
1426
|
+
ValueError: vertex (17) is not a vertex of the graph
|
1427
|
+
|
1428
|
+
Immutable graphs;:
|
1429
|
+
|
1430
|
+
sage: G = graphs.RandomGNP(10, .7)
|
1431
|
+
sage: G._backend
|
1432
|
+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
|
1433
|
+
sage: H = Graph(G, immutable=True)
|
1434
|
+
sage: H._backend
|
1435
|
+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
|
1436
|
+
sage: G.maximum_cardinality_search() == H.maximum_cardinality_search()
|
1437
|
+
True
|
1438
|
+
"""
|
1439
|
+
if tree:
|
1440
|
+
from sage.graphs.digraph import DiGraph
|
1441
|
+
|
1442
|
+
cdef int N = G.order()
|
1443
|
+
if not N:
|
1444
|
+
return ([], DiGraph()) if tree else []
|
1445
|
+
if N == 1:
|
1446
|
+
return (list(G), DiGraph(G)) if tree else list(G)
|
1447
|
+
|
1448
|
+
cdef list int_to_vertex
|
1449
|
+
cdef StaticSparseCGraph cg
|
1450
|
+
cdef short_digraph sd
|
1451
|
+
if isinstance(G, StaticSparseBackend):
|
1452
|
+
cg = <StaticSparseCGraph> G._cg
|
1453
|
+
sd = <short_digraph> cg.g
|
1454
|
+
int_to_vertex = cg._vertex_to_labels
|
1455
|
+
else:
|
1456
|
+
int_to_vertex = list(G)
|
1457
|
+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
|
1458
|
+
|
1459
|
+
if initial_vertex is None:
|
1460
|
+
initial_vertex = 0
|
1461
|
+
elif initial_vertex in G:
|
1462
|
+
if isinstance(G, StaticSparseBackend):
|
1463
|
+
initial_vertex = cg._vertex_to_int[initial_vertex]
|
1464
|
+
else:
|
1465
|
+
initial_vertex = int_to_vertex.index(initial_vertex)
|
1466
|
+
else:
|
1467
|
+
raise ValueError("vertex ({0}) is not a vertex of the graph".format(initial_vertex))
|
1468
|
+
|
1469
|
+
cdef uint32_t** p_vertices = sd.neighbors
|
1470
|
+
cdef uint32_t* p_tmp
|
1471
|
+
cdef uint32_t* p_end
|
1472
|
+
|
1473
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
1474
|
+
cdef int* weight = <int*>mem.calloc(N, sizeof(int))
|
1475
|
+
cdef bint* seen = <bint*>mem.calloc(N, sizeof(bint))
|
1476
|
+
cdef int* pred = <int *>mem.allocarray(N, sizeof(int))
|
1477
|
+
|
1478
|
+
cdef int i, u, v
|
1479
|
+
for i in range(N):
|
1480
|
+
pred[i] = i
|
1481
|
+
|
1482
|
+
# We emulate a max-heap data structure using a min-heap with negative values
|
1483
|
+
cdef PairingHeap_of_n_integers P = PairingHeap_of_n_integers(N)
|
1484
|
+
P.push(initial_vertex, 0)
|
1485
|
+
|
1486
|
+
# The ordering alpha is feed in reversed order and revert afterword
|
1487
|
+
cdef list alpha = []
|
1488
|
+
|
1489
|
+
while P:
|
1490
|
+
u = P.top_item()
|
1491
|
+
P.pop()
|
1492
|
+
alpha.append(int_to_vertex[u])
|
1493
|
+
seen[u] = True
|
1494
|
+
|
1495
|
+
p_tmp = p_vertices[u]
|
1496
|
+
p_end = p_vertices[u + 1]
|
1497
|
+
while p_tmp < p_end:
|
1498
|
+
v = p_tmp[0]
|
1499
|
+
if not seen[v]:
|
1500
|
+
weight[v] += 1
|
1501
|
+
P.decrease(v, -weight[v])
|
1502
|
+
if pred[v] == v:
|
1503
|
+
pred[v] = u
|
1504
|
+
p_tmp += 1
|
1505
|
+
|
1506
|
+
if not isinstance(G, StaticSparseBackend):
|
1507
|
+
free_short_digraph(sd)
|
1508
|
+
|
1509
|
+
if len(alpha) < N:
|
1510
|
+
raise ValueError("the input graph is not connected")
|
1511
|
+
|
1512
|
+
if not reverse:
|
1513
|
+
alpha.reverse()
|
1514
|
+
|
1515
|
+
if tree:
|
1516
|
+
D = DiGraph([int_to_vertex, [(int_to_vertex[i], int_to_vertex[pred[i]])
|
1517
|
+
for i in range(N) if pred[i] != i]],
|
1518
|
+
format='vertices_and_edges')
|
1519
|
+
return alpha, D
|
1520
|
+
|
1521
|
+
return alpha
|
1522
|
+
|
1523
|
+
|
1524
|
+
cdef inline int swap(int* alpha, int* alpha_inv, int u, int new_pos_u) noexcept:
|
1525
|
+
"""
|
1526
|
+
Swap positions of u and v in alpha, where v is be the vertex occupying cell
|
1527
|
+
new_pos_u in alpha.
|
1528
|
+
"""
|
1529
|
+
cdef int v = alpha[new_pos_u]
|
1530
|
+
alpha[new_pos_u], alpha[alpha_inv[u]] = u, v
|
1531
|
+
alpha_inv[u], alpha_inv[v] = alpha_inv[v], alpha_inv[u]
|
1532
|
+
return v
|
1533
|
+
|
1534
|
+
|
1535
|
+
cdef maximum_cardinality_search_M_short_digraph(short_digraph sd, int initial_vertex,
|
1536
|
+
int* alpha, int* alpha_inv, list F, bint* X):
|
1537
|
+
r"""
|
1538
|
+
Compute the ordering and the edges of the triangulation produced by MCS-M.
|
1539
|
+
|
1540
|
+
Maximum cardinality search M (MCS-M) is an extension of MCS
|
1541
|
+
(:meth:`~sage.graphs.traversals.maximum_cardinality_search`) in the same way
|
1542
|
+
that Lex-M (:meth:`~sage.graphs.traversals.lex_M`) is an extension of
|
1543
|
+
Lex-BFS (:meth:`~sage.graphs.traversalslex_BFS`). That is, in MCS-M when `u`
|
1544
|
+
receives number `i` at step `n - i + 1`, it increments the weight of all
|
1545
|
+
unnumbered vertices `v` for which there exists a path between `u` and `v`
|
1546
|
+
consisting only of unnumbered vertices with weight strictly less than
|
1547
|
+
`w^-(u)` and `w^-(v)`, where `w^-` is the number of times a vertex has been
|
1548
|
+
reached during previous iterations. See [BBHP2004]_ for the details of this
|
1549
|
+
`O(nm)` time algorithm.
|
1550
|
+
|
1551
|
+
If `G` is not connected, the orderings of each of its connected components
|
1552
|
+
are added consecutively.
|
1553
|
+
|
1554
|
+
This method is the core of
|
1555
|
+
:meth:`~sage.graphs.traversals.maximum_cardinality_search_M`.
|
1556
|
+
|
1557
|
+
INPUT:
|
1558
|
+
|
1559
|
+
- ``sd`` -- a ``short_digraph`` as documented in
|
1560
|
+
:mod:`~sage.graphs.base.static_sparse_graph`
|
1561
|
+
|
1562
|
+
- ``initial_vertex`` -- integer; initial vertex for the search
|
1563
|
+
|
1564
|
+
- ``alpha`` -- int array of size `N`; the computed ordering of MCS-M
|
1565
|
+
|
1566
|
+
- ``alpha_inv`` -- int array of size `N`; the position of vertex ``u`` in
|
1567
|
+
``alpha``, that is the inverse function of alpha. So we have
|
1568
|
+
``alpha[alpha_inv[u]] == u`` for all `0 \leq u < N - 1`.
|
1569
|
+
|
1570
|
+
- ``F`` -- list; to be filled with the edges of the triangulation
|
1571
|
+
|
1572
|
+
- ``X`` -- boolean array of size `N`; ``X[u]`` is set to ``True`` if the
|
1573
|
+
neighborhood of `u` is a separator of the graph
|
1574
|
+
|
1575
|
+
TESTS::
|
1576
|
+
|
1577
|
+
sage: Graph().maximum_cardinality_search_M()
|
1578
|
+
([], [], [])
|
1579
|
+
sage: Graph(1).maximum_cardinality_search_M()
|
1580
|
+
([0], [], [])
|
1581
|
+
sage: graphs.PathGraph(2).maximum_cardinality_search_M(initial_vertex=17)
|
1582
|
+
Traceback (most recent call last):
|
1583
|
+
...
|
1584
|
+
ValueError: vertex (17) is not a vertex of the graph
|
1585
|
+
|
1586
|
+
.. TODO::
|
1587
|
+
|
1588
|
+
Use a fast heap data structure with decrease-key operation.
|
1589
|
+
"""
|
1590
|
+
# Initialization of data structures
|
1591
|
+
cdef int N = sd.n
|
1592
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
1593
|
+
# number of times a vertex is reached, initially 0
|
1594
|
+
cdef int* weight = <int*>mem.calloc(N, sizeof(int))
|
1595
|
+
# has a vertex been reached, initially False
|
1596
|
+
cdef bint* reached = <bint*>mem.calloc(N, sizeof(bint))
|
1597
|
+
|
1598
|
+
cdef int i, u, v, xi
|
1599
|
+
for i in range(N):
|
1600
|
+
weight[i] = 0
|
1601
|
+
alpha[i] = i
|
1602
|
+
alpha_inv[i] = i
|
1603
|
+
X[i] = False
|
1604
|
+
|
1605
|
+
# If an initial vertex is specified, we put it at position 0 in alpha.
|
1606
|
+
# This way, it will be the first vertex to be considered.
|
1607
|
+
if initial_vertex:
|
1608
|
+
swap(alpha, alpha_inv, initial_vertex, 0)
|
1609
|
+
|
1610
|
+
# variables for the manipulation of the short digraph
|
1611
|
+
cdef uint32_t** p_vertices = sd.neighbors
|
1612
|
+
cdef uint32_t* p_tmp
|
1613
|
+
cdef uint32_t* p_end
|
1614
|
+
|
1615
|
+
cdef vector[vector[int]] reach
|
1616
|
+
cdef int s = -1
|
1617
|
+
cdef int current_pos = N
|
1618
|
+
|
1619
|
+
while current_pos:
|
1620
|
+
|
1621
|
+
# Choose an unnumbered vertex of maximum weight.
|
1622
|
+
# This could be done faster if we had a heap data structure with
|
1623
|
+
# decrease key operation.
|
1624
|
+
u = alpha[0]
|
1625
|
+
for i in range(current_pos):
|
1626
|
+
v = alpha[i]
|
1627
|
+
if weight[u] < weight[v]:
|
1628
|
+
u = v
|
1629
|
+
|
1630
|
+
# Swap u and the vertex v occupying position current_pos in alpha
|
1631
|
+
current_pos -= 1
|
1632
|
+
v = swap(alpha, alpha_inv, u, current_pos)
|
1633
|
+
reached[u] = True
|
1634
|
+
|
1635
|
+
# If the weight decreases, the neighborhood of u is a separator
|
1636
|
+
if weight[u] <= s:
|
1637
|
+
X[u] = True
|
1638
|
+
s = weight[u]
|
1639
|
+
|
1640
|
+
# Search for new edges of the triangulation.
|
1641
|
+
# We add an edge to the triangulation between u and any unnumbered
|
1642
|
+
# vertex v such that there is a path (v, x1, x2,... , xk, u) through
|
1643
|
+
# unnumbered vertices such that count-[xi] < count-[v], 1 <= i <= k. If
|
1644
|
+
# such an edge is found, we increase the count of v for next round.
|
1645
|
+
|
1646
|
+
# Mark all unnumbered vertices unreached. These vertices occupy
|
1647
|
+
# positions 0,..,current_pos-1 in alpha
|
1648
|
+
reach.clear()
|
1649
|
+
reach.resize(N)
|
1650
|
+
for i in range(current_pos):
|
1651
|
+
v = alpha[i]
|
1652
|
+
reached[v] = False
|
1653
|
+
|
1654
|
+
# Initialize reach with unnumbered neighbors of u
|
1655
|
+
p_tmp = p_vertices[u]
|
1656
|
+
p_end = p_vertices[u + 1]
|
1657
|
+
while p_tmp < p_end:
|
1658
|
+
v = p_tmp[0]
|
1659
|
+
p_tmp += 1
|
1660
|
+
if not reached[v]:
|
1661
|
+
reach[weight[v]].push_back(v)
|
1662
|
+
reached[v] = True
|
1663
|
+
weight[v] += 1
|
1664
|
+
|
1665
|
+
# Search
|
1666
|
+
for i in range(N):
|
1667
|
+
while not reach[i].empty():
|
1668
|
+
xi = reach[i].back()
|
1669
|
+
reach[i].pop_back()
|
1670
|
+
p_tmp = p_vertices[xi]
|
1671
|
+
p_end = p_vertices[xi + 1]
|
1672
|
+
while p_tmp < p_end:
|
1673
|
+
v = p_tmp[0]
|
1674
|
+
p_tmp += 1
|
1675
|
+
if reached[v]:
|
1676
|
+
continue
|
1677
|
+
reached[v] = True
|
1678
|
+
if i < weight[v]:
|
1679
|
+
reach[weight[v]].push_back(v)
|
1680
|
+
weight[v] += 1
|
1681
|
+
F.append((u, v))
|
1682
|
+
else:
|
1683
|
+
reach[i].push_back(v)
|
1684
|
+
|
1685
|
+
reach.clear()
|
1686
|
+
|
1687
|
+
|
1688
|
+
def maximum_cardinality_search_M(G, initial_vertex=None):
|
1689
|
+
r"""
|
1690
|
+
Return the ordering and the edges of the triangulation produced by MCS-M.
|
1691
|
+
|
1692
|
+
Maximum cardinality search M (MCS-M) is an extension of MCS
|
1693
|
+
(:meth:`~sage.graphs.traversals.maximum_cardinality_search`) in the same way
|
1694
|
+
that Lex-M (:meth:`~sage.graphs.traversals.lex_M`) is an extension of
|
1695
|
+
Lex-BFS (:meth:`~sage.graphs.traversals.lex_BFS`). That is, in MCS-M when
|
1696
|
+
`u` receives number `i` at step `n - i + 1`, it increments the weight of all
|
1697
|
+
unnumbered vertices `v` for which there exists a path between `u` and `v`
|
1698
|
+
consisting only of unnumbered vertices with weight strictly less than
|
1699
|
+
`w^-(u)` and `w^-(v)`, where `w^-` is the number of times a vertex has been
|
1700
|
+
reached during previous iterations. See [BBHP2004]_ for the details of this
|
1701
|
+
`O(nm)` time algorithm.
|
1702
|
+
|
1703
|
+
If `G` is not connected, the orderings of each of its connected components
|
1704
|
+
are added consecutively. Furthermore, if `G` has `k` connected components
|
1705
|
+
`C_i` for `0 \leq i < k`, `X` contains at least one vertex of `C_i` for each
|
1706
|
+
`i \geq 1`. Hence, `|X| \geq k - 1`. In particular, some isolated vertices
|
1707
|
+
(i.e., of degree 0) can appear in `X` as for such a vertex `x`, we have that
|
1708
|
+
`G \setminus N(x) = G` is not connected.
|
1709
|
+
|
1710
|
+
INPUT:
|
1711
|
+
|
1712
|
+
- ``G`` -- a Sage graph
|
1713
|
+
|
1714
|
+
- ``initial_vertex`` -- (default: ``None``) the first vertex to consider
|
1715
|
+
|
1716
|
+
OUTPUT: a tuple `(\alpha, F, X)`, where
|
1717
|
+
|
1718
|
+
- `\alpha` is the resulting ordering of the vertices. If an initial vertex
|
1719
|
+
is specified, it gets the last position in the ordering `\alpha`.
|
1720
|
+
|
1721
|
+
- `F` is the list of edges of a minimal triangulation of `G` according
|
1722
|
+
`\alpha`
|
1723
|
+
|
1724
|
+
- `X` is a list of vertices such that for each `x \in X`, the
|
1725
|
+
neighborhood of `x` in `G` is a separator (i.e., `G \setminus N(x)` is not
|
1726
|
+
connected). Note that we may have `N(x) = \emptyset` if `G` is not
|
1727
|
+
connected and `x` has degree 0.
|
1728
|
+
|
1729
|
+
EXAMPLES:
|
1730
|
+
|
1731
|
+
Chordal graphs have a perfect elimination ordering, and so the set `F` of
|
1732
|
+
edges of the triangulation is empty::
|
1733
|
+
|
1734
|
+
sage: G = graphs.RandomChordalGraph(20)
|
1735
|
+
sage: alpha, F, X = G.maximum_cardinality_search_M(); F
|
1736
|
+
[]
|
1737
|
+
|
1738
|
+
The cycle of order 4 is not chordal and so the triangulation has one edge::
|
1739
|
+
|
1740
|
+
sage: G = graphs.CycleGraph(4)
|
1741
|
+
sage: alpha, F, X = G.maximum_cardinality_search_M(); len(F)
|
1742
|
+
1
|
1743
|
+
|
1744
|
+
The number of edges needed to triangulate of a cycle graph or order `n` is
|
1745
|
+
`n - 3`, independently of the initial vertex::
|
1746
|
+
|
1747
|
+
sage: n = randint(3, 20)
|
1748
|
+
sage: C = graphs.CycleGraph(n)
|
1749
|
+
sage: _, F, X = C.maximum_cardinality_search_M()
|
1750
|
+
sage: len(F) == n - 3
|
1751
|
+
True
|
1752
|
+
sage: _, F, X = C.maximum_cardinality_search_M(initial_vertex=C.random_vertex())
|
1753
|
+
sage: len(F) == n - 3
|
1754
|
+
True
|
1755
|
+
|
1756
|
+
When an initial vertex is specified, it gets the last position in the
|
1757
|
+
ordering::
|
1758
|
+
|
1759
|
+
sage: G = graphs.PathGraph(4)
|
1760
|
+
sage: G.maximum_cardinality_search_M(initial_vertex=0)
|
1761
|
+
([3, 2, 1, 0], [], [2, 3])
|
1762
|
+
sage: G.maximum_cardinality_search_M(initial_vertex=1)
|
1763
|
+
([3, 2, 0, 1], [], [2, 3])
|
1764
|
+
sage: G.maximum_cardinality_search_M(initial_vertex=2)
|
1765
|
+
([0, 1, 3, 2], [], [0, 1])
|
1766
|
+
sage: G.maximum_cardinality_search_M(initial_vertex=3)
|
1767
|
+
([0, 1, 2, 3], [], [0, 1])
|
1768
|
+
|
1769
|
+
|
1770
|
+
When `G` is not connected, the orderings of each of its connected components
|
1771
|
+
are added consecutively, the vertices of the component containing the
|
1772
|
+
initial vertex occupying the last positions::
|
1773
|
+
|
1774
|
+
sage: G = graphs.CycleGraph(4) * 2
|
1775
|
+
sage: G.maximum_cardinality_search_M()[0]
|
1776
|
+
[5, 4, 6, 7, 2, 3, 1, 0]
|
1777
|
+
sage: G.maximum_cardinality_search_M(initial_vertex=7)[0]
|
1778
|
+
[2, 1, 3, 0, 5, 6, 4, 7]
|
1779
|
+
|
1780
|
+
Furthermore, if `G` has `k` connected components, `X` contains at least one
|
1781
|
+
vertex per connected component, except for the first one, and so at least `k
|
1782
|
+
- 1` vertices::
|
1783
|
+
|
1784
|
+
sage: for k in range(1, 5):
|
1785
|
+
....: _, _, X = Graph(k).maximum_cardinality_search_M()
|
1786
|
+
....: if len(X) < k - 1:
|
1787
|
+
....: raise ValueError("something goes wrong")
|
1788
|
+
sage: G = graphs.RandomGNP(10, .2)
|
1789
|
+
sage: cc = G.connected_components(sort=False)
|
1790
|
+
sage: _, _, X = G.maximum_cardinality_search_M()
|
1791
|
+
sage: len(X) >= len(cc) - 1
|
1792
|
+
True
|
1793
|
+
|
1794
|
+
In the example of [BPS2010]_, the triangulation has 3 edges::
|
1795
|
+
|
1796
|
+
sage: G = Graph({'a': ['b', 'k'], 'b': ['c'], 'c': ['d', 'j', 'k'],
|
1797
|
+
....: 'd': ['e', 'f', 'j', 'k'], 'e': ['g'],
|
1798
|
+
....: 'f': ['g', 'j', 'k'], 'g': ['j', 'k'], 'h': ['i', 'j'],
|
1799
|
+
....: 'i': ['k'], 'j': ['k']})
|
1800
|
+
sage: _, F, _ = G.maximum_cardinality_search_M(initial_vertex='a')
|
1801
|
+
sage: len(F)
|
1802
|
+
3
|
1803
|
+
|
1804
|
+
TESTS::
|
1805
|
+
|
1806
|
+
sage: Graph().maximum_cardinality_search_M()
|
1807
|
+
([], [], [])
|
1808
|
+
sage: Graph(1).maximum_cardinality_search_M()
|
1809
|
+
([0], [], [])
|
1810
|
+
sage: graphs.PathGraph(2).maximum_cardinality_search_M(initial_vertex=17)
|
1811
|
+
Traceback (most recent call last):
|
1812
|
+
...
|
1813
|
+
ValueError: vertex (17) is not a vertex of the graph
|
1814
|
+
|
1815
|
+
Immutable graphs::
|
1816
|
+
|
1817
|
+
sage: G = graphs.RandomGNP(10, .7)
|
1818
|
+
sage: G._backend
|
1819
|
+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
|
1820
|
+
sage: H = Graph(G, immutable=True)
|
1821
|
+
sage: H._backend
|
1822
|
+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
|
1823
|
+
sage: G.maximum_cardinality_search_M() == H.maximum_cardinality_search_M()
|
1824
|
+
True
|
1825
|
+
"""
|
1826
|
+
cdef int N = G.order()
|
1827
|
+
if not N:
|
1828
|
+
return ([], [], [])
|
1829
|
+
if N == 1:
|
1830
|
+
return (list(G), [], [])
|
1831
|
+
|
1832
|
+
# Copying the whole graph to obtain the list of neighbors quicker than by
|
1833
|
+
# calling out_neighbors. This data structure is well documented in the
|
1834
|
+
# module sage.graphs.base.static_sparse_graph
|
1835
|
+
cdef list int_to_vertex
|
1836
|
+
cdef StaticSparseCGraph cg
|
1837
|
+
cdef short_digraph sd
|
1838
|
+
if isinstance(G, StaticSparseBackend):
|
1839
|
+
cg = <StaticSparseCGraph> G._cg
|
1840
|
+
sd = <short_digraph> cg.g
|
1841
|
+
int_to_vertex = cg._vertex_to_labels
|
1842
|
+
else:
|
1843
|
+
int_to_vertex = list(G)
|
1844
|
+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
|
1845
|
+
|
1846
|
+
if initial_vertex is None:
|
1847
|
+
initial_vertex = 0
|
1848
|
+
elif initial_vertex in G:
|
1849
|
+
if isinstance(G, StaticSparseBackend):
|
1850
|
+
initial_vertex = cg._vertex_to_int[initial_vertex]
|
1851
|
+
else:
|
1852
|
+
initial_vertex = int_to_vertex.index(initial_vertex)
|
1853
|
+
else:
|
1854
|
+
raise ValueError("vertex ({0}) is not a vertex of the graph".format(initial_vertex))
|
1855
|
+
|
1856
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
1857
|
+
cdef int* alpha = <int*>mem.calloc(N, sizeof(int))
|
1858
|
+
cdef int* alpha_inv = <int*>mem.calloc(N, sizeof(int))
|
1859
|
+
cdef bint* X = <bint*>mem.calloc(N, sizeof(bint))
|
1860
|
+
cdef list F = []
|
1861
|
+
|
1862
|
+
sig_on()
|
1863
|
+
maximum_cardinality_search_M_short_digraph(sd, initial_vertex, alpha, alpha_inv, F, X)
|
1864
|
+
sig_off()
|
1865
|
+
|
1866
|
+
if not isinstance(G, StaticSparseBackend):
|
1867
|
+
free_short_digraph(sd)
|
1868
|
+
|
1869
|
+
cdef int u, v
|
1870
|
+
return ([int_to_vertex[alpha[u]] for u in range(N)],
|
1871
|
+
[(int_to_vertex[u], int_to_vertex[v]) for u, v in F],
|
1872
|
+
[int_to_vertex[u] for u in range(N) if X[u]])
|