passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
  2. passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
  3. passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
  5. passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
  6. passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
  7. passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
  8. sage/all__sagemath_graphs.py +39 -0
  9. sage/combinat/abstract_tree.py +2723 -0
  10. sage/combinat/all__sagemath_graphs.py +34 -0
  11. sage/combinat/binary_tree.py +5306 -0
  12. sage/combinat/cluster_algebra_quiver/all.py +22 -0
  13. sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
  14. sage/combinat/cluster_algebra_quiver/interact.py +124 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
  18. sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
  19. sage/combinat/designs/MOLS_handbook_data.py +570 -0
  20. sage/combinat/designs/all.py +58 -0
  21. sage/combinat/designs/bibd.py +1655 -0
  22. sage/combinat/designs/block_design.py +1071 -0
  23. sage/combinat/designs/covering_array.py +269 -0
  24. sage/combinat/designs/covering_design.py +530 -0
  25. sage/combinat/designs/database.py +5615 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  28. sage/combinat/designs/designs_pyx.pxd +21 -0
  29. sage/combinat/designs/designs_pyx.pyx +993 -0
  30. sage/combinat/designs/difference_family.py +3951 -0
  31. sage/combinat/designs/difference_matrices.py +279 -0
  32. sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  33. sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
  34. sage/combinat/designs/ext_rep.py +1064 -0
  35. sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
  36. sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
  37. sage/combinat/designs/group_divisible_designs.py +361 -0
  38. sage/combinat/designs/incidence_structures.py +2357 -0
  39. sage/combinat/designs/latin_squares.py +581 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2244 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
  44. sage/combinat/designs/resolvable_bibd.py +815 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
  47. sage/combinat/designs/subhypergraph_search.pyx +530 -0
  48. sage/combinat/designs/twographs.py +306 -0
  49. sage/combinat/finite_state_machine.py +14874 -0
  50. sage/combinat/finite_state_machine_generators.py +2006 -0
  51. sage/combinat/graph_path.py +448 -0
  52. sage/combinat/interval_posets.py +3908 -0
  53. sage/combinat/nu_tamari_lattice.py +269 -0
  54. sage/combinat/ordered_tree.py +1446 -0
  55. sage/combinat/posets/all.py +46 -0
  56. sage/combinat/posets/bubble_shuffle.py +247 -0
  57. sage/combinat/posets/cartesian_product.py +493 -0
  58. sage/combinat/posets/d_complete.py +182 -0
  59. sage/combinat/posets/elements.py +273 -0
  60. sage/combinat/posets/forest.py +30 -0
  61. sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
  62. sage/combinat/posets/hasse_cython.pyx +174 -0
  63. sage/combinat/posets/hasse_diagram.py +3672 -0
  64. sage/combinat/posets/hochschild_lattice.py +158 -0
  65. sage/combinat/posets/incidence_algebras.py +794 -0
  66. sage/combinat/posets/lattices.py +5117 -0
  67. sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
  68. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  69. sage/combinat/posets/linear_extensions.py +1037 -0
  70. sage/combinat/posets/mobile.py +275 -0
  71. sage/combinat/posets/moebius_algebra.py +776 -0
  72. sage/combinat/posets/poset_examples.py +2178 -0
  73. sage/combinat/posets/posets.py +9360 -0
  74. sage/combinat/rooted_tree.py +1070 -0
  75. sage/combinat/shard_order.py +239 -0
  76. sage/combinat/tamari_lattices.py +384 -0
  77. sage/combinat/yang_baxter_graph.py +923 -0
  78. sage/databases/all__sagemath_graphs.py +1 -0
  79. sage/databases/knotinfo_db.py +1231 -0
  80. sage/ext_data/all__sagemath_graphs.py +1 -0
  81. sage/ext_data/graphs/graph_plot_js.html +330 -0
  82. sage/ext_data/kenzo/CP2.txt +45 -0
  83. sage/ext_data/kenzo/CP3.txt +349 -0
  84. sage/ext_data/kenzo/CP4.txt +4774 -0
  85. sage/ext_data/kenzo/README.txt +49 -0
  86. sage/ext_data/kenzo/S4.txt +20 -0
  87. sage/graphs/all.py +42 -0
  88. sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
  89. sage/graphs/asteroidal_triples.pyx +320 -0
  90. sage/graphs/base/all.py +1 -0
  91. sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  92. sage/graphs/base/boost_graph.pxd +106 -0
  93. sage/graphs/base/boost_graph.pyx +3045 -0
  94. sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  95. sage/graphs/base/c_graph.pxd +106 -0
  96. sage/graphs/base/c_graph.pyx +5096 -0
  97. sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sage/graphs/base/dense_graph.pxd +28 -0
  99. sage/graphs/base/dense_graph.pyx +801 -0
  100. sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
  101. sage/graphs/base/graph_backends.pxd +5 -0
  102. sage/graphs/base/graph_backends.pyx +797 -0
  103. sage/graphs/base/overview.py +85 -0
  104. sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  105. sage/graphs/base/sparse_graph.pxd +90 -0
  106. sage/graphs/base/sparse_graph.pyx +1653 -0
  107. sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  108. sage/graphs/base/static_dense_graph.pxd +5 -0
  109. sage/graphs/base/static_dense_graph.pyx +1032 -0
  110. sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sage/graphs/base/static_sparse_backend.pxd +27 -0
  112. sage/graphs/base/static_sparse_backend.pyx +1583 -0
  113. sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  114. sage/graphs/base/static_sparse_graph.pxd +37 -0
  115. sage/graphs/base/static_sparse_graph.pyx +1375 -0
  116. sage/graphs/bipartite_graph.py +2732 -0
  117. sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
  118. sage/graphs/centrality.pyx +1038 -0
  119. sage/graphs/cographs.py +519 -0
  120. sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/comparability.pyx +851 -0
  122. sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  123. sage/graphs/connectivity.pxd +157 -0
  124. sage/graphs/connectivity.pyx +4813 -0
  125. sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
  126. sage/graphs/convexity_properties.pxd +16 -0
  127. sage/graphs/convexity_properties.pyx +870 -0
  128. sage/graphs/digraph.py +4754 -0
  129. sage/graphs/digraph_generators.py +1993 -0
  130. sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
  131. sage/graphs/distances_all_pairs.pxd +12 -0
  132. sage/graphs/distances_all_pairs.pyx +2938 -0
  133. sage/graphs/domination.py +1363 -0
  134. sage/graphs/dot2tex_utils.py +100 -0
  135. sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  136. sage/graphs/edge_connectivity.pyx +1215 -0
  137. sage/graphs/generators/all.py +1 -0
  138. sage/graphs/generators/basic.py +1769 -0
  139. sage/graphs/generators/chessboard.py +538 -0
  140. sage/graphs/generators/classical_geometries.py +1611 -0
  141. sage/graphs/generators/degree_sequence.py +235 -0
  142. sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
  143. sage/graphs/generators/distance_regular.pyx +2846 -0
  144. sage/graphs/generators/families.py +4759 -0
  145. sage/graphs/generators/intersection.py +565 -0
  146. sage/graphs/generators/platonic_solids.py +262 -0
  147. sage/graphs/generators/random.py +2623 -0
  148. sage/graphs/generators/smallgraphs.py +5741 -0
  149. sage/graphs/generators/world_map.py +724 -0
  150. sage/graphs/generic_graph.py +26867 -0
  151. sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  152. sage/graphs/generic_graph_pyx.pxd +34 -0
  153. sage/graphs/generic_graph_pyx.pyx +1673 -0
  154. sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
  155. sage/graphs/genus.pyx +622 -0
  156. sage/graphs/graph.py +9645 -0
  157. sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sage/graphs/graph_coloring.pyx +2284 -0
  159. sage/graphs/graph_database.py +1177 -0
  160. sage/graphs/graph_decompositions/all.py +1 -0
  161. sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  163. sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
  165. sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  167. sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
  168. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  169. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  170. sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/graph_products.pyx +508 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  173. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  174. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  176. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  177. sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  179. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  180. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  181. sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
  182. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  183. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  184. sage/graphs/graph_editor.py +82 -0
  185. sage/graphs/graph_generators.py +3314 -0
  186. sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  187. sage/graphs/graph_generators_pyx.pyx +95 -0
  188. sage/graphs/graph_input.py +812 -0
  189. sage/graphs/graph_latex.py +2064 -0
  190. sage/graphs/graph_list.py +410 -0
  191. sage/graphs/graph_plot.py +1756 -0
  192. sage/graphs/graph_plot_js.py +338 -0
  193. sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
  194. sage/graphs/hyperbolicity.pyx +1704 -0
  195. sage/graphs/hypergraph_generators.py +364 -0
  196. sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sage/graphs/independent_sets.pxd +13 -0
  198. sage/graphs/independent_sets.pyx +402 -0
  199. sage/graphs/isgci.py +1033 -0
  200. sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/isoperimetric_inequalities.pyx +489 -0
  202. sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  203. sage/graphs/line_graph.pyx +743 -0
  204. sage/graphs/lovasz_theta.py +77 -0
  205. sage/graphs/matching.py +1633 -0
  206. sage/graphs/matching_covered_graph.py +3590 -0
  207. sage/graphs/orientations.py +1489 -0
  208. sage/graphs/partial_cube.py +459 -0
  209. sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
  210. sage/graphs/path_enumeration.pyx +2040 -0
  211. sage/graphs/pq_trees.py +1129 -0
  212. sage/graphs/print_graphs.py +201 -0
  213. sage/graphs/schnyder.py +865 -0
  214. sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/spanning_tree.pyx +1457 -0
  216. sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/strongly_regular_db.pyx +3340 -0
  218. sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
  219. sage/graphs/traversals.pxd +9 -0
  220. sage/graphs/traversals.pyx +1872 -0
  221. sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sage/graphs/trees.pxd +15 -0
  223. sage/graphs/trees.pyx +310 -0
  224. sage/graphs/tutte_polynomial.py +713 -0
  225. sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/views.pyx +794 -0
  227. sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
  228. sage/graphs/weakly_chordal.pyx +604 -0
  229. sage/groups/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  231. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
  233. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  234. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  235. sage/knots/all.py +6 -0
  236. sage/knots/free_knotinfo_monoid.py +507 -0
  237. sage/knots/gauss_code.py +291 -0
  238. sage/knots/knot.py +682 -0
  239. sage/knots/knot_table.py +284 -0
  240. sage/knots/knotinfo.py +2900 -0
  241. sage/knots/link.py +4715 -0
  242. sage/sandpiles/all.py +13 -0
  243. sage/sandpiles/examples.py +225 -0
  244. sage/sandpiles/sandpile.py +6365 -0
  245. sage/topology/all.py +22 -0
  246. sage/topology/cell_complex.py +1214 -0
  247. sage/topology/cubical_complex.py +1976 -0
  248. sage/topology/delta_complex.py +1806 -0
  249. sage/topology/filtered_simplicial_complex.py +744 -0
  250. sage/topology/moment_angle_complex.py +823 -0
  251. sage/topology/simplicial_complex.py +5160 -0
  252. sage/topology/simplicial_complex_catalog.py +92 -0
  253. sage/topology/simplicial_complex_examples.py +1680 -0
  254. sage/topology/simplicial_complex_homset.py +205 -0
  255. sage/topology/simplicial_complex_morphism.py +836 -0
  256. sage/topology/simplicial_set.py +4102 -0
  257. sage/topology/simplicial_set_catalog.py +55 -0
  258. sage/topology/simplicial_set_constructions.py +2954 -0
  259. sage/topology/simplicial_set_examples.py +865 -0
  260. sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,1536 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ # distutils: language = c++
