passagemath-graphs 10.5.43__cp39-cp39-musllinux_1_2_aarch64.whl

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