passagemath-graphs 10.5.43__cp39-cp39-musllinux_1_2_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_graphs-10.5.43.dist-info/METADATA +293 -0
- passagemath_graphs-10.5.43.dist-info/RECORD +258 -0
- passagemath_graphs-10.5.43.dist-info/WHEEL +5 -0
- passagemath_graphs-10.5.43.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 +2552 -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 +125 -0
- sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
- sage/combinat/cluster_algebra_quiver/mutation_type.py +1556 -0
- sage/combinat/cluster_algebra_quiver/quiver.py +2262 -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 +534 -0
- sage/combinat/designs/database.py +5614 -0
- sage/combinat/designs/design_catalog.py +122 -0
- sage/combinat/designs/designs_pyx.cpython-39-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-39-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-39-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 +548 -0
- sage/combinat/designs/orthogonal_arrays.py +2243 -0
- sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +966 -0
- sage/combinat/designs/resolvable_bibd.py +781 -0
- sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
- sage/combinat/designs/subhypergraph_search.cpython-39-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/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-39-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +174 -0
- sage/combinat/posets/hasse_diagram.py +3678 -0
- sage/combinat/posets/incidence_algebras.py +796 -0
- sage/combinat/posets/lattices.py +5119 -0
- sage/combinat/posets/linear_extension_iterator.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/combinat/posets/linear_extension_iterator.pyx +292 -0
- sage/combinat/posets/linear_extensions.py +1039 -0
- sage/combinat/posets/mobile.py +275 -0
- sage/combinat/posets/moebius_algebra.py +776 -0
- sage/combinat/posets/poset_examples.py +2131 -0
- sage/combinat/posets/posets.py +9169 -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 +1230 -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-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/asteroidal_triples.pyx +299 -0
- sage/graphs/base/all.py +1 -0
- sage/graphs/base/boost_graph.cpython-39-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-39-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-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/dense_graph.pxd +26 -0
- sage/graphs/base/dense_graph.pyx +757 -0
- sage/graphs/base/graph_backends.cpython-39-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-39-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-39-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-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_backend.pxd +27 -0
- sage/graphs/base/static_sparse_backend.pyx +1580 -0
- sage/graphs/base/static_sparse_graph.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/base/static_sparse_graph.pxd +37 -0
- sage/graphs/base/static_sparse_graph.pyx +1304 -0
- sage/graphs/bipartite_graph.py +2709 -0
- sage/graphs/centrality.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/centrality.pyx +965 -0
- sage/graphs/cographs.py +519 -0
- sage/graphs/comparability.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/comparability.pyx +813 -0
- sage/graphs/connectivity.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/connectivity.pxd +157 -0
- sage/graphs/connectivity.pyx +4813 -0
- sage/graphs/convexity_properties.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/convexity_properties.pxd +16 -0
- sage/graphs/convexity_properties.pyx +827 -0
- sage/graphs/digraph.py +4410 -0
- sage/graphs/digraph_generators.py +1921 -0
- sage/graphs/distances_all_pairs.cpython-39-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-39-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-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/generators/distance_regular.pyx +2846 -0
- sage/graphs/generators/families.py +4749 -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 +26395 -0
- sage/graphs/generic_graph_pyx.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/generic_graph_pyx.pxd +34 -0
- sage/graphs/generic_graph_pyx.pyx +1626 -0
- sage/graphs/genus.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/genus.pyx +623 -0
- sage/graphs/graph.py +9362 -0
- sage/graphs/graph_coloring.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_coloring.pyx +2284 -0
- sage/graphs/graph_database.py +1122 -0
- sage/graphs/graph_decompositions/all.py +1 -0
- sage/graphs/graph_decompositions/bandwidth.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +595 -0
- sage/graphs/graph_decompositions/cutwidth.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-39-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-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +462 -0
- sage/graphs/graph_decompositions/modular_decomposition.cpython-39-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-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +1080 -0
- sage/graphs/graph_decompositions/tree_decomposition.cpython-39-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-39-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 +3301 -0
- sage/graphs/graph_generators_pyx.cpython-39-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 +367 -0
- sage/graphs/graph_plot.py +1749 -0
- sage/graphs/graph_plot_js.py +338 -0
- sage/graphs/hyperbolicity.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/hyperbolicity.pyx +1702 -0
- sage/graphs/hypergraph_generators.py +364 -0
- sage/graphs/independent_sets.cpython-39-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-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +453 -0
- sage/graphs/line_graph.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/line_graph.pyx +627 -0
- sage/graphs/lovasz_theta.py +77 -0
- sage/graphs/matching.py +1633 -0
- sage/graphs/matching_covered_graph.py +3566 -0
- sage/graphs/orientations.py +1504 -0
- sage/graphs/partial_cube.py +459 -0
- sage/graphs/path_enumeration.cpython-39-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-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/spanning_tree.pyx +1457 -0
- sage/graphs/strongly_regular_db.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/strongly_regular_db.pyx +3340 -0
- sage/graphs/traversals.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/traversals.pxd +9 -0
- sage/graphs/traversals.pyx +1871 -0
- sage/graphs/trees.cpython-39-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-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/views.pyx +794 -0
- sage/graphs/weakly_chordal.cpython-39-aarch64-linux-gnu.so +0 -0
- sage/graphs/weakly_chordal.pyx +562 -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-39-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 +2880 -0
- sage/knots/link.py +4682 -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 +1977 -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 +5161 -0
- sage/topology/simplicial_complex_catalog.py +86 -0
- sage/topology/simplicial_complex_examples.py +1692 -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,1080 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
# cython: binding=True
|
3
|
+
# distutils: language = c++
|
4
|
+
# distutils: extra_compile_args = -std=c++11
|
5
|
+
r"""
|
6
|
+
Slice decomposition
|
7
|
+
|
8
|
+
This module implements an extended lexBFS algorithm for computing the slice
|
9
|
+
decomposition of undirected graphs and the class :class:`~SliceDecomposition` to
|
10
|
+
represent such decompositions.
|
11
|
+
|
12
|
+
A formal definition of slice decompositions can be found in Section 3.2 of
|
13
|
+
[TCHP2008]_ and a description of the extended lexBFS algorithm is given in
|
14
|
+
Section 3.3 of [TCHP2008]_.
|
15
|
+
|
16
|
+
AUTHORS:
|
17
|
+
|
18
|
+
- Cyril Bouvier (2024-06-25): initial version
|
19
|
+
"""
|
20
|
+
# ****************************************************************************
|
21
|
+
# Copyright (C) 2024 Cyril Bouvier <cyril.bouvier@lirmm.fr>
|
22
|
+
#
|
23
|
+
# This program is free software: you can redistribute it and/or modify
|
24
|
+
# it under the terms of the GNU General Public License as published by
|
25
|
+
# the Free Software Foundation, either version 2 of the License, or
|
26
|
+
# (at your option) any later version.
|
27
|
+
# https://www.gnu.org/licenses/
|
28
|
+
# ****************************************************************************
|
29
|
+
|
30
|
+
from libcpp.algorithm cimport swap
|
31
|
+
from cython.operator cimport dereference as deref
|
32
|
+
|
33
|
+
from sage.graphs.base.c_graph cimport CGraphBackend
|
34
|
+
from sage.data_structures.bitset_base cimport bitset_in
|
35
|
+
|
36
|
+
cdef void extended_lex_BFS(
|
37
|
+
CGraph cg, vector[int] &sigma, vector[int] *sigma_inv,
|
38
|
+
int initial_v_int, vector[int] *pred, vector[size_t] *xslice_len,
|
39
|
+
vector[vector[int]] *lex_label) except *:
|
40
|
+
r"""
|
41
|
+
Perform a extended lexicographic breadth first search (LexBFS) on the
|
42
|
+
undirected graph `G`.
|
43
|
+
|
44
|
+
In addition to computing a LexBFS ordering, the extended LexBFS algorithm
|
45
|
+
can be used to compute the slice decomposition of the graph.
|
46
|
+
|
47
|
+
This function implements the `O(n+m)` time algorithm proposed in [HMPV2000]_
|
48
|
+
and [TCHP2008]_.
|
49
|
+
|
50
|
+
INPUT:
|
51
|
+
|
52
|
+
- ``cg`` -- a ``CGraph``. This function ignores loops and multiple edges and
|
53
|
+
assumes that the graph is undirected.
|
54
|
+
|
55
|
+
- ``sigma`` -- vector of ``int`` to store the ordering of the vertices
|
56
|
+
resulting from the LexBFS traversal. At the end, the vector will have size
|
57
|
+
`n` (the number of vertices of the graph).
|
58
|
+
|
59
|
+
- ``sigma_inv`` -- a pointer to a vector to store the inverse of the
|
60
|
+
permutation ``sigma``. ``sigma_inv`` can be ``NULL`` if the caller does
|
61
|
+
not need it (but, note that, the inverse of ``sigma`` is still needed by
|
62
|
+
the algorithm, so it does not save time nor memory to have ``sigma_inv``
|
63
|
+
equal to ``NULL``). At the end, if ``sigma_inv`` is not NULL, the vector
|
64
|
+
pointer by it will have size `n` (the number of vertices of the graph)
|
65
|
+
and will satisfy:
|
66
|
+
* sigma[deref(sigma_inv)[v_int]] = v_int
|
67
|
+
* deref(sigma_inv)[sigma[i]] = i
|
68
|
+
|
69
|
+
- ``initial_v_int`` -- the first vertex to consider. It can be `-1`; in this
|
70
|
+
case the first active vertex (corresponding to the first bit set in
|
71
|
+
``cg.active_vertices``) will be taken as first vertex.
|
72
|
+
|
73
|
+
- ``pred`` -- a pointer to a vector of int to store the predecessor of a
|
74
|
+
vertex in the LexBFS traversal. ``pred`` can be ``NULL`` if the caller
|
75
|
+
does not need it (and the information will not be computed by the
|
76
|
+
algorithm). At the end, if ``pred`` is not NULL, the vector pointer by it
|
77
|
+
will have size `n` (the number of vertices of the graph) and pred[i] will
|
78
|
+
be either -1 (if sigma[i] as no predecessor) or a positive value less than
|
79
|
+
n such that the predecessor of sigma[i] is sigma[pred[i]].
|
80
|
+
|
81
|
+
- ``xslice_len`` -- a pointer to a vector of size_t to store the length of
|
82
|
+
the x-slices associated with the lexBFS traversal. ``xslice_len`` can be
|
83
|
+
``NULL`` if the caller does not need it (and the information will not be
|
84
|
+
computed by the algorithm). At the end, if ``xslice_len`` is not NULL, the
|
85
|
+
vector pointer by it will have size `n` (the number of vertices of the
|
86
|
+
graph) and the length of the x-slice starting at sigma[i] will be
|
87
|
+
xslice_len[i].
|
88
|
+
|
89
|
+
- ``lex_label`` -- a pointer to a vector of vector[int] to store the
|
90
|
+
lexicographic labels associated with the lexBFS traversal. ``lex_label``
|
91
|
+
can be ``NULL`` if the caller does not need it (and the information will
|
92
|
+
not be computed by the algorithm). At the end, if ``lex_label`` is not
|
93
|
+
NULL, the vector pointer by it will have size `n` (the number of
|
94
|
+
vertices of the graph) and the lexicographic label of sigma[i]
|
95
|
+
will given by lex_label[i].
|
96
|
+
|
97
|
+
ALGORITHM:
|
98
|
+
|
99
|
+
This algorithm uses the notion of *partition refinement* to determine the
|
100
|
+
exact position of the vertices in the ordering.
|
101
|
+
|
102
|
+
Consider an ordering `\sigma` of the vertices. For a vertex `v`, we define
|
103
|
+
`N_i(v) = \{u | u \in N(v) \text{ and } \sigma(u) < i\}`, that is the subset
|
104
|
+
of neighbors of `v` appearing before the `i`-th vertex in the ordering
|
105
|
+
`\sigma`. Now, a part of an ordering `\sigma` is a set of consecutive
|
106
|
+
vertices, `S = \{u | i \leq \sigma(u) \leq j\}`, such that for any `u \in
|
107
|
+
S`, we have `N_i(u) = N_i(\sigma^{-1}(i))` and for any `v` such that `j <
|
108
|
+
\sigma(v)`, `N_i(v) \neq N_i(\sigma^{-1}(i))`. The *head* of a part is the
|
109
|
+
first position of its vertices.
|
110
|
+
|
111
|
+
The algorithm starts with a single part containing all vertices. Then, when
|
112
|
+
the position of the `i`-th vertex `v` is fixed, it explores the neighbors of
|
113
|
+
`v` that have not yet been ordered. Consider a part `S` such that `N(x)\cap
|
114
|
+
S \neq \emptyset`. The algorithm will rearrange the ordering of the vertices
|
115
|
+
in `S` so that the first vertices are the neighbors of `v`. The subpart
|
116
|
+
containing the neighbors of `v` is assigned a new name, and the head of `S`
|
117
|
+
is set to the position of the first vertex of `S \setminus N(v)` in the
|
118
|
+
ordering `\sigma`.
|
119
|
+
|
120
|
+
Observe that each arc of the graph can induce the subdivision of a part.
|
121
|
+
Hence, the algorithm can use up to `m + 1` different parts.
|
122
|
+
|
123
|
+
The time complexity of this algorithm is in `O(n + m)`, and our
|
124
|
+
implementation follows that complexity ``SparseGraph``. For ``DenseGraph``,
|
125
|
+
the complexity is `O(n^2)`. See [HMPV2000]_ and [TCHP2008]_ for more
|
126
|
+
details.
|
127
|
+
|
128
|
+
This implementation of extended LexBFS offers some guarantee on the order in
|
129
|
+
which the vertices appear in the computed ordering: in case of a tie between
|
130
|
+
lexicographic labels during the computation, this function will "choose" the
|
131
|
+
vertices in the order in which they appear during the enumeration of the
|
132
|
+
neighbors of their last common neighbor.
|
133
|
+
For example, if `(u_0, ..., u_k)` is the beginning of the ordering being
|
134
|
+
computed and the vertices `v` and `w` currently have the same lexicographic
|
135
|
+
label (it means that they have the same neighbors in `(u_0, ..., u_k)`).
|
136
|
+
Let call `u_j` their last neighbor in the current ordering (*i.e.*, for all
|
137
|
+
`i > j`, `u_i` is not a neighbor of `v` and `w`). This implementation
|
138
|
+
will choose `v` for the next vertex of the ordering if and only if `v`
|
139
|
+
appeared before `w` when the neighbors of `u_j` where enumerated.
|
140
|
+
|
141
|
+
One possible use of this guarantee is that the caller can reorder the
|
142
|
+
adjacency list of vertices (by using, for example, a static sparse graph)
|
143
|
+
to force the computed LexBFS order to respect a previous one.
|
144
|
+
|
145
|
+
EXAMPLES::
|
146
|
+
|
147
|
+
To see how it can be used, see the code of the lex_BFS method (in
|
148
|
+
traversals.pyx) or of the class SliceDecomposition in this module.
|
149
|
+
|
150
|
+
TESTS:
|
151
|
+
|
152
|
+
Indirect doctests::
|
153
|
+
|
154
|
+
sage: G = graphs.HouseGraph()
|
155
|
+
sage: G.slice_decomposition()
|
156
|
+
[0[1[2]] [3] [4]]
|
157
|
+
sage: G.lex_BFS(algorithm="fast")
|
158
|
+
[0, 1, 2, 3, 4]
|
159
|
+
"""
|
160
|
+
cdef int n = <int> cg.num_verts
|
161
|
+
# Variables for the partition refinement algorithm
|
162
|
+
cdef size_t max_nparts = cg.num_arcs // 2 + 1
|
163
|
+
cdef bint need_to_delete_sigma_inv = sigma_inv == NULL
|
164
|
+
if sigma_inv == NULL:
|
165
|
+
sigma_inv = new vector[int]()
|
166
|
+
cdef vector[size_t] part_of = vector[size_t](n, 0)
|
167
|
+
cdef vector[size_t] part_len # only used if xslice_len != NULL (see below)
|
168
|
+
cdef vector[size_t] part_head = vector[size_t](max_nparts)
|
169
|
+
cdef vector[size_t] subpart = vector[size_t](max_nparts)
|
170
|
+
cdef size_t p, part_of_i, nparts, old_nparts
|
171
|
+
# Temporary variables
|
172
|
+
cdef int max_degree = 0
|
173
|
+
cdef int i, j, k, l, u_int, v_int, t_int
|
174
|
+
|
175
|
+
# Resize vectors
|
176
|
+
sigma.resize(n)
|
177
|
+
deref(sigma_inv).resize(cg.active_vertices.size)
|
178
|
+
if pred != NULL:
|
179
|
+
deref(pred).clear()
|
180
|
+
deref(pred).resize(n, -1) # initialize pred[i] to -1 for 0 <= i < n
|
181
|
+
if xslice_len != NULL:
|
182
|
+
deref(xslice_len).resize(n)
|
183
|
+
part_len.resize(max_nparts)
|
184
|
+
if lex_label != NULL:
|
185
|
+
deref(lex_label).resize(n)
|
186
|
+
|
187
|
+
# Initialize the position of vertices in sigma (and compute max_degree)
|
188
|
+
if initial_v_int >= 0:
|
189
|
+
sigma[0] = initial_v_int
|
190
|
+
deref(sigma_inv)[initial_v_int] = 0
|
191
|
+
i = 1
|
192
|
+
else:
|
193
|
+
i = 0
|
194
|
+
for v_int in range(<int> cg.active_vertices.size):
|
195
|
+
if bitset_in(cg.active_vertices, v_int):
|
196
|
+
if v_int != initial_v_int:
|
197
|
+
sigma[i] = v_int
|
198
|
+
deref(sigma_inv)[v_int] = i
|
199
|
+
i = i + 1
|
200
|
+
max_degree = max(max_degree, cg.out_degrees[v_int])
|
201
|
+
|
202
|
+
# Variables needed to iterate over neighbors of a vertex
|
203
|
+
cdef int nneighbors
|
204
|
+
cdef vector[int] neighbors = vector[int](max_degree)
|
205
|
+
|
206
|
+
# Initialize partition: one part containing all the vertices
|
207
|
+
nparts = 1
|
208
|
+
# all element of part_of are already initialized to 0
|
209
|
+
part_head[0] = 0
|
210
|
+
subpart[0] = 0
|
211
|
+
if xslice_len != NULL:
|
212
|
+
part_len[0] = n
|
213
|
+
|
214
|
+
# Main loop
|
215
|
+
for i in range(n):
|
216
|
+
old_nparts = nparts
|
217
|
+
|
218
|
+
part_of_i = part_of[i]
|
219
|
+
|
220
|
+
# put i out of its part (updating part_len if needed)
|
221
|
+
part_head[part_of_i] += 1
|
222
|
+
if xslice_len != NULL:
|
223
|
+
deref(xslice_len)[i] = part_len[part_of_i]
|
224
|
+
part_len[part_of_i] -= 1
|
225
|
+
|
226
|
+
v_int = sigma[i]
|
227
|
+
|
228
|
+
# Iterate over the neighbors of v
|
229
|
+
nneighbors = cg.out_neighbors_unsafe (v_int, neighbors.data(), max_degree)
|
230
|
+
for k in range(nneighbors):
|
231
|
+
u_int = neighbors[k]
|
232
|
+
j = deref(sigma_inv)[u_int] # get the position of u
|
233
|
+
if j <= i:
|
234
|
+
continue # already taken care of
|
235
|
+
|
236
|
+
if lex_label != NULL:
|
237
|
+
deref(lex_label)[j].push_back (v_int)
|
238
|
+
|
239
|
+
p = part_of[j] # get the part of u
|
240
|
+
l = part_head[p] # get the beginning of the part containing u
|
241
|
+
|
242
|
+
# if not last and next elem belongs in the same part (ie #part >= 2)
|
243
|
+
if l < n - 1 and part_of[l + 1] == p:
|
244
|
+
if l != j: # not already first elem of the part
|
245
|
+
# Place u at the position of the head of the part
|
246
|
+
t_int = sigma[l]
|
247
|
+
deref(sigma_inv)[t_int], deref(sigma_inv)[u_int] = j, l
|
248
|
+
sigma[j], sigma[l] = t_int, u_int
|
249
|
+
if lex_label != NULL:
|
250
|
+
swap[vector[int]](deref(lex_label)[j],
|
251
|
+
deref(lex_label)[l])
|
252
|
+
j = l
|
253
|
+
part_head[p] += 1 # move the head of the part to next elem
|
254
|
+
|
255
|
+
# if part p was not already cut in two during this iteration, we
|
256
|
+
# create a new part using subpart
|
257
|
+
if subpart[p] < old_nparts:
|
258
|
+
subpart[p] = nparts
|
259
|
+
part_head[nparts] = j
|
260
|
+
if xslice_len != NULL:
|
261
|
+
part_len[nparts] = 0
|
262
|
+
subpart[nparts] = 0
|
263
|
+
nparts += 1
|
264
|
+
|
265
|
+
# Finally, we update the name of the part for position j and set v
|
266
|
+
# as predecessor of u
|
267
|
+
part_of[j] = subpart[p]
|
268
|
+
if xslice_len != NULL:
|
269
|
+
part_len[p] -= 1
|
270
|
+
part_len[subpart[p]] += 1
|
271
|
+
if pred != NULL:
|
272
|
+
deref(pred)[j] = i
|
273
|
+
|
274
|
+
if need_to_delete_sigma_inv:
|
275
|
+
del sigma_inv
|
276
|
+
|
277
|
+
|
278
|
+
def slice_decomposition(G, initial_vertex=None):
|
279
|
+
r"""
|
280
|
+
Compute a slice decomposition of the simple undirected graph
|
281
|
+
|
282
|
+
INPUT:
|
283
|
+
|
284
|
+
- ``G`` -- a Sage graph.
|
285
|
+
|
286
|
+
- ``initial_vertex`` -- (default: ``None``); the first vertex to consider.
|
287
|
+
|
288
|
+
OUTPUT:
|
289
|
+
|
290
|
+
An object of type :class:`~sage.graphs.graph_decompositions.slice_decomposition.SliceDecomposition`
|
291
|
+
that represents a slice decomposition of ``G``
|
292
|
+
|
293
|
+
.. NOTE::
|
294
|
+
|
295
|
+
Loops and multiple edges are ignored during the computation of the slice
|
296
|
+
decomposition.
|
297
|
+
|
298
|
+
ALGORITHM:
|
299
|
+
|
300
|
+
The method use the algorithm based on "partition refinement" described in
|
301
|
+
[HMPV2000]_ and [TCHP2008]_.
|
302
|
+
The time complexity of this algorithm is in `O(n + m)`, and our
|
303
|
+
implementation follows that complexity for ``SparseGraph``. For
|
304
|
+
``DenseGraph``, the complexity is `O(n^2)`.
|
305
|
+
|
306
|
+
EXAMPLES:
|
307
|
+
|
308
|
+
Slice decomposition of the Petersen Graph::
|
309
|
+
|
310
|
+
sage: G = graphs.PetersenGraph()
|
311
|
+
sage: SD = G.slice_decomposition(); SD
|
312
|
+
[0[1[4[5]]] [2[6]] [3] [9] [7] [8]]
|
313
|
+
|
314
|
+
The graph can have loops or multiple edges but they are ignored::
|
315
|
+
|
316
|
+
sage: H = Graph(G,loops=True,multiedges=True)
|
317
|
+
sage: H.add_edges([(4, 4), (2, 2), (1, 6)])
|
318
|
+
sage: SD2 = H.slice_decomposition()
|
319
|
+
sage: SD2 == SD
|
320
|
+
True
|
321
|
+
sage: SD2.underlying_graph() == G.to_simple(immutable=True)
|
322
|
+
True
|
323
|
+
|
324
|
+
The tree corresponding to the slice decomposition can be displayed using
|
325
|
+
``view``::
|
326
|
+
|
327
|
+
sage: from sage.graphs.graph_latex import check_tkz_graph
|
328
|
+
sage: check_tkz_graph() # random - depends on Tex installation
|
329
|
+
sage: view(G) # not tested
|
330
|
+
sage: latex(G) # to obtain the corresponding LaTeX code # needs sage.plot
|
331
|
+
\begin{tikzpicture}
|
332
|
+
...
|
333
|
+
\end{tikzpicture}
|
334
|
+
|
335
|
+
Slice decompositions are only defined for undirected graphs::
|
336
|
+
|
337
|
+
sage: from sage.graphs.graph_decompositions.slice_decomposition import slice_decomposition
|
338
|
+
sage: slice_decomposition(DiGraph())
|
339
|
+
Traceback (most recent call last):
|
340
|
+
...
|
341
|
+
ValueError: parameter G must be an undirected graph
|
342
|
+
"""
|
343
|
+
return SliceDecomposition(G, initial_vertex=initial_vertex)
|
344
|
+
|
345
|
+
|
346
|
+
cdef class SliceDecomposition(SageObject):
|
347
|
+
|
348
|
+
def __init__(self, G, initial_vertex=None):
|
349
|
+
r"""
|
350
|
+
Represents a slice decomposition of a simple directed graph.
|
351
|
+
|
352
|
+
INPUT:
|
353
|
+
|
354
|
+
- ``G`` -- a Sage graph.
|
355
|
+
|
356
|
+
- ``initial_vertex`` -- (default: ``None``); the first vertex to
|
357
|
+
consider.
|
358
|
+
|
359
|
+
.. SEEALSO::
|
360
|
+
|
361
|
+
* :meth:`~slice_decomposition` -- compute a slice decomposition of
|
362
|
+
the simple undirected graph
|
363
|
+
* Section 3.2 of [TCHP2008]_ for a formal definition.
|
364
|
+
|
365
|
+
EXAMPLES:
|
366
|
+
|
367
|
+
The constructor of the :class:`~SliceDecomposition` class is called by
|
368
|
+
the :meth:`~slice_decomposition` method of undirected graphs::
|
369
|
+
|
370
|
+
sage: from sage.graphs.graph_decompositions.slice_decomposition import SliceDecomposition
|
371
|
+
sage: G = graphs.PetersenGraph()
|
372
|
+
sage: SliceDecomposition(G) == G.slice_decomposition()
|
373
|
+
True
|
374
|
+
|
375
|
+
The vertex appearing first in the slice decomposition can be specified::
|
376
|
+
|
377
|
+
sage: from sage.graphs.graph_decompositions.slice_decomposition import SliceDecomposition
|
378
|
+
sage: SliceDecomposition(graphs.PetersenGraph(), initial_vertex=3)
|
379
|
+
[3[2[4[8]]] [1[7]] [0] [9] [6] [5]]
|
380
|
+
|
381
|
+
Slice decompositions are not defined for directed graphs::
|
382
|
+
|
383
|
+
sage: from sage.graphs.graph_decompositions.slice_decomposition import SliceDecomposition
|
384
|
+
sage: SliceDecomposition(DiGraph())
|
385
|
+
Traceback (most recent call last):
|
386
|
+
...
|
387
|
+
ValueError: parameter G must be an undirected graph
|
388
|
+
|
389
|
+
.. automethod:: __getitem__
|
390
|
+
"""
|
391
|
+
if G.is_directed():
|
392
|
+
raise ValueError("parameter G must be an undirected graph")
|
393
|
+
|
394
|
+
if initial_vertex is not None and initial_vertex not in G:
|
395
|
+
raise LookupError(f"vertex ({initial_vertex}) is not a vertex of the graph")
|
396
|
+
|
397
|
+
cdef CGraphBackend Gbackend = <CGraphBackend> G._backend
|
398
|
+
cdef CGraph cg = Gbackend.cg()
|
399
|
+
|
400
|
+
self._graph_class = type(G)
|
401
|
+
|
402
|
+
cdef int initial_v_int
|
403
|
+
if initial_vertex is not None:
|
404
|
+
# we already checked that initial_vertex is in G
|
405
|
+
initial_v_int = Gbackend.get_vertex(initial_vertex)
|
406
|
+
else:
|
407
|
+
initial_v_int = -1
|
408
|
+
|
409
|
+
cdef vector[int] sigma
|
410
|
+
cdef vector[vector[int]] lex_label
|
411
|
+
|
412
|
+
# Compute the slice decomposition using the extended lexBFS algorithm
|
413
|
+
extended_lex_BFS(cg, sigma, NULL, initial_v_int, NULL,
|
414
|
+
&(self.xslice_len), &lex_label)
|
415
|
+
|
416
|
+
# Translate the results with the actual vertices of the graph
|
417
|
+
self.sigma = tuple(Gbackend.vertex_label(v_int) for v_int in sigma)
|
418
|
+
self.sigma_inv = {v: i for i, v in enumerate(self.sigma)}
|
419
|
+
self.lex_label = {i: tuple(Gbackend.vertex_label(v_int) for v_int in lli)
|
420
|
+
for i, lli in enumerate(lex_label)}
|
421
|
+
|
422
|
+
def __eq__(self, other):
|
423
|
+
"""
|
424
|
+
Return whether ``self`` and ``other`` are equal.
|
425
|
+
|
426
|
+
TESTS::
|
427
|
+
|
428
|
+
sage: G = graphs.PetersenGraph()
|
429
|
+
sage: SD = G.slice_decomposition()
|
430
|
+
sage: SD == SD
|
431
|
+
True
|
432
|
+
sage: SD == G.slice_decomposition()
|
433
|
+
True
|
434
|
+
|
435
|
+
sage: P3 = graphs.PathGraph(3)
|
436
|
+
sage: SD1 = P3.slice_decomposition(initial_vertex=0)
|
437
|
+
sage: SD2 = P3.slice_decomposition(initial_vertex=2)
|
438
|
+
sage: SD1 == SD2
|
439
|
+
False
|
440
|
+
sage: SD3 = graphs.CompleteGraph(3).slice_decomposition()
|
441
|
+
sage: SD1 == SD3 # same lexBFS but different slice for 1
|
442
|
+
False
|
443
|
+
sage: SD4 = Graph([(0,1), (0,2)]).slice_decomposition()
|
444
|
+
sage: SD3 == SD4 # same lexBFS and slices but different active edges
|
445
|
+
False
|
446
|
+
"""
|
447
|
+
if not isinstance(other, type(self)):
|
448
|
+
return False
|
449
|
+
|
450
|
+
cdef SliceDecomposition sd = <SliceDecomposition>other
|
451
|
+
|
452
|
+
return self.sigma_inv == sd.sigma_inv \
|
453
|
+
and self.lex_label == sd.lex_label \
|
454
|
+
and self.xslice_len == sd.xslice_len
|
455
|
+
|
456
|
+
def __hash__(self):
|
457
|
+
r"""
|
458
|
+
Compute a hash of a ``SliceDecomposition`` object.
|
459
|
+
|
460
|
+
TESTS::
|
461
|
+
|
462
|
+
sage: P3 = graphs.PathGraph(3)
|
463
|
+
sage: SD1 = P3.slice_decomposition(initial_vertex=0)
|
464
|
+
sage: SD2 = P3.slice_decomposition(initial_vertex=2)
|
465
|
+
sage: len({SD1: 1, SD2: 2}) # indirect doctest
|
466
|
+
2
|
467
|
+
"""
|
468
|
+
return hash((tuple(self.sigma_inv.items()),
|
469
|
+
tuple(self.lex_label.items()),
|
470
|
+
tuple(self.xslice_len)))
|
471
|
+
|
472
|
+
def __getitem__(self, v):
|
473
|
+
r"""
|
474
|
+
Return the data about the x-slice of the vertex `v`.
|
475
|
+
|
476
|
+
INPUT:
|
477
|
+
|
478
|
+
- ``v`` -- a vertex of the graph corresponding to the slice
|
479
|
+
decomposition.
|
480
|
+
|
481
|
+
OUTPUT:
|
482
|
+
|
483
|
+
A dictionnary with the keys:
|
484
|
+
|
485
|
+
* ``"pivot"`` -- the vertex `v` given as parameter
|
486
|
+
|
487
|
+
* ``"slice"`` -- the slice of `v` (see :meth:`~slice`)
|
488
|
+
|
489
|
+
* ``"active_edges"`` -- the actives edges of `v` (see
|
490
|
+
:meth:`~active_edges`)
|
491
|
+
|
492
|
+
* ``"lexicographic_label"`` -- the lexicographic label of `v` (see
|
493
|
+
:meth:`~lexicographic_label`)
|
494
|
+
|
495
|
+
* ``"sequence"`` -- the x-slice sequence of `v` (see
|
496
|
+
:meth:`~xslice_sequence`)
|
497
|
+
|
498
|
+
This method can also be called via :meth:`xslice_data`.
|
499
|
+
|
500
|
+
EXAMPLES:
|
501
|
+
|
502
|
+
::
|
503
|
+
|
504
|
+
sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False)
|
505
|
+
sage: SD = G.slice_decomposition(initial_vertex='x')
|
506
|
+
sage: SD.xslice_data('a')
|
507
|
+
{'active_edges': [('a', 'b'),
|
508
|
+
('a', 'c'),
|
509
|
+
('a', 'd'),
|
510
|
+
('a', 'e'),
|
511
|
+
('a', 'f'),
|
512
|
+
('c', 'g'),
|
513
|
+
('d', 'g'),
|
514
|
+
('f', 'g')],
|
515
|
+
'lexicographic_label': ['x'],
|
516
|
+
'pivot': 'a',
|
517
|
+
'sequence': [['a'], ['b', 'c', 'd', 'e', 'f'], ['g']],
|
518
|
+
'slice': ['a', 'b', 'c', 'd', 'e', 'f', 'g']}
|
519
|
+
sage: SD.xslice_data('u')
|
520
|
+
{'active_edges': [],
|
521
|
+
'lexicographic_label': ['a', 'b', 'c', 'd', 'e', 'f', 'g'],
|
522
|
+
'pivot': 'u',
|
523
|
+
'sequence': [['u'], ['y', 'z']],
|
524
|
+
'slice': ['u', 'y', 'z']}
|
525
|
+
|
526
|
+
Some values of the returned dictionnary can be obtained via other
|
527
|
+
methods (:meth:`~slice`, :meth:`~xslice_sequence`,
|
528
|
+
:meth:`~active_edges`, :meth:`~lexicographic_label`)::
|
529
|
+
|
530
|
+
sage: SD.slice('a')
|
531
|
+
['a', 'b', 'c', 'd', 'e', 'f', 'g']
|
532
|
+
sage: SD.xslice_data('a')['slice']
|
533
|
+
['a', 'b', 'c', 'd', 'e', 'f', 'g']
|
534
|
+
|
535
|
+
sage: SD.xslice_sequence('a')
|
536
|
+
[['a'], ['b', 'c', 'd', 'e', 'f'], ['g']]
|
537
|
+
sage: SD.xslice_data('a')['sequence']
|
538
|
+
[['a'], ['b', 'c', 'd', 'e', 'f'], ['g']]
|
539
|
+
|
540
|
+
sage: SD.active_edges('b') == SD.xslice_data('b')['active_edges']
|
541
|
+
True
|
542
|
+
|
543
|
+
sage: SD.lexicographic_label('u')
|
544
|
+
['a', 'b', 'c', 'd', 'e', 'f', 'g']
|
545
|
+
sage: SD.xslice_data('u')['lexicographic_label']
|
546
|
+
['a', 'b', 'c', 'd', 'e', 'f', 'g']
|
547
|
+
|
548
|
+
TESTS::
|
549
|
+
|
550
|
+
sage: G = graphs.RandomGNP(15, 0.3)
|
551
|
+
sage: SD = G.slice_decomposition()
|
552
|
+
sage: all(SD[v]['slice'] == SD.slice(v) for v in G)
|
553
|
+
True
|
554
|
+
sage: all(SD[v]['sequence'] == SD.xslice_sequence(v) for v in G)
|
555
|
+
True
|
556
|
+
sage: all(SD[v]['active_edges'] == SD.active_edges(v) for v in G)
|
557
|
+
True
|
558
|
+
sage: all(SD[v]['lexicographic_label'] == SD.lexicographic_label(v) for v in G)
|
559
|
+
True
|
560
|
+
|
561
|
+
sage: SD = graphs.PetersenGraph().slice_decomposition()
|
562
|
+
sage: SD['John']
|
563
|
+
Traceback (most recent call last):
|
564
|
+
...
|
565
|
+
LookupError: vertex (John) does not appear in the slice decomposition
|
566
|
+
"""
|
567
|
+
if v not in self.sigma_inv:
|
568
|
+
raise LookupError(f"vertex ({v}) does not appear in the slice "
|
569
|
+
"decomposition")
|
570
|
+
cdef size_t i = self.sigma_inv[v]
|
571
|
+
return {'pivot': v,
|
572
|
+
'slice': self._slice(i),
|
573
|
+
'sequence': self._xslice_sequence(i),
|
574
|
+
'lexicographic_label': self._xslice_lex_label(i),
|
575
|
+
'active_edges': self._xslice_active_edges(i),
|
576
|
+
}
|
577
|
+
|
578
|
+
def lexBFS_order(self):
|
579
|
+
r"""
|
580
|
+
Return the lexBFS order corresponding to the slice decomposition.
|
581
|
+
|
582
|
+
EXAMPLES::
|
583
|
+
|
584
|
+
sage: from sage.graphs.traversals import _is_valid_lex_BFS_order
|
585
|
+
sage: G = graphs.PetersenGraph(); SD = G.slice_decomposition()
|
586
|
+
sage: SD.lexBFS_order()
|
587
|
+
[0, 1, 4, 5, 2, 6, 3, 9, 7, 8]
|
588
|
+
sage: _is_valid_lex_BFS_order(G, SD.lexBFS_order())
|
589
|
+
True
|
590
|
+
|
591
|
+
TESTS::
|
592
|
+
|
593
|
+
sage: from sage.graphs.traversals import _is_valid_lex_BFS_order
|
594
|
+
sage: for _ in range(5):
|
595
|
+
....: G = graphs.RandomGNP(15, 0.3)
|
596
|
+
....: SD = G.slice_decomposition()
|
597
|
+
....: _is_valid_lex_BFS_order(G, SD.lexBFS_order())
|
598
|
+
True
|
599
|
+
True
|
600
|
+
True
|
601
|
+
True
|
602
|
+
True
|
603
|
+
"""
|
604
|
+
return list(self.sigma)
|
605
|
+
|
606
|
+
def xslice_data(self, v):
|
607
|
+
r"""
|
608
|
+
Return the data about the x-slice of the vertex `v`.
|
609
|
+
|
610
|
+
This method is a wrapper around :meth:`SliceDecomposition.__getitem__`
|
611
|
+
|
612
|
+
TESTS::
|
613
|
+
|
614
|
+
sage: G = graphs.RandomGNP(15, 0.3)
|
615
|
+
sage: SD = G.slice_decomposition()
|
616
|
+
sage: all(SD[v] == SD.xslice_data(v) for v in G)
|
617
|
+
True
|
618
|
+
"""
|
619
|
+
return self[v]
|
620
|
+
|
621
|
+
def slice(self, v):
|
622
|
+
r"""
|
623
|
+
Return the slice of the vertex `v`.
|
624
|
+
|
625
|
+
The slice of `v` is the list of vertices `u` such that the neighbors of
|
626
|
+
`u` that are before `v` in the lexBFS order are that same that the
|
627
|
+
neighbors of `v` that are before `v` in the lexBFS order (*i.e.*, the
|
628
|
+
lexicographic label of `v`). It can be shown that it is a factor of the
|
629
|
+
lexBFS order.
|
630
|
+
|
631
|
+
INPUT:
|
632
|
+
|
633
|
+
- ``v`` -- a vertex of the graph corresponding to the slice
|
634
|
+
decomposition.
|
635
|
+
|
636
|
+
OUTPUT:
|
637
|
+
|
638
|
+
A list of vertices
|
639
|
+
|
640
|
+
EXAMPLES:
|
641
|
+
|
642
|
+
::
|
643
|
+
|
644
|
+
sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False)
|
645
|
+
sage: SD = G.slice_decomposition(initial_vertex='x')
|
646
|
+
sage: SD.slice('a')
|
647
|
+
['a', 'b', 'c', 'd', 'e', 'f', 'g']
|
648
|
+
|
649
|
+
The vertices of the slice have the same neighborhood "on the left"::
|
650
|
+
|
651
|
+
sage: pos = lambda v: SD.lexBFS_order().index(v)
|
652
|
+
sage: lla = set(SD.lexicographic_label('a'))
|
653
|
+
sage: all(lla == {u for u in G.neighbors(v) if pos(u) < pos('a')} \
|
654
|
+
....: for v in SD.slice('a'))
|
655
|
+
True
|
656
|
+
|
657
|
+
The slice is a factor of the lexBFS order::
|
658
|
+
|
659
|
+
sage: ''.join(SD.slice('a')) in ''.join(SD.lexBFS_order())
|
660
|
+
True
|
661
|
+
|
662
|
+
The slice of the initial vertex is the whole graph::
|
663
|
+
|
664
|
+
sage: SD.slice('x') == SD.lexBFS_order()
|
665
|
+
True
|
666
|
+
|
667
|
+
TESTS::
|
668
|
+
|
669
|
+
sage: SD.slice('Michael')
|
670
|
+
Traceback (most recent call last):
|
671
|
+
...
|
672
|
+
LookupError: vertex (Michael) does not appear in the slice decomposition
|
673
|
+
"""
|
674
|
+
if v not in self.sigma_inv:
|
675
|
+
raise LookupError(f"vertex ({v}) does not appear in the slice "
|
676
|
+
"decomposition")
|
677
|
+
cdef size_t i = self.sigma_inv[v]
|
678
|
+
return self._slice(i)
|
679
|
+
|
680
|
+
def xslice_sequence(self, v):
|
681
|
+
r"""
|
682
|
+
Return the x-slice sequence of the vertex `v`.
|
683
|
+
|
684
|
+
INPUT:
|
685
|
+
|
686
|
+
- ``v`` -- a vertex of the graph corresponding to the slice
|
687
|
+
decomposition.
|
688
|
+
|
689
|
+
OUTPUT:
|
690
|
+
|
691
|
+
A list of list corresponding to the x-slice sequence of ``v``.
|
692
|
+
|
693
|
+
EXAMPLES:
|
694
|
+
|
695
|
+
::
|
696
|
+
|
697
|
+
sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False)
|
698
|
+
sage: SD = G.slice_decomposition(initial_vertex='x')
|
699
|
+
sage: SD.xslice_sequence('x')
|
700
|
+
[['x'], ['a', 'b', 'c', 'd', 'e', 'f', 'g'], ['u', 'y', 'z'], ['v', 'w']]
|
701
|
+
sage: SD.xslice_sequence('a')
|
702
|
+
[['a'], ['b', 'c', 'd', 'e', 'f'], ['g']]
|
703
|
+
|
704
|
+
The flatten x-slice sequence of a vertex corresponds to the slice of the
|
705
|
+
same vertex::
|
706
|
+
|
707
|
+
sage: from itertools import chain
|
708
|
+
sage: all(list(chain(*SD.xslice_sequence(v))) == SD.slice(v) \
|
709
|
+
....: for v in G)
|
710
|
+
True
|
711
|
+
|
712
|
+
The first list of the sequence is always a singleton containing the
|
713
|
+
input vertex::
|
714
|
+
|
715
|
+
sage: all(SD.xslice_sequence(v)[0] == [v] for v in G)
|
716
|
+
True
|
717
|
+
|
718
|
+
If the length of the slice if more than 1, the second list of the
|
719
|
+
sequence is either, all the remaining vertices of the slice of `v`, if
|
720
|
+
`v` is isolated in the subgraph induced by the slice of `v`, or the
|
721
|
+
neighbors of `v` in the subgraph induced by the slice of `v`::
|
722
|
+
|
723
|
+
sage: all(SD.xslice_sequence(v)[1] == SD.slice(v)[1:] for v in G \
|
724
|
+
....: if G.subgraph(SD.slice(v)).degree(v) == 0 \
|
725
|
+
....: and len(SD.slice(v)) > 1)
|
726
|
+
True
|
727
|
+
sage: for v in G:
|
728
|
+
....: if len(SD.slice(v)) > 1:
|
729
|
+
....: xslice_seq = SD.xslice_sequence(v)
|
730
|
+
....: S = G.subgraph(SD.slice(v))
|
731
|
+
....: if S.degree(v) > 0:
|
732
|
+
....: set(xslice_seq[1]) == set(S.neighbor_iterator(v))
|
733
|
+
True
|
734
|
+
True
|
735
|
+
True
|
736
|
+
True
|
737
|
+
|
738
|
+
TESTS::
|
739
|
+
|
740
|
+
sage: SD = graphs.PetersenGraph().slice_decomposition()
|
741
|
+
sage: SD.xslice_sequence('Terry')
|
742
|
+
Traceback (most recent call last):
|
743
|
+
...
|
744
|
+
LookupError: vertex (Terry) does not appear in the slice decomposition
|
745
|
+
"""
|
746
|
+
if v not in self.sigma_inv:
|
747
|
+
raise LookupError(f"vertex ({v}) does not appear in the slice "
|
748
|
+
"decomposition")
|
749
|
+
cdef size_t i = self.sigma_inv[v]
|
750
|
+
return self._xslice_sequence(i)
|
751
|
+
|
752
|
+
def lexicographic_label(self, v):
|
753
|
+
r"""
|
754
|
+
Return the lexicographic label of the vertex `v`.
|
755
|
+
|
756
|
+
The lexicographic label of a vertex `v` is the list of all the
|
757
|
+
neighbors of `v` that appear before `v` in the lexBFS ordering
|
758
|
+
corresponding to the slice decomposition.
|
759
|
+
|
760
|
+
INPUT:
|
761
|
+
|
762
|
+
- ``v`` -- a vertex of the graph corresponding to the slice
|
763
|
+
decomposition.
|
764
|
+
|
765
|
+
OUTPUT:
|
766
|
+
|
767
|
+
A list of vertices.
|
768
|
+
|
769
|
+
EXAMPLES::
|
770
|
+
|
771
|
+
sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False)
|
772
|
+
sage: SD = G.slice_decomposition(initial_vertex='x')
|
773
|
+
sage: SD.lexicographic_label('f')
|
774
|
+
['x', 'a', 'c', 'd']
|
775
|
+
sage: pos = lambda v: SD.lexBFS_order().index(v)
|
776
|
+
sage: set(SD.lexicographic_label('f')) \
|
777
|
+
....: == {v for v in G.neighbors('f') if pos(v) < pos('f')}
|
778
|
+
True
|
779
|
+
|
780
|
+
TESTS::
|
781
|
+
|
782
|
+
sage: SD = graphs.PetersenGraph().slice_decomposition()
|
783
|
+
sage: SD.lexicographic_label('Eric')
|
784
|
+
Traceback (most recent call last):
|
785
|
+
...
|
786
|
+
LookupError: vertex (Eric) does not appear in the slice decomposition
|
787
|
+
"""
|
788
|
+
if v not in self.sigma_inv:
|
789
|
+
raise LookupError(f"vertex ({v}) does not appear in the slice "
|
790
|
+
"decomposition")
|
791
|
+
cdef size_t i = self.sigma_inv[v]
|
792
|
+
return self._xslice_lex_label(i)
|
793
|
+
|
794
|
+
def active_edges(self, v):
|
795
|
+
r"""
|
796
|
+
Return the active edges of the vertex `v`.
|
797
|
+
|
798
|
+
An edge `(u, w)` is said to be active for `v` if `u` and `w` belongs
|
799
|
+
to two differents slices of the x-slice sequence of `v`. Note that it
|
800
|
+
defines a partition of the edges of the underlying graph.
|
801
|
+
|
802
|
+
INPUT:
|
803
|
+
|
804
|
+
- ``v`` -- a vertex of the graph corresponding to the slice
|
805
|
+
decomposition.
|
806
|
+
|
807
|
+
OUTPUT:
|
808
|
+
|
809
|
+
A list of edges
|
810
|
+
|
811
|
+
EXAMPLES:
|
812
|
+
|
813
|
+
::
|
814
|
+
|
815
|
+
sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False)
|
816
|
+
sage: SD = G.slice_decomposition(initial_vertex='x')
|
817
|
+
sage: SD.xslice_sequence('a')
|
818
|
+
[['a'], ['b', 'c', 'd', 'e', 'f'], ['g']]
|
819
|
+
sage: ('c', 'g') in SD.active_edges('a')
|
820
|
+
True
|
821
|
+
sage: ('a', 'c') in SD.active_edges('a')
|
822
|
+
True
|
823
|
+
sage: ('c', 'd') in SD.active_edges('a') # c and d in same slice
|
824
|
+
False
|
825
|
+
sage: ('a', 'u') in SD.active_edges('a') # u not in x-slice of a
|
826
|
+
False
|
827
|
+
|
828
|
+
The set of active edges of every vertex is a partition of the edges::
|
829
|
+
|
830
|
+
sage: from itertools import chain
|
831
|
+
sage: E = list(chain(*(SD.active_edges(v) for v in G)))
|
832
|
+
sage: G.size() == len(E) == len(set(E)) \
|
833
|
+
....: and all(G.has_edge(u, w) for v in G for u, w in SD.active_edges(v))
|
834
|
+
True
|
835
|
+
|
836
|
+
TESTS::
|
837
|
+
|
838
|
+
sage: SD = graphs.PetersenGraph().slice_decomposition()
|
839
|
+
sage: SD.active_edges('Graham')
|
840
|
+
Traceback (most recent call last):
|
841
|
+
...
|
842
|
+
LookupError: vertex (Graham) does not appear in the slice decomposition
|
843
|
+
"""
|
844
|
+
if v not in self.sigma_inv:
|
845
|
+
raise LookupError(f"vertex ({v}) does not appear in the slice "
|
846
|
+
"decomposition")
|
847
|
+
cdef size_t i = self.sigma_inv[v]
|
848
|
+
return self._xslice_active_edges(i)
|
849
|
+
|
850
|
+
def _slice(self, size_t idx):
|
851
|
+
r"""
|
852
|
+
This method is for internal use only
|
853
|
+
|
854
|
+
TESTS:
|
855
|
+
|
856
|
+
Indirect doctests::
|
857
|
+
|
858
|
+
sage: SD = graphs.HouseGraph().slice_decomposition()
|
859
|
+
sage: SD.slice(1)
|
860
|
+
[1, 2]
|
861
|
+
"""
|
862
|
+
return list(self.sigma[idx:idx+self.xslice_len[idx]])
|
863
|
+
|
864
|
+
def _xslice_sequence(self, size_t idx):
|
865
|
+
r"""
|
866
|
+
This method is for internal use only
|
867
|
+
|
868
|
+
TESTS:
|
869
|
+
|
870
|
+
Indirect doctests::
|
871
|
+
|
872
|
+
sage: SD = graphs.HouseGraph().slice_decomposition()
|
873
|
+
sage: SD.xslice_sequence(0)
|
874
|
+
[[0], [1, 2], [3], [4]]
|
875
|
+
"""
|
876
|
+
cdef size_t l = self.xslice_len[idx]
|
877
|
+
cdef size_t j = idx + 1
|
878
|
+
cdef size_t lj
|
879
|
+
|
880
|
+
S = [ [self.sigma[idx]] ]
|
881
|
+
while j < idx + l:
|
882
|
+
lj = self.xslice_len[j]
|
883
|
+
S.append(list(self.sigma[j:j+lj]))
|
884
|
+
j += lj
|
885
|
+
assert j == idx + l, "slice decomposition is ill-formed"
|
886
|
+
return S
|
887
|
+
|
888
|
+
def _xslice_lex_label(self, size_t idx):
|
889
|
+
r"""
|
890
|
+
This method is for internal use only
|
891
|
+
|
892
|
+
TESTS:
|
893
|
+
|
894
|
+
Indirect doctests::
|
895
|
+
|
896
|
+
sage: SD = graphs.HouseGraph().slice_decomposition()
|
897
|
+
sage: SD.lexicographic_label(3)
|
898
|
+
[1, 2]
|
899
|
+
"""
|
900
|
+
return list(self.lex_label[idx])
|
901
|
+
|
902
|
+
def _xslice_active_edges(self, size_t idx):
|
903
|
+
r"""
|
904
|
+
This method is for internal use only
|
905
|
+
|
906
|
+
TESTS:
|
907
|
+
|
908
|
+
Indirect doctests::
|
909
|
+
|
910
|
+
sage: SD = graphs.HouseGraph().slice_decomposition()
|
911
|
+
sage: SD.active_edges(0)
|
912
|
+
[(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)]
|
913
|
+
"""
|
914
|
+
cdef size_t l = self.xslice_len[idx]
|
915
|
+
cdef size_t llv_prefix = len(self.lex_label[idx])
|
916
|
+
cdef size_t j = idx + 1
|
917
|
+
cdef size_t lj
|
918
|
+
|
919
|
+
A = []
|
920
|
+
while j < idx + l:
|
921
|
+
lj = self.xslice_len[j]
|
922
|
+
llj = self.lex_label[j]
|
923
|
+
for u in self.sigma[j:j+lj]:
|
924
|
+
for w in llj[llv_prefix:]:
|
925
|
+
A.append((w, u))
|
926
|
+
j += lj
|
927
|
+
assert j == idx + l, "slice decomposition is ill-formed"
|
928
|
+
return A
|
929
|
+
|
930
|
+
def underlying_graph(self):
|
931
|
+
r"""
|
932
|
+
Return the underlying graph corresponding to the slice decomposition.
|
933
|
+
|
934
|
+
If `G` was the graph given as parameter to compute the slice
|
935
|
+
decomposition, the underlying graph corresponds to ``G.to_simple()``
|
936
|
+
where labels are ignored, *i.e.*, it is the input graph without loops,
|
937
|
+
multiple edges and labels.
|
938
|
+
|
939
|
+
.. NOTE::
|
940
|
+
|
941
|
+
This method is mostly defined to test the computation of
|
942
|
+
lexicographic labels and actives edges.
|
943
|
+
|
944
|
+
EXAMPLES:
|
945
|
+
|
946
|
+
::
|
947
|
+
|
948
|
+
sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False)
|
949
|
+
sage: SD = G.slice_decomposition(initial_vertex='x')
|
950
|
+
sage: SD.underlying_graph() == G
|
951
|
+
True
|
952
|
+
|
953
|
+
The graph can have loops or multiple edges but they are ignored::
|
954
|
+
|
955
|
+
sage: G = graphs.CubeConnectedCycle(2) # multiple edges
|
956
|
+
sage: SD = G.slice_decomposition()
|
957
|
+
sage: SD.underlying_graph() == G.to_simple(immutable=True)
|
958
|
+
True
|
959
|
+
|
960
|
+
sage: G = graphs.CubeConnectedCycle(1) # loops
|
961
|
+
sage: SD = G.slice_decomposition()
|
962
|
+
sage: SD.underlying_graph() == G.to_simple(immutable=True)
|
963
|
+
True
|
964
|
+
|
965
|
+
TESTS::
|
966
|
+
|
967
|
+
sage: for _ in range(5):
|
968
|
+
....: G = graphs.RandomGNP(15, 0.3)
|
969
|
+
....: SD = G.slice_decomposition()
|
970
|
+
....: SD.underlying_graph() == G
|
971
|
+
True
|
972
|
+
True
|
973
|
+
True
|
974
|
+
True
|
975
|
+
True
|
976
|
+
"""
|
977
|
+
if not hasattr(self, '_underlying_graph'):
|
978
|
+
vertices = self.sigma
|
979
|
+
edges = [(u, v) for i, v in enumerate(self.sigma)
|
980
|
+
for u in self.lex_label[i]]
|
981
|
+
data = [vertices, edges]
|
982
|
+
Gclass = self._graph_class
|
983
|
+
self._underlying_graph = Gclass(data, format='vertices_and_edges',
|
984
|
+
immutable=True)
|
985
|
+
return self._underlying_graph
|
986
|
+
|
987
|
+
def _repr_(self):
|
988
|
+
r"""
|
989
|
+
Return a string representation of a ``SliceDecomposition`` object.
|
990
|
+
|
991
|
+
TESTS::
|
992
|
+
|
993
|
+
sage: G = graphs.PetersenGraph(); SD = G.slice_decomposition()
|
994
|
+
sage: repr(SD)
|
995
|
+
'[0[1[4[5]]] [2[6]] [3] [9] [7] [8]]'
|
996
|
+
sage: G = Graph('L~mpn~Nrv{^o~_').relabel('abcdefguvwxyz',inplace=False)
|
997
|
+
sage: SD = G.slice_decomposition(initial_vertex='x'); repr(SD)
|
998
|
+
'[x[a[b[c[d]] [e[f]]] [g]] [u[y[z]]] [v[w]]]'
|
999
|
+
"""
|
1000
|
+
def inner_repr(idx):
|
1001
|
+
l = self.xslice_len[idx]
|
1002
|
+
S = []
|
1003
|
+
if l > 1:
|
1004
|
+
j = idx + 1
|
1005
|
+
while j < idx + l:
|
1006
|
+
lj = self.xslice_len[j]
|
1007
|
+
S.append(inner_repr(j))
|
1008
|
+
j += lj
|
1009
|
+
assert j == idx + l, "slice decomposition is ill-formed"
|
1010
|
+
return f'{self.sigma[idx]}' + ' '.join(f'[{s}]' for s in S)
|
1011
|
+
return f'[{inner_repr(0)}]'
|
1012
|
+
|
1013
|
+
def _latex_(self):
|
1014
|
+
r"""
|
1015
|
+
Return a string to render, using `\LaTeX`, the slice decomposition as a
|
1016
|
+
tree.
|
1017
|
+
|
1018
|
+
TESTS::
|
1019
|
+
|
1020
|
+
sage: from sage.graphs.graph_latex import check_tkz_graph
|
1021
|
+
sage: check_tkz_graph() # random - depends on Tex installation
|
1022
|
+
sage: G = graphs.PetersenGraph(); SD = G.slice_decomposition()
|
1023
|
+
sage: latex(SD)
|
1024
|
+
\begin{tikzpicture}
|
1025
|
+
...
|
1026
|
+
v0 -- {l0, v1, v4, v6, v7, v8, v9};
|
1027
|
+
v1 -- {l1, v2};
|
1028
|
+
v2 -- {l2, v3};
|
1029
|
+
v3 -- {l3};
|
1030
|
+
v4 -- {l4, v5};
|
1031
|
+
v5 -- {l5};
|
1032
|
+
v6 -- {l6};
|
1033
|
+
v7 -- {l7};
|
1034
|
+
v8 -- {l8};
|
1035
|
+
v9 -- {l9};
|
1036
|
+
...
|
1037
|
+
\end{tikzpicture}
|
1038
|
+
|
1039
|
+
"""
|
1040
|
+
from sage.misc.latex import latex
|
1041
|
+
|
1042
|
+
latex.add_package_to_preamble_if_available("tikz")
|
1043
|
+
latex.add_to_preamble(r"\usetikzlibrary{arrows,shapes,fit}")
|
1044
|
+
latex.add_to_preamble(r"\usetikzlibrary{graphs,graphdrawing}")
|
1045
|
+
latex.add_to_preamble(r"\usegdlibrary{trees}")
|
1046
|
+
|
1047
|
+
# Call latex() on all vertices
|
1048
|
+
sigma_latex = [ latex(v) for v in self.sigma ]
|
1049
|
+
slices = [[] for _ in self.sigma]
|
1050
|
+
|
1051
|
+
lines = [ r"\begin{tikzpicture}" ]
|
1052
|
+
lines.append(r"\graph [tree layout,level distance=0,level sep=1em,"
|
1053
|
+
r"sibling distance=0,sibling sep=0.6em,"
|
1054
|
+
r"tail anchor=center,head anchor=north,"
|
1055
|
+
r"nodes={draw,rectangle,inner xsep=0.2em},edges={thick}]")
|
1056
|
+
lines.append("{")
|
1057
|
+
bo, bc = "{", "}" # to write { and } in f-strings
|
1058
|
+
# Create the nodes and leaves of the slice decomposition tree
|
1059
|
+
for i in range(len(self.sigma)):
|
1060
|
+
l = self.xslice_len[i]
|
1061
|
+
label = r"\ ".join(sigma_latex[i:i+l])
|
1062
|
+
lines.append(f" v{i}[as={bo}${label}${bc}];")
|
1063
|
+
lines.append(f" l{i}[draw=none,as={bo}${sigma_latex[i]}${bc}];")
|
1064
|
+
j = i + 1
|
1065
|
+
slices[i].append(f"l{i}")
|
1066
|
+
while j < i + l:
|
1067
|
+
slices[i].append(f"v{j}")
|
1068
|
+
j += self.xslice_len[j]
|
1069
|
+
# Create the edges of the slice decomposition tree
|
1070
|
+
for i, S in enumerate(slices):
|
1071
|
+
lines.append(f" v{i} -- " + "{" + ", ".join(S) + "};")
|
1072
|
+
lines.append("};")
|
1073
|
+
# Add dahsed red boxes around xslices
|
1074
|
+
for i, S in enumerate(slices):
|
1075
|
+
fit=" ".join(f"({s})" for s in S)
|
1076
|
+
lines.append(rf"\node (s{i}) [rectangle,inner xsep=0.2em,draw=red,"
|
1077
|
+
f"densely dashed,fit={fit}]{bo}{bc};")
|
1078
|
+
|
1079
|
+
lines.append(r"\end{tikzpicture}")
|
1080
|
+
return "\n".join(lines)
|