3
+ # distutils: extra_compile_args = -std=c++11
4
+ r"""
5
+ Modular Decomposition
6
+
7
+ This module implements the function for computing the modular decomposition
8
+ of undirected graphs.
9
+
10
+ AUTHORS:
11
+
12
+ - Lokesh Jain (2017): first implementation of the linear time algorithm of
13
+ D. Corneil, M. Habib, C. Paul and M. Tedder [TCHP2008]_
14
+
15
+ - David Einstein (2018): added the algorithm of M. Habib and M. Maurer [HM1979]_
16
+
17
+ - Cyril Bouvier (2024): second implementation of the linear time algorithm
18
+ of D. Corneil, M. Habib, C. Paul and M. Tedder [TCHP2008]_
19
+ """
20
+ # ****************************************************************************
21
+ # Copyright (C) 2017 Lokesh Jain <lokeshj1703@gmail.com>
22
+ # 2018 David Einstein <deinst@gmail.com>
23
+ # 2024 Cyril Bouvier <cyril.bouvier@lirmm.fr>
24
+ #
25
+ # This program is free software: you can redistribute it and/or modify
26
+ # it under the terms of the GNU General Public License as published by
27
+ # the Free Software Foundation, either version 2 of the License, or
28
+ # (at your option) any later version.
29
+ # https://www.gnu.org/licenses/
30
+ # ****************************************************************************
31
+ from cython.operator cimport dereference as deref
32
+
33
+ from enum import IntEnum
34
+
35
+ from sage.graphs.base.c_graph cimport CGraph, CGraphBackend
36
+ from sage.graphs.graph_decompositions.slice_decomposition cimport \
37
+ extended_lex_BFS
38
+ from sage.misc.random_testing import random_testing
39
+
40
+ try:
41
+ from sage.groups.perm_gps.permgroup_element import PermutationGroupElement
42
+ except ImportError:
43
+ PermutationGroupElement = ()
44
+
45
+
46
+ ################################################################################
47
+ # Corneil-Habib-Paul-Tedder algorithm #
48
+ ################################################################################
49
+ def corneil_habib_paul_tedder_algorithm(G):
50
+ r"""
51
+ Compute the modular decomposition by the algorithm of Corneil, Habib, Paul
52
+ and Tedder.
53
+
54
+ INPUT:
55
+
56
+ - ``G`` -- the graph for which modular decomposition tree needs to be
57
+ computed
58
+
59
+ OUTPUT: an object of type Node representing the modular decomposition tree
60
+ of the graph G
61
+
62
+ This function computes the modular decomposition of the given graph by the
63
+ algorithm of Corneil, Habib, Paul and Tedder [TCHP2008]_. It is a recursive,
64
+ linear-time algorithm that first computes the slice decomposition of the
65
+ graph (via the extended lexBFS algorithm) and then computes the modular
66
+ decomposition by calling itself recursively on the slices of the previously
67
+ computed slice decomposition.
68
+
69
+ This functions is based on the last version of the paper [TCHP2008]_.
70
+ Previous versions of the paper and previous implementations were found to
71
+ contains errors, see [AP2024]_.
72
+
73
+ .. SEEALSO::
74
+
75
+ * :mod:`~sage.graphs.graph_decompositions.slice_decomposition` --
76
+ compute a slice decomposition of the simple undirect graph
77
+
78
+ This function should not be used directly, it should be called via the
79
+ ``modular_decomposition`` method of ``Graph`` with the parameter
80
+ ``algorithm='corneil_habib_paul_tedder'``.
81
+
82
+ This functions assumes that ``graph`` is a object of the class ``Graph`` and
83
+ is a simple graph.
84
+
85
+ TESTS::
86
+
87
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
88
+ sage: recreate_decomposition(15, corneil_habib_paul_tedder_algorithm,
89
+ ....: 3, 4, 0.2)
90
+ sage: recreate_decomposition(10, corneil_habib_paul_tedder_algorithm,
91
+ ....: 4, 5, 0.2)
92
+ sage: recreate_decomposition(3, corneil_habib_paul_tedder_algorithm,
93
+ ....: 6, 5, 0.2)
94
+
95
+ sage: H = Graph('Hv|mmjz', format='graph6')
96
+ sage: H.relabel('abcdefghi') # counter-exemple graph from [AP2024]_
97
+ sage: H.modular_decomposition()
98
+ (SERIES,
99
+ [(PRIME, ['e', (PARALLEL, ['g', 'h']), 'b', 'c']),
100
+ (PARALLEL, [(SERIES, ['i', 'd', 'a']), 'f'])])
101
+ """
102
+ cdef CGraphBackend Gbackend = <CGraphBackend> G._backend
103
+ cdef CGraph cg = Gbackend.cg()
104
+
105
+ cdef vector[int] sigma
106
+ cdef vector[vector[int]] lex_label
107
+ cdef vector[size_t] xslice_len
108
+
109
+ # Compute the slice decomposition using the extended lexBFS algorithm
110
+ extended_lex_BFS(cg, sigma, NULL, -1, NULL, &xslice_len, &lex_label)
111
+
112
+ cdef SDData SD
113
+ SD.set_from_data(0, sigma.data(), xslice_len.data(), lex_label.data())
114
+
115
+ MD = corneil_habib_paul_tedder_inner(SD)
116
+
117
+ r = md_tree_node_to_md_tree(MD, Gbackend)
118
+ dealloc_md_tree_nodes_recursively(MD)
119
+ return r
120
+
121
+
122
+ cdef object _md_tree_node_to_md_tree_inner_rec(const md_tree_node *n,
123
+ CGraphBackend Gb):
124
+ """
125
+ Utility function for :func:`md_tree_node_to_md_tree`.
126
+ """
127
+ cdef md_tree_node *c
128
+ if deref(n).is_leaf():
129
+ return Node.create_leaf(Gb.vertex_label(deref(n).vertex))
130
+
131
+ if deref(n).is_series():
132
+ node = Node(NodeType.SERIES)
133
+ elif deref(n).is_parallel():
134
+ node = Node(NodeType.PARALLEL)
135
+ else: # is_prime
136
+ node = Node(NodeType.PRIME)
137
+ node.children.extend(_md_tree_node_to_md_tree_inner_rec(c, Gb)
138
+ for c in deref(n).children)
139
+ return node
140
+
141
+
142
+ cdef object md_tree_node_to_md_tree(const md_tree_node *n, CGraphBackend Gb):
143
+ """
144
+ This function converts a modular decomposition tree (given as a pointer to a
145
+ md_tree_node) into an object of the Python class Node (which is then
146
+ converted into the correct output by :meth:`Graph.modular_decomposition`).
147
+
148
+ The graph backend is needed to convert the int stored into the md_tree_node
149
+ into the corresponding vertex of the Python graph.
150
+
151
+ This function deals with the case of an empty tree and then delegates the
152
+ actual conversion to :func:`_md_tree_node_to_md_tree_inner_rec`.
153
+
154
+ TESTS:
155
+
156
+ Indirect doctests::
157
+
158
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
159
+ sage: corneil_habib_paul_tedder_algorithm(Graph(1))
160
+ NORMAL [0]
161
+ sage: corneil_habib_paul_tedder_algorithm(Graph(2))
162
+ PARALLEL [NORMAL [0], NORMAL [1]]
163
+ sage: corneil_habib_paul_tedder_algorithm(graphs.CompleteGraph(3))
164
+ SERIES [NORMAL [1], NORMAL [2], NORMAL [0]]
165
+ """
166
+ if n == NULL:
167
+ return Node(NodeType.EMPTY)
168
+ return _md_tree_node_to_md_tree_inner_rec(n, Gb)
169
+
170
+
171
+ ################################################################################
172
+ class NodeType(IntEnum):
173
+ """
174
+ NodeType is an enumeration class used to define the various types of nodes
175
+ in modular decomposition tree.
176
+
177
+ The various node types defined are
178
+
179
+ - ``PARALLEL`` -- indicates the node is a parallel module
180
+
181
+ - ``SERIES`` -- indicates the node is a series module
182
+
183
+ - ``PRIME`` -- indicates the node is a prime module
184
+
185
+ - ``EMPTY`` -- indicates a empty tree
186
+
187
+ - ``NORMAL`` -- indicates the node is normal containing a vertex
188
+ """
189
+ PRIME = 0
190
+ SERIES = 1
191
+ PARALLEL = 2
192
+ NORMAL = 3
193
+ EMPTY = -1
194
+
195
+ def __repr__(self) -> str:
196
+ r"""
197
+ Return a string representation of a ``NodeType`` object.
198
+
199
+ TESTS::
200
+
201
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import NodeType
202
+ sage: repr(NodeType.PARALLEL)
203
+ 'PARALLEL'
204
+ sage: str(NodeType.PRIME)
205
+ 'PRIME'
206
+ """
207
+ return self.name
208
+
209
+ __str__ = __repr__
210
+
211
+
212
+ class Node:
213
+ """
214
+ Node class stores information about the node type.
215
+
216
+ Node type can be ``PRIME``, ``SERIES``, ``PARALLEL``, ``NORMAL`` or
217
+ ``EMPTY``.
218
+
219
+ - ``node_type`` -- is of type NodeType and specifies the type of node
220
+ """
221
+ def __init__(self, node_type):
222
+ r"""
223
+ Create a node with the given node type.
224
+
225
+ EXAMPLES::
226
+
227
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
228
+ sage: n = Node(NodeType.SERIES); n.node_type
229
+ SERIES
230
+ sage: n.children
231
+ []
232
+ """
233
+ self.node_type = node_type
234
+ self.children = []
235
+
236
+ def is_prime(self):
237
+ r"""
238
+ Check whether ``self`` is a prime node.
239
+
240
+ EXAMPLES::
241
+
242
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
243
+ sage: n = Node(NodeType.PRIME)
244
+ sage: n.children.append(Node.create_leaf(1))
245
+ sage: n.children.append(Node.create_leaf(2))
246
+ sage: n.is_prime()
247
+ True
248
+ sage: (n.children[0].is_prime(), n.children[1].is_prime())
249
+ (False, False)
250
+ """
251
+ return self.node_type == NodeType.PRIME
252
+
253
+ def is_series(self):
254
+ r"""
255
+ Check whether ``self`` is a series node.
256
+
257
+ EXAMPLES::
258
+
259
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
260
+ sage: n = Node(NodeType.SERIES)
261
+ sage: n.children.append(Node.create_leaf(1))
262
+ sage: n.children.append(Node.create_leaf(2))
263
+ sage: n.is_series()
264
+ True
265
+ sage: (n.children[0].is_series(), n.children[1].is_series())
266
+ (False, False)
267
+ """
268
+ return self.node_type == NodeType.SERIES
269
+
270
+ def is_empty(self):
271
+ r"""
272
+ Check whether ``self`` is an empty node.
273
+
274
+ EXAMPLES::
275
+
276
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
277
+ sage: Node(NodeType.EMPTY).is_empty()
278
+ True
279
+ sage: Node.create_leaf(1).is_empty()
280
+ False
281
+ """
282
+ return self.node_type == NodeType.EMPTY
283
+
284
+ def is_leaf(self):
285
+ r"""
286
+ Check whether ``self`` is a leaf.
287
+
288
+ EXAMPLES::
289
+
290
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
291
+ sage: n = Node(NodeType.PRIME)
292
+ sage: n.children.append(Node.create_leaf(1))
293
+ sage: n.children.append(Node.create_leaf(2))
294
+ sage: n.is_leaf()
295
+ False
296
+ sage: all(c.is_leaf() for c in n.children)
297
+ True
298
+ """
299
+ return self.node_type == NodeType.NORMAL
300
+
301
+ def __repr__(self):
302
+ r"""
303
+ Return a string representation of the node.
304
+
305
+ EXAMPLES::
306
+
307
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
308
+ sage: n = Node(NodeType.PRIME)
309
+ sage: n.children.append(Node.create_leaf(1))
310
+ sage: n.children.append(Node.create_leaf(2))
311
+ sage: str(n)
312
+ 'PRIME [NORMAL [1], NORMAL [2]]'
313
+ """
314
+ return f"{self.node_type} {self.children}"
315
+
316
+ def __eq__(self, other):
317
+ r"""
318
+ Compare two nodes for equality.
319
+
320
+ EXAMPLES::
321
+
322
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
323
+ sage: n1 = Node(NodeType.PRIME)
324
+ sage: n2 = Node(NodeType.PRIME)
325
+ sage: n3 = Node(NodeType.SERIES)
326
+ sage: n1 == n2
327
+ True
328
+ sage: n1 == n3
329
+ False
330
+ """
331
+ return (self.node_type == other.node_type and
332
+ self.children == other.children)
333
+
334
+ @classmethod
335
+ def create_leaf(cls, v):
336
+ """
337
+ Return Node object that is a leaf corresponding to the vertex ``v``.
338
+
339
+ INPUT:
340
+
341
+ - ``vertex`` -- vertex number
342
+
343
+ OUTPUT: a node object representing the vertex with ``node_type`` set as
344
+ ``NodeType.NORMAL``
345
+
346
+ EXAMPLES::
347
+
348
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import Node
349
+ sage: node = Node.create_leaf(2)
350
+ sage: node
351
+ NORMAL [2]
352
+ """
353
+ node = cls(NodeType.NORMAL)
354
+ node.children.append(v)
355
+ return node
356
+
357
+
358
+ def print_md_tree(root):
359
+ """
360
+ Print the modular decomposition tree.
361
+
362
+ INPUT:
363
+
364
+ - ``root`` -- root of the modular decomposition tree
365
+
366
+ EXAMPLES::
367
+
368
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
369
+ sage: print_md_tree(habib_maurer_algorithm(graphs.IcosahedralGraph()))
370
+ PRIME
371
+ 3
372
+ 4
373
+ 7
374
+ 9
375
+ 11
376
+ 1
377
+ 5
378
+ 8
379
+ 0
380
+ 2
381
+ 6
382
+ 10
383
+ """
384
+
385
+ def recursive_print_md_tree(root, level):
386
+ """
387
+ Print the modular decomposition tree at root.
388
+
389
+ INPUT:
390
+
391
+ - ``root`` -- root of the modular decomposition tree
392
+
393
+ - ``level`` -- indicates the depth of root in the original modular
394
+ decomposition tree
395
+ """
396
+ if root.node_type != NodeType.NORMAL:
397
+ print("{}{}".format(level, str(root.node_type)))
398
+ for tree in root.children:
399
+ recursive_print_md_tree(tree, level + " ")
400
+ else:
401
+ print("{}{}".format(level, str(root.children[0])))
402
+
403
+ recursive_print_md_tree(root, "")
404
+
405
+
406
+ # =============================================================================
407
+ # Habib Maurer algorithm
408
+ # =============================================================================
409
+
410
+ def gamma_classes(graph):
411
+ """
412
+ Partition the edges of the graph into Gamma classes.
413
+
414
+ Two distinct edges are Gamma related if they share a vertex but are not
415
+ part of a triangle. A Gamma class of edges is a collection of edges such
416
+ that any edge in the class can be reached from any other by a chain of
417
+ Gamma related edges (that are also in the class).
418
+
419
+ The two important properties of the Gamma class
420
+
421
+ * The vertex set corresponding to a Gamma class is a module
422
+ * If the graph is not fragile (neither it or its complement is
423
+ disconnected) then there is exactly one class that visits all the
424
+ vertices of the graph, and this class consists of just the edges that
425
+ connect the maximal strong modules of that graph.
426
+
427
+ EXAMPLES:
428
+
429
+ The gamma_classes of the octahedral graph are the three 4-cycles
430
+ corresponding to the slices through the center of the octahedron::
431
+
432
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import gamma_classes
433
+ sage: g = graphs.OctahedralGraph()
434
+ sage: sorted(gamma_classes(g), key=str)
435
+ [frozenset({0, 1, 4, 5}), frozenset({0, 2, 3, 5}), frozenset({1, 2, 3, 4})]
436
+
437
+ TESTS:
438
+
439
+ Ensure that the returned vertex sets from some random graphs are modules::
440
+
441
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import test_gamma_modules
442
+ sage: test_gamma_modules(2, 10, 0.5)
443
+ """
444
+ from itertools import chain
445
+ from sage.sets.disjoint_set import DisjointSet
446
+
447
+ pieces = DisjointSet(frozenset(e) for e in graph.edge_iterator(labels=False))
448
+ for v in graph:
449
+ neighborhood = graph.subgraph(vertices=graph.neighbors(v))
450
+ for component in neighborhood.complement().connected_components(sort=False):
451
+ v1 = component[0]
452
+ e = frozenset([v1, v])
453
+ for vi in component[1:]:
454
+ ei = frozenset([vi, v])
455
+ pieces.union(e, ei)
456
+ return {frozenset(chain.from_iterable(loe)): loe for loe in pieces}
457
+
458
+
459
+ def habib_maurer_algorithm(graph, g_classes=None):
460
+ """
461
+ Compute the modular decomposition by the algorithm of Habib and Maurer.
462
+
463
+ Compute the modular decomposition of the given graph by the algorithm of
464
+ Habib and Maurer [HM1979]_ . If the graph is disconnected or its complement
465
+ is disconnected return a tree with a ``PARALLEL`` or ``SERIES`` node at the
466
+ root and children being the modular decomposition of the subgraphs induced
467
+ by the components. Otherwise, the root is ``PRIME`` and the modules are
468
+ identified by having identical neighborhoods in the gamma class that spans
469
+ the vertices of the subgraph (exactly one is guaranteed to exist). The gamma
470
+ classes only need to be computed once, as the algorithm computes the the
471
+ classes for the current root and each of the submodules. See also [BM1983]_
472
+ for an equivalent algorithm described in greater detail.
473
+
474
+ This function should not be used directly, it should be called via the
475
+ ``modular_decomposition`` method of ``Graph`` with the parameter
476
+ ``algorithm='habib_maurer'``.
477
+
478
+ This functions assumes that ``graph`` is a object of the class ``Graph``, is
479
+ a simple graph and has at least 1 vertex.
480
+
481
+ INPUT:
482
+
483
+ - ``graph`` -- the graph for which modular decomposition tree needs to be
484
+ computed
485
+
486
+ - ``g_classes`` -- dictionary (default: ``None``); a dictionary whose values
487
+ are the gamma classes of the graph, and whose keys are a frozenset of the
488
+ vertices corresponding to the class. Used internally.
489
+
490
+ OUTPUT: the modular decomposition tree of the graph
491
+
492
+ EXAMPLES:
493
+
494
+ The Icosahedral graph is Prime::
495
+
496
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
497
+ sage: print_md_tree(habib_maurer_algorithm(graphs.IcosahedralGraph()))
498
+ PRIME
499
+ 3
500
+ 4
501
+ 7
502
+ 9
503
+ 11
504
+ 1
505
+ 5
506
+ 8
507
+ 0
508
+ 2
509
+ 6
510
+ 10
511
+
512
+ The Octahedral graph is not Prime::
513
+
514
+ sage: print_md_tree(habib_maurer_algorithm(graphs.OctahedralGraph()))
515
+ SERIES
516
+ PARALLEL
517
+ 0
518
+ 5
519
+ PARALLEL
520
+ 1
521
+ 4
522
+ PARALLEL
523
+ 2
524
+ 3
525
+
526
+ Tetrahedral Graph is Series::
527
+
528
+ sage: print_md_tree(habib_maurer_algorithm(graphs.TetrahedralGraph()))
529
+ SERIES
530
+ 0
531
+ 1
532
+ 2
533
+ 3
534
+
535
+ Modular Decomposition tree containing both parallel and series modules::
536
+
537
+ sage: d = {2:[4,3,5], 1:[4,3,5], 5:[3,2,1,4], 3:[1,2,5], 4:[1,2,5]}
538
+ sage: g = Graph(d)
539
+ sage: print_md_tree(habib_maurer_algorithm(g))
540
+ SERIES
541
+ PARALLEL
542
+ 1
543
+ 2
544
+ PARALLEL
545
+ 3
546
+ 4
547
+ 5
548
+
549
+ Graph from Marc Tedder implementation of modular decomposition::
550
+
551
+ sage: d = {1:[5,4,3,24,6,7,8,9,2,10,11,12,13,14,16,17], 2:[1],
552
+ ....: 3:[24,9,1], 4:[5,24,9,1], 5:[4,24,9,1], 6:[7,8,9,1],
553
+ ....: 7:[6,8,9,1], 8:[6,7,9,1], 9:[6,7,8,5,4,3,1], 10:[1],
554
+ ....: 11:[12,1], 12:[11,1], 13:[14,16,17,1], 14:[13,17,1],
555
+ ....: 16:[13,17,1], 17:[13,14,16,18,1], 18:[17], 24:[5,4,3,1]}
556
+ sage: g = Graph(d)
557
+ sage: test_modular_decomposition(habib_maurer_algorithm(g), g)
558
+ True
559
+
560
+ Tetrahedral Graph is Series::
561
+
562
+ sage: print_md_tree(habib_maurer_algorithm(graphs.TetrahedralGraph()))
563
+ SERIES
564
+ 0
565
+ 1
566
+ 2
567
+ 3
568
+
569
+ Modular Decomposition tree containing both parallel and series modules::
570
+
571
+ sage: d = {2:[4,3,5], 1:[4,3,5], 5:[3,2,1,4], 3:[1,2,5], 4:[1,2,5]}
572
+ sage: g = Graph(d)
573
+ sage: print_md_tree(habib_maurer_algorithm(g))
574
+ SERIES
575
+ PARALLEL
576
+ 1
577
+ 2
578
+ PARALLEL
579
+ 3
580
+ 4
581
+ 5
582
+
583
+ TESTS:
584
+
585
+ Ensure that a random graph and an isomorphic graph have identical modular
586
+ decompositions. ::
587
+
588
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import permute_decomposition
589
+ sage: permute_decomposition(2, habib_maurer_algorithm, 20, 0.5) # needs sage.groups
590
+ """
591
+ if graph.order() == 1:
592
+ root = Node.create_leaf(next(graph.vertex_iterator()))
593
+ return root
594
+
595
+ elif not graph.is_connected():
596
+ root = Node(NodeType.PARALLEL)
597
+ root.children = [habib_maurer_algorithm(graph.subgraph(vertices=sg), g_classes)
598
+ for sg in graph.connected_components(sort=False)]
599
+ return root
600
+
601
+ g_comp = graph.complement()
602
+ if g_comp.is_connected():
603
+ from collections import defaultdict
604
+ root = Node(NodeType.PRIME)
605
+ if g_classes is None:
606
+ g_classes = gamma_classes(graph)
607
+ vertex_set = frozenset(graph)
608
+ edges = [tuple(e) for e in g_classes[vertex_set]]
609
+ sub = graph.subgraph(edges=edges)
610
+ d = defaultdict(list)
611
+ for v in sub:
612
+ for v1 in sub.neighbor_iterator(v):
613
+ d[v1].append(v)
614
+ d1 = defaultdict(list)
615
+ for k, v in d.items():
616
+ d1[frozenset(v)].append(k)
617
+ root.children = [habib_maurer_algorithm(graph.subgraph(vertices=sg), g_classes)
618
+ for sg in d1.values()]
619
+ return root
620
+
621
+ root = Node(NodeType.SERIES)
622
+ root.children = [habib_maurer_algorithm(graph.subgraph(vertices=sg), g_classes)
623
+ for sg in g_comp.connected_components(sort=False)]
624
+ return root
625
+
626
+
627
+ ################################################################################
628
+ # Exported modular_decomposition function #
629
+ ################################################################################
630
+ def modular_decomposition(G, algorithm=None):
631
+ r"""
632
+ Return the modular decomposition of the current graph.
633
+
634
+ This function should not be used directly, it should be called via the
635
+ ``modular_decomposition`` method of ``Graph``.
636
+
637
+ INPUT:
638
+
639
+ - ``G`` -- graph whose modular decomposition tree is to be computed
640
+
641
+ - ``algorithm`` -- string (default: ``None``); the algorithm to use among:
642
+
643
+ - ``None`` or ``'corneil_habib_paul_tedder'`` -- will use the
644
+ Corneil-Habib-Paul-Tedder algorithm from [TCHP2008]_, its complexity
645
+ is linear in the number of vertices and edges.
646
+
647
+ - ``'habib_maurer'`` -- will use the Habib-Maurer algorithm from
648
+ [HM1979]_, its complexity is cubic in the number of vertices.
649
+
650
+ OUTPUT: The modular decomposition tree, as an object of type ``Node``.
651
+
652
+ TESTS::
653
+
654
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
655
+
656
+ sage: modular_decomposition(Graph())
657
+ EMPTY []
658
+
659
+ sage: modular_decomposition(Graph(1))
660
+ NORMAL [0]
661
+
662
+ sage: check_algos_are_equivalent(5, # needs networkx
663
+ ....: lambda : graphs.RandomProperIntervalGraph(100))
664
+
665
+ sage: check_algos_are_equivalent(5, lambda : graphs.RandomGNM(75, 1000)) # needs networkx
666
+
667
+ sage: modular_decomposition(DiGraph())
668
+ Traceback (most recent call last):
669
+ ...
670
+ TypeError: the input must be an undirected Sage graph
671
+
672
+ sage: modular_decomposition(Graph(5, loops=True))
673
+ Traceback (most recent call last):
674
+ ...
675
+ ValueError: This method is not known to work on graphs with loops...
676
+
677
+ sage: modular_decomposition(Graph(5, multiedges=True))
678
+ Traceback (most recent call last):
679
+ ...
680
+ ValueError: This method is not known to work on graphs with multiedges...
681
+
682
+ sage: modular_decomposition(Graph(), algorithm='silly walk')
683
+ Traceback (most recent call last):
684
+ ...
685
+ ValueError: unknown algorithm "silly walk"
686
+ """
687
+ from sage.graphs.graph import Graph
688
+ if not isinstance(G, Graph):
689
+ raise TypeError("the input must be an undirected Sage graph")
690
+ G._scream_if_not_simple()
691
+
692
+ if algorithm is None:
693
+ algorithm = "corneil_habib_paul_tedder"
694
+
695
+ if algorithm not in ("habib_maurer", "corneil_habib_paul_tedder"):
696
+ raise ValueError(f'unknown algorithm "{algorithm}"')
697
+
698
+ if not G.order():
699
+ return Node(NodeType.EMPTY)
700
+ if G.order() == 1:
701
+ D = Node(NodeType.NORMAL)
702
+ D.children.append(next(G.vertex_iterator()))
703
+ return D
704
+ if algorithm == "habib_maurer":
705
+ return habib_maurer_algorithm(G)
706
+ return corneil_habib_paul_tedder_algorithm(G)
707
+
708
+
709
+ # ============================================================================
710
+ # Below functions are implemented to test the modular decomposition tree
711
+ # ============================================================================
712
+ # Function implemented for testing
713
+ def test_modular_decomposition(tree_root, graph):
714
+ """
715
+ Test the input modular decomposition tree using recursion.
716
+
717
+ INPUT:
718
+
719
+ - ``tree_root`` -- root of the modular decomposition tree to be tested
720
+
721
+ - ``graph`` -- graph whose modular decomposition tree needs to be tested
722
+
723
+ OUTPUT: ``True`` if input tree is a modular decomposition else ``False``
724
+
725
+ EXAMPLES::
726
+
727
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
728
+ sage: g = graphs.HexahedralGraph()
729
+ sage: test_modular_decomposition(modular_decomposition(g), g)
730
+ True
731
+ """
732
+ if tree_root.node_type != NodeType.NORMAL:
733
+ for module in tree_root.children:
734
+ if not test_module(module, graph):
735
+ # test whether modules pass the defining
736
+ # characteristics of modules
737
+ return False
738
+ if not test_modular_decomposition(module,
739
+ graph.subgraph(get_vertices(module))):
740
+ # recursively test the modular decomposition subtrees
741
+ return False
742
+
743
+ if not test_maximal_modules(tree_root, graph):
744
+ # test whether the mdoules are maximal in nature
745
+ return False
746
+
747
+ return True
748
+
749
+
750
+ # Function implemented for testing
751
+ def test_maximal_modules(tree_root, graph):
752
+ r"""
753
+ Test the maximal nature of modules in a modular decomposition tree.
754
+
755
+ Suppose the module `M = [M_1, M_2, \cdots, n]` is the input modular
756
+ decomposition tree. Algorithm forms pairs like `(M_1, M_2), (M_1, M_3),
757
+ \cdots, (M_1, M_n)`; `(M_2, M_3), (M_2, M_4), \cdots, (M_2, M_n)`; `\cdots`
758
+ and so on and tries to form a module using the pair. If the module formed
759
+ has same type as `M` and is of type ``SERIES`` or ``PARALLEL`` then the
760
+ formed module is not considered maximal. Otherwise it is considered maximal
761
+ and `M` is not a modular decomposition tree.
762
+
763
+ INPUT:
764
+
765
+ - ``tree_root`` -- modular decomposition tree whose modules are tested for
766
+ maximal nature
767
+
768
+ - ``graph`` -- graph whose modular decomposition tree is tested
769
+
770
+ OUTPUT:
771
+
772
+ ``True`` if all modules at first level in the modular decomposition tree
773
+ are maximal in nature
774
+
775
+ EXAMPLES::
776
+
777
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
778
+ sage: g = graphs.HexahedralGraph()
779
+ sage: test_maximal_modules(modular_decomposition(g), g)
780
+ True
781
+ """
782
+ if tree_root.node_type != NodeType.NORMAL:
783
+ for index, module in enumerate(tree_root.children):
784
+ for other_index in range(index + 1, len(tree_root.children)):
785
+
786
+ # compute the module formed using modules at index and
787
+ # other_index
788
+ module_formed = form_module(index, other_index,
789
+ tree_root, graph)
790
+
791
+ if module_formed[0]:
792
+ # Module formed and the parent of the formed module
793
+ # should not both be of type SERIES or PARALLEL
794
+ mod_type = get_module_type(graph.subgraph(module_formed[1]))
795
+ if (mod_type == tree_root.node_type and
796
+ (tree_root.node_type == NodeType.PARALLEL or
797
+ tree_root.node_type == NodeType.SERIES)):
798
+ continue
799
+ return False
800
+ return True
801
+
802
+
803
+ def get_vertices(component_root):
804
+ """
805
+ Compute the list of vertices in the (co)component.
806
+
807
+ INPUT:
808
+
809
+ - ``component_root`` -- root of the (co)component whose vertices need to be
810
+ returned as a list
811
+
812
+ OUTPUT:
813
+
814
+ list of vertices in the (co)component
815
+
816
+ EXAMPLES::
817
+
818
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
819
+ sage: forest = Node(NodeType.PRIME)
820
+ sage: forest.children = [Node.create_leaf(2), Node.create_leaf(0),
821
+ ....: Node.create_leaf(3), Node.create_leaf(1)]
822
+ sage: series_node = Node(NodeType.SERIES)
823
+ sage: series_node.children = [Node.create_leaf(4),
824
+ ....: Node.create_leaf(5)]
825
+ sage: parallel_node = Node(NodeType.PARALLEL)
826
+ sage: parallel_node.children = [Node.create_leaf(6),
827
+ ....: Node.create_leaf(7)]
828
+ sage: forest.children.insert(1, series_node)
829
+ sage: forest.children.insert(3, parallel_node)
830
+ sage: get_vertices(forest)
831
+ [2, 4, 5, 0, 6, 7, 3, 1]
832
+ """
833
+ vertices = []
834
+
835
+ # inner recursive function to recurse over the elements in the
836
+ # ``component``
837
+ def recurse_component(node, vertices):
838
+ if node.node_type == NodeType.NORMAL:
839
+ vertices.append(node.children[0])
840
+ return
841
+ for child in node.children:
842
+ recurse_component(child, vertices)
843
+
844
+ recurse_component(component_root, vertices)
845
+ return vertices
846
+
847
+
848
+ # Function implemented for testing
849
+ def get_module_type(graph):
850
+ """
851
+ Return the module type of the root of the modular decomposition tree of
852
+ ``graph``.
853
+
854
+ INPUT:
855
+
856
+ - ``graph`` -- input sage graph
857
+
858
+ OUTPUT:
859
+
860
+ ``PRIME`` if graph is PRIME, ``PARALLEL`` if graph is PARALLEL and
861
+ ``SERIES`` if graph is of type SERIES
862
+
863
+ EXAMPLES::
864
+
865
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import get_module_type
866
+ sage: g = graphs.HexahedralGraph()
867
+ sage: get_module_type(g)
868
+ PRIME
869
+ """
870
+ if not graph.is_connected():
871
+ return NodeType.PARALLEL
872
+ elif graph.complement().is_connected():
873
+ return NodeType.PRIME
874
+ return NodeType.SERIES
875
+
876
+
877
+ # Function implemented for testing
878
+ def form_module(index, other_index, tree_root, graph):
879
+ r"""
880
+ Forms a module out of the modules in the module pair.
881
+
882
+ Let `M_1` and `M_2` be the input modules. Let `V` be the set of vertices in
883
+ these modules. Suppose `x` is a neighbor of subset of the vertices in `V`
884
+ but not all the vertices and `x` does not belong to `V`. Then the set of
885
+ modules also include the module which contains `x`. This process is repeated
886
+ until a module is formed and the formed module if subset of `V` is returned.
887
+
888
+ INPUT:
889
+
890
+ - ``index`` -- first module in the module pair
891
+
892
+ - ``other_index`` -- second module in the module pair
893
+
894
+ - ``tree_root`` -- modular decomposition tree which contains the modules
895
+ in the module pair
896
+
897
+ - ``graph`` -- graph whose modular decomposition tree is created
898
+
899
+ OUTPUT:
900
+
901
+ ``[module_formed, vertices]`` where ``module_formed`` is ``True`` if
902
+ module is formed else ``False`` and ``vertices`` is a list of vertices
903
+ included in the formed module
904
+
905
+ EXAMPLES::
906
+
907
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
908
+ sage: g = graphs.HexahedralGraph()
909
+ sage: tree_root = modular_decomposition(g)
910
+ sage: form_module(0, 2, tree_root, g)
911
+ [False, {0, 1, 2, 3, 4, 5, 6, 7}]
912
+ """
913
+ vertices = set(get_vertices(tree_root.children[index]))
914
+ vertices.update(get_vertices(tree_root.children[other_index]))
915
+
916
+ # stores all neighbors which are common for all vertices in V
917
+ common_neighbors = set()
918
+
919
+ # stores all neighbors of vertices in V which are outside V
920
+ all_neighbors = set()
921
+
922
+ while True:
923
+ # remove vertices from all_neighbors and common_neighbors
924
+ all_neighbors.difference_update(vertices)
925
+ common_neighbors.difference_update(vertices)
926
+
927
+ for v in vertices:
928
+ # stores the neighbors of v which are outside the set of vertices
929
+ neighbor_list = set(graph.neighbors(v))
930
+ neighbor_list.difference_update(vertices)
931
+
932
+ # update all_neighbors and common_neighbors using the
933
+ # neighbor_list
934
+ all_neighbors.update(neighbor_list)
935
+ common_neighbors.intersection_update(neighbor_list)
936
+
937
+ if all_neighbors == common_neighbors: # indicates a module is formed
938
+
939
+ # module formed covers the entire graph
940
+ if len(vertices) == graph.order():
941
+ return [False, vertices]
942
+
943
+ return [True, vertices]
944
+
945
+ # add modules containing uncommon neighbors into the formed module
946
+ for v in (all_neighbors - common_neighbors):
947
+ for index in range(len(tree_root.children)):
948
+ if v in get_vertices(tree_root.children[index]):
949
+ vertices.update(get_vertices(tree_root.children[index]))
950
+ break
951
+
952
+
953
+ # Function implemented for testing
954
+ def test_module(module, graph):
955
+ """
956
+ Test whether input module is actually a module.
957
+
958
+ INPUT:
959
+
960
+ - ``module`` -- module which needs to be tested
961
+
962
+ - ``graph`` -- input sage graph which contains the module
963
+
964
+ OUTPUT: ``True`` if input module is a module by definition else ``False``
965
+
966
+ EXAMPLES::
967
+
968
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
969
+ sage: g = graphs.HexahedralGraph()
970
+ sage: tree_root = modular_decomposition(g)
971
+ sage: test_module(tree_root, g)
972
+ True
973
+ sage: test_module(tree_root.children[0], g)
974
+ True
975
+ """
976
+ # A single vertex is a module
977
+ if module.node_type == NodeType.NORMAL:
978
+ return True
979
+
980
+ # vertices contained in module
981
+ vertices_in_module = get_vertices(module)
982
+
983
+ # vertices outside module
984
+ vertices_outside = list(set(graph.vertices(sort=False)) - set(vertices_in_module))
985
+
986
+ # Nested module with only one child
987
+ if module.node_type != NodeType.NORMAL and len(module.children) == 1:
988
+ return False
989
+
990
+ # If children of SERIES module are all SERIES modules
991
+ if module.node_type == NodeType.SERIES:
992
+ if children_node_type(module, NodeType.SERIES):
993
+ return False
994
+
995
+ # If children of PARALLEL module are all PARALLEL modules
996
+ if module.node_type == NodeType.PARALLEL:
997
+ if children_node_type(module, NodeType.PARALLEL):
998
+ return False
999
+
1000
+ # check the module by definition. Vertices in a module should all either
1001
+ # be connected or disconnected to any vertex outside module
1002
+ for v in vertices_outside:
1003
+ if not either_connected_or_not_connected(v, vertices_in_module, graph):
1004
+ return False
1005
+ return True
1006
+
1007
+
1008
+ # Function implemented for testing
1009
+ def children_node_type(module, node_type):
1010
+ """
1011
+ Check whether the node type of the children of ``module`` is ``node_type``.
1012
+
1013
+ INPUT:
1014
+
1015
+ - ``module`` -- module which is tested
1016
+
1017
+ - ``node_type`` -- input node_type
1018
+
1019
+ EXAMPLES::
1020
+
1021
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
1022
+ sage: g = graphs.OctahedralGraph()
1023
+ sage: tree_root = modular_decomposition(g)
1024
+ sage: print_md_tree(tree_root)
1025
+ SERIES
1026
+ PARALLEL
1027
+ 2
1028
+ 3
1029
+ PARALLEL
1030
+ 1
1031
+ 4
1032
+ PARALLEL
1033
+ 0
1034
+ 5
1035
+ sage: children_node_type(tree_root, NodeType.SERIES)
1036
+ False
1037
+ sage: children_node_type(tree_root, NodeType.PARALLEL)
1038
+ True
1039
+ """
1040
+ return all(node.node_type == node_type for node in module.children)
1041
+
1042
+
1043
+ # Function implemented for testing
1044
+ def either_connected_or_not_connected(v, vertices_in_module, graph):
1045
+ """
1046
+ Check whether ``v`` is connected or disconnected to all vertices in the
1047
+ module.
1048
+
1049
+ INPUT:
1050
+
1051
+ - ``v`` -- vertex tested
1052
+
1053
+ - ``vertices_in_module`` -- list containing vertices in the module
1054
+
1055
+ - ``graph`` -- graph to which the vertices belong
1056
+
1057
+ EXAMPLES::
1058
+
1059
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
1060
+ sage: g = graphs.OctahedralGraph()
1061
+ sage: print_md_tree(modular_decomposition(g))
1062
+ SERIES
1063
+ PARALLEL
1064
+ 2
1065
+ 3
1066
+ PARALLEL
1067
+ 1
1068
+ 4
1069
+ PARALLEL
1070
+ 0
1071
+ 5
1072
+ sage: either_connected_or_not_connected(2, [1, 4], g)
1073
+ True
1074
+ sage: either_connected_or_not_connected(2, [3, 4], g)
1075
+ False
1076
+ """
1077
+ # marks whether vertex v is connected to first vertex in the module
1078
+ connected = graph.has_edge(vertices_in_module[0], v)
1079
+
1080
+ # if connected is True then all vertices in module should be connected to
1081
+ # v else all should be disconnected
1082
+ return all(graph.has_edge(u, v) == connected for u in vertices_in_module)
1083
+
1084
+
1085
+ def tree_to_nested_tuple(root):
1086
+ r"""
1087
+ Convert a modular decomposition tree to a nested tuple.
1088
+
1089
+ INPUT:
1090
+
1091
+ - ``root`` -- the root of the modular decomposition tree
1092
+
1093
+ OUTPUT:
1094
+
1095
+ A tuple whose first element is the type of the root of the tree and whose
1096
+ subsequent nodes are either vertex labels in the case of leaves or tuples
1097
+ representing the child subtrees.
1098
+
1099
+ EXAMPLES::
1100
+
1101
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
1102
+ sage: g = graphs.OctahedralGraph()
1103
+ sage: tree_to_nested_tuple(modular_decomposition(g))
1104
+ (SERIES, [(PARALLEL, [2, 3]), (PARALLEL, [1, 4]), (PARALLEL, [0, 5])])
1105
+ """
1106
+ if root.node_type == NodeType.NORMAL:
1107
+ return root.children[0]
1108
+ else:
1109
+ return (root.node_type, [tree_to_nested_tuple(x) for x in root.children])
1110
+
1111
+
1112
+ def nested_tuple_to_tree(nest):
1113
+ r"""
1114
+ Turn a tuple representing the modular decomposition into a tree.
1115
+
1116
+ INPUT:
1117
+
1118
+ - ``nest`` -- a nested tuple of the form returned by
1119
+ :meth:`tree_to_nested_tuple`
1120
+
1121
+ OUTPUT: the root node of a modular decomposition tree
1122
+
1123
+ EXAMPLES::
1124
+
1125
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
1126
+ sage: tree = (NodeType.SERIES, 1, 2, (NodeType.PARALLEL, 3, 4))
1127
+ sage: print_md_tree(nested_tuple_to_tree(tree))
1128
+ SERIES
1129
+ 1
1130
+ 2
1131
+ PARALLEL
1132
+ 3
1133
+ 4
1134
+ """
1135
+ if not isinstance(nest, tuple):
1136
+ return Node.create_leaf(nest)
1137
+
1138
+ root = Node(nest[0])
1139
+ root.children = [nested_tuple_to_tree(n) for n in nest[1:]]
1140
+ return root
1141
+
1142
+
1143
+ def equivalent_trees(root1, root2):
1144
+ r"""
1145
+ Check that two modular decomposition trees are the same.
1146
+
1147
+ Verify that the structure of the trees is the same. Two leaves are
1148
+ equivalent if they represent the same vertex, two internal nodes are
1149
+ equivalent if they have the same nodes type and the same number of children
1150
+ and there is a matching between the children such that each pair of
1151
+ children is a pair of equivalent subtrees.
1152
+
1153
+ EXAMPLES::
1154
+
1155
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
1156
+ sage: t1 = nested_tuple_to_tree((NodeType.SERIES, 1, 2,
1157
+ ....: (NodeType.PARALLEL, 3, 4)))
1158
+ sage: t2 = nested_tuple_to_tree((NodeType.SERIES,
1159
+ ....: (NodeType.PARALLEL, 4, 3), 2, 1))
1160
+ sage: equivalent_trees(t1, t2)
1161
+ True
1162
+ """
1163
+ # internal definition
1164
+ def node_id(root):
1165
+ return (root.node_type, frozenset(get_vertices(root)))
1166
+
1167
+ if root1.node_type != root2.node_type:
1168
+ return False
1169
+
1170
+ if len(root1.children) != len(root2.children):
1171
+ return False
1172
+
1173
+ if root1.node_type == NodeType.NORMAL:
1174
+ return root1.children[0] == root2.children[0]
1175
+
1176
+ child_map = {}
1177
+ for node in root2.children:
1178
+ child_map[node_id(node)] = node
1179
+
1180
+ for node in root1.children:
1181
+ id = node_id(node)
1182
+ if id not in child_map:
1183
+ return False
1184
+ if not equivalent_trees(node, child_map[id]):
1185
+ return False
1186
+
1187
+ return True
1188
+
1189
+
1190
+ def relabel_tree(root, perm):
1191
+ r"""
1192
+ Relabel the leaves of a tree according to a dictionary.
1193
+
1194
+ INPUT:
1195
+
1196
+ - ``root`` -- the root of the tree
1197
+
1198
+ - ``perm`` -- a function, dictionary, list, permutation, or ``None``
1199
+ representing the relabeling. See
1200
+ :meth:`~sage.graphs.generic_graph.GenericGraph.relabel` for description of
1201
+ the permutation input.
1202
+
1203
+ EXAMPLES::
1204
+
1205
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
1206
+ sage: tuple_tree = (NodeType.SERIES, 1, 2, (NodeType.PARALLEL, 3, 4))
1207
+ sage: tree = nested_tuple_to_tree(tuple_tree)
1208
+ sage: print_md_tree(relabel_tree(tree, (4,3,2,1)))
1209
+ SERIES
1210
+ 4
1211
+ 3
1212
+ PARALLEL
1213
+ 2
1214
+ 1
1215
+ """
1216
+ # If perm is not a dictionary, we build one !
1217
+ if perm is None:
1218
+
1219
+ # vertices() returns a sorted list:
1220
+ # this guarantees consistent relabeling
1221
+ perm = {v: i for i, v in enumerate(get_vertices(root))}
1222
+
1223
+ elif isinstance(perm, dict):
1224
+ from copy import copy
1225
+ # If all vertices do not have a new label, the code will touch the
1226
+ # dictionary. Let us keep the one we received from the user clean !
1227
+ perm = copy(perm)
1228
+
1229
+ elif isinstance(perm, (list, tuple)):
1230
+ perm = dict(zip(sorted(get_vertices(root)), perm))
1231
+
1232
+ elif isinstance(perm, PermutationGroupElement):
1233
+ n = len(get_vertices(root))
1234
+ ddict = {}
1235
+ for i in range(1, n):
1236
+ ddict[i] = perm(i) % n
1237
+ if n > 0:
1238
+ ddict[0] = perm(n) % n
1239
+ perm = ddict
1240
+
1241
+ elif callable(perm):
1242
+ perm = {i: perm(i) for i in get_vertices(root)}
1243
+
1244
+ else:
1245
+ raise TypeError("type of perm is not supported for relabeling")
1246
+
1247
+ if root.node_type == NodeType.NORMAL:
1248
+ return Node.create_leaf(perm[root.children[0]])
1249
+ else:
1250
+ new_root = Node(root.node_type)
1251
+ new_root.children = [relabel_tree(child, perm) for child in root.children]
1252
+ return new_root
1253
+
1254
+
1255
+ # =============================================================================
1256
+ # Random tests
1257
+ # =============================================================================
1258
+
1259
+ @random_testing
1260
+ def test_gamma_modules(trials, vertices, prob, verbose=False):
1261
+ r"""
1262
+ Verify that the vertices of each gamma class of a random graph are modules
1263
+ of that graph.
1264
+
1265
+ INPUT:
1266
+
1267
+ - ``trials`` -- the number of trials to run
1268
+
1269
+ - ``vertices`` -- the size of the graph to use
1270
+
1271
+ - ``prob`` -- the probability that any given edge is in the graph
1272
+ See :meth:`~sage.graphs.generators.random.RandomGNP` for more details
1273
+
1274
+ - ``verbose`` -- print information on each trial
1275
+
1276
+ EXAMPLES::
1277
+
1278
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
1279
+ sage: test_gamma_modules(3, 7, 0.5)
1280
+ """
1281
+ from sage.graphs.generators.random import RandomGNP
1282
+ for _ in range(trials):
1283
+ g = RandomGNP(vertices, prob)
1284
+ if verbose:
1285
+ print(g.graph6_string())
1286
+ g_classes = gamma_classes(g)
1287
+ for module in g_classes.keys():
1288
+ m_list = list(module)
1289
+ for v in g:
1290
+ if v not in module:
1291
+ assert either_connected_or_not_connected(v, m_list, g)
1292
+ if verbose:
1293
+ print("Passes!")
1294
+
1295
+
1296
+ @random_testing
1297
+ def permute_decomposition(trials, algorithm, vertices, prob, verbose=False):
1298
+ r"""
1299
+ Check that a graph and its permuted relabeling have the same modular
1300
+ decomposition.
1301
+
1302
+ We generate a ``trials`` random graphs and then generate an isomorphic graph
1303
+ by relabeling the original graph. We then verify
1304
+
1305
+ EXAMPLES::
1306
+
1307
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
1308
+ sage: permute_decomposition(30, habib_maurer_algorithm, 10, 0.5)
1309
+ """
1310
+ from sage.graphs.generators.random import RandomGNP
1311
+ from sage.combinat.permutation import Permutations
1312
+ for _ in range(trials):
1313
+ g1 = RandomGNP(vertices, prob)
1314
+ random_perm = Permutations(list(g1)).random_element()
1315
+ g2 = g1.relabel(perm=random_perm, inplace=False)
1316
+ if verbose:
1317
+ print(g1.graph6_string())
1318
+ print(random_perm)
1319
+ t1 = algorithm(g1)
1320
+ t2 = algorithm(g2)
1321
+ assert test_modular_decomposition(t1, g1)
1322
+ assert test_modular_decomposition(t2, g2)
1323
+ t1p = relabel_tree(t1, random_perm)
1324
+ assert equivalent_trees(t1p, t2)
1325
+ if verbose:
1326
+ print("Passes!")
1327
+
1328
+
1329
+ def random_md_tree(max_depth, max_fan_out, leaf_probability):
1330
+ r"""
1331
+ Create a random MD tree.
1332
+
1333
+ INPUT:
1334
+
1335
+ - ``max_depth`` -- the maximum depth of the tree
1336
+
1337
+ - ``max_fan_out`` -- the maximum number of children a node can have
1338
+ (must be >=4 as a prime node must have at least 4 vertices)
1339
+
1340
+ - ``leaf_probability`` -- the probability that a subtree is a leaf
1341
+
1342
+ EXAMPLES::
1343
+
1344
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
1345
+ sage: set_random_seed(0)
1346
+ sage: tree_to_nested_tuple(random_md_tree(2, 5, 0.5))
1347
+ (PRIME, [0, 1, (PRIME, [2, 3, 4, 5, 6]), 7, (PARALLEL, [8, 9, 10])])
1348
+ """
1349
+
1350
+ from sage.misc.prandom import choice, randint, random
1351
+
1352
+ if max_fan_out < 4:
1353
+ raise ValueError("max_fan_out must be at least 4")
1354
+
1355
+ # Internal function
1356
+ def rand_md_tree(max_depth, parent_type):
1357
+ r"""
1358
+ Create the subtrees of a node.
1359
+
1360
+ A child of a node cannot have the same node type as its parent if its
1361
+ parent's node type is either PARALLEL or SERIES. Also its ``max_depth``
1362
+ is one less than its parent's.
1363
+ """
1364
+ if random() < leaf_probability or max_depth == 1:
1365
+ root = Node.create_leaf(current_leaf[0])
1366
+ current_leaf[0] += 1
1367
+ return root
1368
+ if parent_type == NodeType.PRIME:
1369
+ node_type = choice([NodeType.PRIME, NodeType.SERIES, NodeType.PARALLEL])
1370
+ elif parent_type == NodeType.SERIES:
1371
+ node_type = choice([NodeType.PRIME, NodeType.PARALLEL])
1372
+ else:
1373
+ node_type = choice([NodeType.PRIME, NodeType.SERIES])
1374
+ if node_type == NodeType.PRIME:
1375
+ num_children = randint(4, max_fan_out)
1376
+ else:
1377
+ num_children = randint(2, max_fan_out)
1378
+ root = Node(node_type)
1379
+ root.children = [rand_md_tree(max_depth - 1, node_type)
1380
+ for _ in range(num_children)]
1381
+ return root
1382
+
1383
+ # a hack around python2's lack of 'nonlocal'
1384
+ current_leaf = [0]
1385
+ node_type = choice([NodeType.PRIME, NodeType.SERIES, NodeType.PARALLEL])
1386
+ num_children = randint(4, max_fan_out)
1387
+ root = Node(node_type)
1388
+ root.children = [rand_md_tree(max_depth, node_type)
1389
+ for _ in range(num_children)]
1390
+ return root
1391
+
1392
+
1393
+ def md_tree_to_graph(root, prime_node_generator=None):
1394
+ r"""
1395
+ Create a graph having the given MD tree.
1396
+
1397
+ For the prime nodes, the parameter ``prime_node_generator`` is called with
1398
+ the number of vertices as the only argument. If it is ``None``, the path
1399
+ graph is used (it is prime when the length is 4 or more).
1400
+
1401
+ EXAMPLES::
1402
+
1403
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
1404
+ sage: from sage.graphs.graph_generators import graphs
1405
+ sage: tup1 = (NodeType.PRIME, 1, (NodeType.SERIES, 2, 3),
1406
+ ....: (NodeType.PARALLEL, 4, 5), 6)
1407
+ sage: tree1 = nested_tuple_to_tree(tup1)
1408
+ sage: g1 = md_tree_to_graph(tree1)
1409
+ sage: g2 = Graph({1: [2, 3], 2: [1, 3, 4, 5], 3: [1, 2, 4, 5],
1410
+ ....: 4: [2, 3, 6], 5: [2, 3, 6], 6: [4, 5]})
1411
+ sage: g1.is_isomorphic(g2)
1412
+ True
1413
+
1414
+ sage: G = md_tree_to_graph(Node(NodeType.EMPTY))
1415
+ sage: G.is_isomorphic(Graph())
1416
+ True
1417
+
1418
+ sage: tree = Node(NodeType.SERIES)
1419
+ sage: tree.children.extend(Node.create_leaf(i) for i in range(5))
1420
+ sage: G = md_tree_to_graph(tree)
1421
+ sage: G.is_isomorphic(graphs.CompleteGraph(5))
1422
+ True
1423
+
1424
+ sage: tree = Node(NodeType.PRIME)
1425
+ sage: tree.children.extend(Node.create_leaf(i) for i in range(5))
1426
+ sage: png = lambda n: (graphs.PathGraph if n == 4 else graphs.CycleGraph)(n)
1427
+ sage: G = md_tree_to_graph(tree, prime_node_generator=png)
1428
+ sage: G.is_isomorphic(graphs.CycleGraph(5))
1429
+ True
1430
+ """
1431
+ from itertools import product, combinations
1432
+ from sage.graphs.graph import Graph
1433
+
1434
+ if prime_node_generator is None:
1435
+ from sage.graphs.graph_generators import graphs
1436
+ prime_node_generator = graphs.PathGraph
1437
+
1438
+ def tree_to_vertices_and_edges(root):
1439
+ r"""
1440
+ Give the list of vertices and edges of the graph having the given md tree.
1441
+ """
1442
+ if root.is_leaf():
1443
+ return (root.children, [])
1444
+ children_ve = [tree_to_vertices_and_edges(child) for child in root.children]
1445
+ vertices = [v for vs, es in children_ve for v in vs]
1446
+ edges = [e for vs, es in children_ve for e in es]
1447
+ vertex_lists = [vs for vs, es in children_ve]
1448
+ if root.is_prime():
1449
+ G = prime_node_generator(len(vertex_lists))
1450
+ G.relabel(range(len(vertex_lists)))
1451
+ for i1, i2 in G.edge_iterator(labels=False):
1452
+ edges.extend(product(vertex_lists[i1], vertex_lists[i2]))
1453
+ elif root.is_series():
1454
+ for vs1, vs2 in combinations(vertex_lists, 2):
1455
+ edges.extend(product(vs1, vs2))
1456
+ # else: no edge to be created for PARALLEL nodes
1457
+ return (vertices, edges)
1458
+
1459
+ if root.is_empty():
1460
+ return Graph()
1461
+ vs, es = tree_to_vertices_and_edges(root)
1462
+ return Graph([vs, es], format='vertices_and_edges')
1463
+
1464
+
1465
+ @random_testing
1466
+ def recreate_decomposition(trials, algorithm, max_depth, max_fan_out,
1467
+ leaf_probability, verbose=False):
1468
+ r"""
1469
+ Verify that we can recreate a random MD tree.
1470
+
1471
+ We create a random MD tree, then create a graph having that decomposition,
1472
+ then find a modular decomposition for that graph, and verify that the two
1473
+ modular decomposition trees are equivalent.
1474
+
1475
+ EXAMPLES::
1476
+
1477
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
1478
+ sage: recreate_decomposition(3, habib_maurer_algorithm, 4, 6, 0.5,
1479
+ ....: verbose=False)
1480
+ """
1481
+ for _ in range(trials):
1482
+ rand_tree = random_md_tree(max_depth, max_fan_out, leaf_probability)
1483
+ if verbose:
1484
+ print_md_tree(rand_tree)
1485
+ graph = md_tree_to_graph(rand_tree)
1486
+ if verbose:
1487
+ print(graph.graph6_string())
1488
+ print(graph.to_dictionary())
1489
+ reconstruction = algorithm(graph)
1490
+ if verbose:
1491
+ print_md_tree(reconstruction)
1492
+ assert equivalent_trees(rand_tree, reconstruction)
1493
+ if verbose:
1494
+ print("Passes!")
1495
+
1496
+
1497
+ @random_testing
1498
+ def check_algos_are_equivalent(trials, graph_gen, verbose=False):
1499
+ r"""
1500
+ Verify that both algorithms compute the same tree (up to equivalence) for
1501
+ random graphs.
1502
+
1503
+ INPUT:
1504
+
1505
+ - ``trials`` -- integer; the number of tests the function will run.
1506
+
1507
+ - ``graph_gen`` -- function; a function that can be called without argument
1508
+ and returns a random graph.
1509
+
1510
+ - ``verbose`` -- boolean (defaul: ``False``); enable printing debug
1511
+ information.
1512
+
1513
+ OUTPUT: ``None``. Raises an ``AssertionError`` on failure.
1514
+
1515
+ EXAMPLES::
1516
+
1517
+ sage: from sage.graphs.graph_decompositions.modular_decomposition import *
1518
+ sage: check_algos_are_equivalent(3, lambda : graphs.RandomGNP(10, 0.1))
1519
+ sage: check_algos_are_equivalent(3, lambda : graphs.RandomGNP(10, 0.5))
1520
+ sage: check_algos_are_equivalent(3, lambda : graphs.RandomGNP(10, 0.9))
1521
+ """
1522
+ for _ in range(trials):
1523
+ graph = graph_gen()
1524
+ if verbose:
1525
+ print(graph.graph6_string())
1526
+ print(graph.to_dictionary())
1527
+ MD = []
1528
+ for algo in ('habib_maurer', 'corneil_habib_paul_tedder'):
1529
+ md = modular_decomposition(graph, algorithm=algo)
1530
+ MD.append(md)
1531
+ if verbose:
1532
+ print(f'Using {algo}:')
1533
+ print_md_tree(md)
1534
+ assert equivalent_trees(MD[0], MD[1])
1535
+ if verbose:
1536
+ print("Passes!")