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