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,1963 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
# cython: binding=True
|
3
|
+
r"""
|
4
|
+
Vertex separation
|
5
|
+
|
6
|
+
This module implements several algorithms to compute the vertex separation of a
|
7
|
+
digraph and the corresponding ordering of the vertices. It also implements tests
|
8
|
+
functions for evaluation the width of a linear ordering.
|
9
|
+
|
10
|
+
Given an ordering
|
11
|
+
`v_1,\cdots, v_n` of the vertices of `V(G)`, its *cost* is defined as:
|
12
|
+
|
13
|
+
.. MATH::
|
14
|
+
|
15
|
+
c(v_1, ..., v_n) = \max_{1\leq i \leq n} c'(\{v_1, ..., v_i\})
|
16
|
+
|
17
|
+
Where
|
18
|
+
|
19
|
+
.. MATH::
|
20
|
+
|
21
|
+
c'(S) = |N^+_G(S)\backslash S|
|
22
|
+
|
23
|
+
The *vertex separation* of a digraph `G` is equal to the minimum cost of an
|
24
|
+
ordering of its vertices.
|
25
|
+
|
26
|
+
**Vertex separation and pathwidth**
|
27
|
+
|
28
|
+
The vertex separation is defined on a digraph, but one can obtain from a graph
|
29
|
+
`G` a digraph `D` with the same vertex set, and in which each edge `uv` of `G`
|
30
|
+
is replaced by two edges `uv` and `vu` in `D`. The vertex separation of `D` is
|
31
|
+
equal to the pathwidth of `G`, and the corresponding ordering of the vertices of
|
32
|
+
`D`, also called a *layout*, encodes an optimal path-decomposition of `G`.
|
33
|
+
This is a result of Kinnersley [Kin1992]_ and Bodlaender [Bod1998]_.
|
34
|
+
|
35
|
+
|
36
|
+
**This module contains the following methods**
|
37
|
+
|
38
|
+
.. csv-table::
|
39
|
+
:class: contentstable
|
40
|
+
:widths: 30, 70
|
41
|
+
:delim: |
|
42
|
+
|
43
|
+
:meth:`pathwidth` | Compute the pathwidth of ``self`` (and provides a decomposition)
|
44
|
+
:meth:`path_decomposition` | Return the pathwidth of the given graph and the ordering of the vertices resulting in a corresponding path decomposition
|
45
|
+
:meth:`vertex_separation` | Return an optimal ordering of the vertices and its cost for vertex-separation
|
46
|
+
:meth:`vertex_separation_exp` | Compute the vertex separation of `G` using an exponential time and space algorithm
|
47
|
+
:meth:`vertex_separation_MILP` | Compute the vertex separation of `G` and the optimal ordering of its vertices using an MILP formulation
|
48
|
+
:meth:`vertex_separation_BAB` | Compute the vertex separation of `G` and the optimal ordering of its vertices using a branch and bound algorithm
|
49
|
+
:meth:`lower_bound` | Return a lower bound on the vertex separation of `G`
|
50
|
+
:meth:`is_valid_ordering` | Test if the linear vertex ordering `L` is valid for (di)graph `G`
|
51
|
+
:meth:`width_of_path_decomposition` | Return the width of the path decomposition induced by the linear ordering `L` of the vertices of `G`
|
52
|
+
:meth:`linear_ordering_to_path_decomposition`| Return the path decomposition encoded in the ordering `L`
|
53
|
+
|
54
|
+
|
55
|
+
Exponential algorithm for vertex separation
|
56
|
+
-------------------------------------------
|
57
|
+
|
58
|
+
In order to find an optimal ordering of the vertices for the vertex separation,
|
59
|
+
this algorithm tries to save time by computing the function `c'(S)` **at most
|
60
|
+
once** once for each of the sets `S\subseteq V(G)`. These values are stored in
|
61
|
+
an array of size `2^n` where reading the value of `c'(S)` or updating it can be
|
62
|
+
done in constant (and small) time.
|
63
|
+
|
64
|
+
Assuming that we can compute the cost of a set `S` and remember it, finding an
|
65
|
+
optimal ordering is an easy task. Indeed, we can think of the sequence `v_1,
|
66
|
+
..., v_n` of vertices as a sequence of *sets* `\{v_1\}, \{v_1,v_2\}, ...,
|
67
|
+
\{v_1,...,v_n\}`, whose cost is precisely `\max c'(\{v_1\}), c'(\{v_1,v_2\}),
|
68
|
+
... , c'(\{v_1,...,v_n\})`. Hence, when considering the digraph on the `2^n`
|
69
|
+
sets `S\subseteq V(G)` where there is an arc from `S` to `S'` if `S'=S\cap
|
70
|
+
\{v\}` for some `v` (that is, if the sets `S` and `S'` can be consecutive in a
|
71
|
+
sequence), an ordering of the vertices of `G` corresponds to a *path* from
|
72
|
+
`\emptyset` to `\{v_1,...,v_n\}`. In this setting, checking whether there exists
|
73
|
+
a ordering of cost less than `k` can be achieved by checking whether there
|
74
|
+
exists a directed path `\emptyset` to `\{v_1,...,v_n\}` using only sets of cost
|
75
|
+
less than `k`. This is just a depth-first-search, for each `k`.
|
76
|
+
|
77
|
+
**Lazy evaluation of** `c'`
|
78
|
+
|
79
|
+
In the previous algorithm, most of the time is actually spent on the computation
|
80
|
+
of `c'(S)` for each set `S\subseteq V(G)` -- i.e. `2^n` computations of
|
81
|
+
neighborhoods. This can be seen as a huge waste of time when noticing that it is
|
82
|
+
useless to know that the value `c'(S)` for a set `S` is less than `k` if all the
|
83
|
+
paths leading to `S` have a cost greater than `k`. For this reason, the value of
|
84
|
+
`c'(S)` is computed lazily during the depth-first search. Explanation :
|
85
|
+
|
86
|
+
When the depth-first search discovers a set of size less than `k`, the costs of
|
87
|
+
its out-neighbors (the potential sets that could follow it in the optimal
|
88
|
+
ordering) are evaluated. When an out-neighbor is found that has a cost smaller
|
89
|
+
than `k`, the depth-first search continues with this set, which is explored with
|
90
|
+
the hope that it could lead to a path toward `\{v_1,...,v_n\}`. On the other
|
91
|
+
hand, if an out-neighbour has a cost larger than `k` it is useless to attempt to
|
92
|
+
build a cheap sequence going though this set, and the exploration stops
|
93
|
+
there. This way, a large number of sets will never be evaluated and *a lot* of
|
94
|
+
computational time is saved this way.
|
95
|
+
|
96
|
+
Besides, some improvement is also made by "improving" the values found by
|
97
|
+
`c'`. Indeed, `c'(S)` is a lower bound on the cost of a sequence containing the
|
98
|
+
set `S`, but if all out-neighbors of `S` have a cost of `c'(S) + 5` then one
|
99
|
+
knows that having `S` in a sequence means a total cost of at least `c'(S) +
|
100
|
+
5`. For this reason, for each set `S` we store the value of `c'(S)`, and replace
|
101
|
+
it by `\max (c'(S), \min_{\text{next}})` (where `\min_{\text{next}}` is the
|
102
|
+
minimum of the costs of the out-neighbors of `S`) once the costs of these
|
103
|
+
out-neighbors have been evaluated by the algorithm.
|
104
|
+
|
105
|
+
.. NOTE::
|
106
|
+
|
107
|
+
Because of its current implementation, this algorithm only works on graphs
|
108
|
+
on less than 32 vertices. This can be changed to 64 if necessary, but 32
|
109
|
+
vertices already require 4GB of memory. Running it on 64 bits is not
|
110
|
+
expected to be doable by the computers of the next decade `:-D`
|
111
|
+
|
112
|
+
**Lower bound on the vertex separation**
|
113
|
+
|
114
|
+
One can obtain a lower bound on the vertex separation of a graph in exponential
|
115
|
+
time but *small* memory by computing once the cost of each set `S`. Indeed, the
|
116
|
+
cost of a sequence `v_1, ..., v_n` corresponding to sets `\{v_1\}, \{v_1,v_2\},
|
117
|
+
..., \{v_1,...,v_n\}` is
|
118
|
+
|
119
|
+
.. MATH::
|
120
|
+
|
121
|
+
\max c'(\{v_1\}),c'(\{v_1,v_2\}),...,c'(\{v_1,...,v_n\})\geq\max c'_1,...,c'_n
|
122
|
+
|
123
|
+
where `c_i` is the minimum cost of a set `S` on `i` vertices. Evaluating the
|
124
|
+
`c_i` can take time (and in particular more than the previous exact algorithm),
|
125
|
+
but it does not need much memory to run.
|
126
|
+
|
127
|
+
|
128
|
+
MILP formulation for the vertex separation
|
129
|
+
------------------------------------------
|
130
|
+
|
131
|
+
We describe below a mixed integer linear program (MILP) for determining an
|
132
|
+
optimal layout for the vertex separation of `G`, which is an improved version of
|
133
|
+
the formulation proposed in [SP2010]_. It aims at building a sequence `S_t` of
|
134
|
+
sets such that an ordering `v_1, ..., v_n` of the vertices correspond to
|
135
|
+
`S_0=\{v_1\}, S_2=\{v_1,v_2\}, ..., S_{n-1}=\{v_1,...,v_n\}`.
|
136
|
+
|
137
|
+
**Variables:**
|
138
|
+
|
139
|
+
|
140
|
+
- `y_v^t` -- variable set to 1 if `v\in S_t`, and 0 otherwise. The order of
|
141
|
+
`v` in the layout is the smallest `t` such that `y_v^t==1`.
|
142
|
+
|
143
|
+
- `u_v^t` -- variable set to 1 if `v\not \in S_t` and `v` has an in-neighbor in
|
144
|
+
`S_t`. It is set to 0 otherwise.
|
145
|
+
|
146
|
+
- `x_v^t` -- variable set to 1 if either `v\in S_t` or if `v` has an in-neighbor
|
147
|
+
in `S_t`. It is set to 0 otherwise.
|
148
|
+
|
149
|
+
- `z` -- objective value to minimize. It is equal to the maximum over all step
|
150
|
+
`t` of the number of vertices such that `u_v^t==1`.
|
151
|
+
|
152
|
+
**MILP formulation:**
|
153
|
+
|
154
|
+
.. MATH::
|
155
|
+
:nowrap:
|
156
|
+
|
157
|
+
\begin{alignat}{2}
|
158
|
+
\text{Minimize:}
|
159
|
+
&z&\\
|
160
|
+
\text{Such that:}
|
161
|
+
x_v^t &\leq x_v^{t+1}& \forall v\in V,\ 0\leq t\leq n-2\\
|
162
|
+
y_v^t &\leq y_v^{t+1}& \forall v\in V,\ 0\leq t\leq n-2\\
|
163
|
+
y_v^t &\leq x_w^t& \forall v\in V,\ \forall w\in N^+(v),\ 0\leq t\leq n-1\\
|
164
|
+
\sum_{v \in V} y_v^{t} &= t+1& 0\leq t\leq n-1\\
|
165
|
+
x_v^t-y_v^t&\leq u_v^t & \forall v \in V,\ 0\leq t\leq n-1\\
|
166
|
+
\sum_{v \in V} u_v^t &\leq z& 0\leq t\leq n-1\\
|
167
|
+
0 \leq x_v^t &\leq 1& \forall v\in V,\ 0\leq t\leq n-1\\
|
168
|
+
0 \leq u_v^t &\leq 1& \forall v\in V,\ 0\leq t\leq n-1\\
|
169
|
+
y_v^t &\in \{0,1\}& \forall v\in V,\ 0\leq t\leq n-1\\
|
170
|
+
0 \leq z &\leq n&
|
171
|
+
\end{alignat}
|
172
|
+
|
173
|
+
The vertex separation of `G` is given by the value of `z`, and the order of
|
174
|
+
vertex `v` in the optimal layout is given by the smallest `t` for which
|
175
|
+
`y_v^t==1`.
|
176
|
+
|
177
|
+
|
178
|
+
Branch and Bound algorithm for the vertex separation
|
179
|
+
----------------------------------------------------
|
180
|
+
|
181
|
+
We describe below the principle of a branch and bound algorithm (BAB) for
|
182
|
+
determining an optimal ordering for the vertex separation of `G`, as proposed in
|
183
|
+
[CMN2014]_.
|
184
|
+
|
185
|
+
**Greedy steps:**
|
186
|
+
|
187
|
+
Let us denote `{\cal L}(S)` the set of all possible orderings of the vertices in
|
188
|
+
`S`, and let `{\cal L}_P(S)\subseteq {\cal L}(S)` be the orderings starting with
|
189
|
+
a prefix `P`. Let also `c(L)` be the cost of the ordering `L\in{\cal L}(V)` as
|
190
|
+
defined above.
|
191
|
+
|
192
|
+
Given a digraph `D=(V,A)`, a set `S\subset V`, and a prefix `P`, it has been
|
193
|
+
proved in [CMN2014]_ that `\min_{L\in{\cal L}_P(V)} c(L) = \min_{L\in{\cal
|
194
|
+
L}_{P+v}(V)} c(L)` holds in two (non exhaustive) cases:
|
195
|
+
|
196
|
+
.. MATH::
|
197
|
+
|
198
|
+
\text{or} \begin{cases}
|
199
|
+
N^+(v)\subseteq S\cup N^+(S)\\
|
200
|
+
v\in N^+(S)\text{ and }N^+(v)\setminus(S\cup N^+(S)) = \{w\}
|
201
|
+
\end{cases}
|
202
|
+
|
203
|
+
In other words, if we find a vertex `v` satisfying the above conditions, the
|
204
|
+
best possible ordering with prefix `P` has the same cost as the best possible
|
205
|
+
ordering with prefix `P+v`. So we can greedily extend the prefix with vertices
|
206
|
+
satisfying the conditions which results in a significant reduction of the search
|
207
|
+
space.
|
208
|
+
|
209
|
+
|
210
|
+
**The algorithm:**
|
211
|
+
|
212
|
+
Given the current prefix `P` and the current upper bound `UB` (either an input
|
213
|
+
upper bound or the cost of the best solution found so far), apply the following
|
214
|
+
steps:
|
215
|
+
|
216
|
+
- Extend the prefix `P` into a prefix `P'` using the greedy steps as described
|
217
|
+
above.
|
218
|
+
|
219
|
+
- Sort the vertices `v\in V\setminus P'` by increasing values of `|N^+(P+v)|`,
|
220
|
+
and prune the vertices with a value larger or equal to `UB`. Let `\Delta` be
|
221
|
+
the resulting sorted list.
|
222
|
+
|
223
|
+
- Repeat with prefix `P'+v` for all `v\in\Delta` and keep the best found
|
224
|
+
solution.
|
225
|
+
|
226
|
+
If a lower bound is passed to the algorithm, it will stop as soon as a solution
|
227
|
+
with cost equal to that lower bound is found.
|
228
|
+
|
229
|
+
|
230
|
+
**Storing prefixes:**
|
231
|
+
|
232
|
+
If for a prefix `P` we have `c(P)<\min_{L\in{\cal L}_P(V)} c(L)=C`, then for any
|
233
|
+
permutation `P'` of `P` we have `\min_{L\in{\cal L}_{P'}(V)} c(L)\geq C`.
|
234
|
+
|
235
|
+
Thus, given such a prefix `P` there is no need to explore any of the orderings
|
236
|
+
starting with one of its permutations. To do so, we store `P` (as a set of
|
237
|
+
vertices) to cut branches later. See [CMN2014]_ for more details.
|
238
|
+
|
239
|
+
Since the number of stored sets can get very large, one can control the maximum
|
240
|
+
length and the maximum number of stored prefixes.
|
241
|
+
|
242
|
+
|
243
|
+
Authors
|
244
|
+
-------
|
245
|
+
|
246
|
+
- Nathann Cohen (2011-10): Initial version and exact exponential algorithm
|
247
|
+
|
248
|
+
- David Coudert (2012-04): MILP formulation and tests functions
|
249
|
+
|
250
|
+
- David Coudert (2015-01): BAB formulation and tests functions
|
251
|
+
|
252
|
+
|
253
|
+
Methods
|
254
|
+
-------
|
255
|
+
"""
|
256
|
+
|
257
|
+
# ****************************************************************************
|
258
|
+
# Copyright (C) 2011 Nathann Cohen <nathann.cohen@gmail.com>
|
259
|
+
#
|
260
|
+
# This program is free software: you can redistribute it and/or modify
|
261
|
+
# it under the terms of the GNU General Public License as published by
|
262
|
+
# the Free Software Foundation, either version 2 of the License, or
|
263
|
+
# (at your option) any later version.
|
264
|
+
# http://www.gnu.org/licenses/
|
265
|
+
# ****************************************************************************
|
266
|
+
|
267
|
+
from libc.string cimport memset
|
268
|
+
from cysignals.memory cimport check_malloc, sig_malloc, sig_free
|
269
|
+
from cysignals.signals cimport sig_check, sig_on, sig_off
|
270
|
+
|
271
|
+
from sage.graphs.graph_decompositions.fast_digraph cimport FastDigraph, compute_out_neighborhood_cardinality, popcount32
|
272
|
+
from libc.stdint cimport uint8_t
|
273
|
+
from sage.data_structures.binary_matrix cimport *
|
274
|
+
from sage.graphs.base.static_dense_graph cimport dense_graph_init
|
275
|
+
|
276
|
+
|
277
|
+
###############
|
278
|
+
# Lower Bound #
|
279
|
+
###############
|
280
|
+
|
281
|
+
def lower_bound(G):
|
282
|
+
r"""
|
283
|
+
Return a lower bound on the vertex separation of `G`.
|
284
|
+
|
285
|
+
INPUT:
|
286
|
+
|
287
|
+
- ``G`` -- a Graph or a DiGraph
|
288
|
+
|
289
|
+
OUTPUT:
|
290
|
+
|
291
|
+
A lower bound on the vertex separation of `D` (see the module's
|
292
|
+
documentation).
|
293
|
+
|
294
|
+
.. NOTE::
|
295
|
+
|
296
|
+
This method runs in exponential time but has no memory constraint.
|
297
|
+
|
298
|
+
EXAMPLES:
|
299
|
+
|
300
|
+
On a circuit::
|
301
|
+
|
302
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound
|
303
|
+
sage: g = digraphs.Circuit(6)
|
304
|
+
sage: lower_bound(g)
|
305
|
+
1
|
306
|
+
|
307
|
+
TESTS:
|
308
|
+
|
309
|
+
Given anything else than a Graph or a DiGraph::
|
310
|
+
|
311
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound
|
312
|
+
sage: lower_bound(range(2))
|
313
|
+
Traceback (most recent call last):
|
314
|
+
...
|
315
|
+
ValueError: the parameter must be a Graph or a DiGraph
|
316
|
+
|
317
|
+
Given a too large graph::
|
318
|
+
|
319
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import lower_bound
|
320
|
+
sage: lower_bound(graphs.PathGraph(50))
|
321
|
+
Traceback (most recent call last):
|
322
|
+
...
|
323
|
+
ValueError: the (di)graph can have at most 31 vertices
|
324
|
+
"""
|
325
|
+
from sage.graphs.graph import Graph
|
326
|
+
from sage.graphs.digraph import DiGraph
|
327
|
+
if not isinstance(G, Graph) and not isinstance(G, DiGraph):
|
328
|
+
raise ValueError("the parameter must be a Graph or a DiGraph")
|
329
|
+
|
330
|
+
if G.order() >= 32:
|
331
|
+
raise ValueError("the (di)graph can have at most 31 vertices")
|
332
|
+
|
333
|
+
cdef FastDigraph FD = FastDigraph(G)
|
334
|
+
cdef unsigned int n = <unsigned int>FD.n
|
335
|
+
|
336
|
+
# minimums[i] is means to store the value of c'_{i+1}
|
337
|
+
cdef uint8_t* minimums = <uint8_t*> check_malloc(n * sizeof(uint8_t))
|
338
|
+
cdef unsigned int i
|
339
|
+
|
340
|
+
# They are initialized to n
|
341
|
+
for i in range(n):
|
342
|
+
minimums[i] = n
|
343
|
+
|
344
|
+
cdef uint8_t tmp, tmp_count
|
345
|
+
|
346
|
+
# We go through all sets
|
347
|
+
for i in range(1, <unsigned int> (1 << n)):
|
348
|
+
tmp_count = <uint8_t> popcount32(i)
|
349
|
+
tmp = <uint8_t> compute_out_neighborhood_cardinality(FD, i)
|
350
|
+
|
351
|
+
# And update the costs
|
352
|
+
minimums[tmp_count-1] = minimum(minimums[tmp_count-1], tmp)
|
353
|
+
|
354
|
+
# We compute the maximum of all those values
|
355
|
+
for i in range(1, n):
|
356
|
+
minimums[0] = maximum(minimums[0], minimums[i])
|
357
|
+
|
358
|
+
cdef int lb = minimums[0]
|
359
|
+
|
360
|
+
sig_free(minimums)
|
361
|
+
|
362
|
+
return lb
|
363
|
+
|
364
|
+
|
365
|
+
###################################################################
|
366
|
+
# Method for turning an ordering to a path decomposition and back #
|
367
|
+
###################################################################
|
368
|
+
|
369
|
+
def linear_ordering_to_path_decomposition(G, L):
|
370
|
+
"""
|
371
|
+
Return the path decomposition encoded in the ordering L.
|
372
|
+
|
373
|
+
INPUT:
|
374
|
+
|
375
|
+
- ``G`` -- a Graph
|
376
|
+
|
377
|
+
- ``L`` -- a linear ordering for G
|
378
|
+
|
379
|
+
OUTPUT: a path graph whose vertices are the bags of the path decomposition
|
380
|
+
|
381
|
+
EXAMPLES:
|
382
|
+
|
383
|
+
The bags of an optimal path decomposition of a path-graph have two vertices
|
384
|
+
each::
|
385
|
+
|
386
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
|
387
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import linear_ordering_to_path_decomposition
|
388
|
+
sage: g = graphs.PathGraph(5)
|
389
|
+
sage: pw, L = vertex_separation(g, algorithm = "BAB"); pw
|
390
|
+
1
|
391
|
+
sage: h = linear_ordering_to_path_decomposition(g, L)
|
392
|
+
sage: sorted(h, key=str)
|
393
|
+
[{0, 1}, {1, 2}, {2, 3}, {3, 4}]
|
394
|
+
sage: sorted(h.edge_iterator(labels=None), key=str)
|
395
|
+
[({0, 1}, {1, 2}), ({1, 2}, {2, 3}), ({2, 3}, {3, 4})]
|
396
|
+
|
397
|
+
Giving a non-optimal linear ordering::
|
398
|
+
|
399
|
+
sage: g = graphs.PathGraph(5)
|
400
|
+
sage: L = [1, 4, 0, 2, 3]
|
401
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import width_of_path_decomposition
|
402
|
+
sage: width_of_path_decomposition(g, L)
|
403
|
+
3
|
404
|
+
sage: h = linear_ordering_to_path_decomposition(g, L)
|
405
|
+
sage: h.vertices(sort=True)
|
406
|
+
[{0, 2, 3, 4}, {0, 1, 2}]
|
407
|
+
|
408
|
+
The bags of the path decomposition of a cycle have three vertices each::
|
409
|
+
|
410
|
+
sage: g = graphs.CycleGraph(6)
|
411
|
+
sage: pw, L = vertex_separation(g, algorithm = "BAB"); pw
|
412
|
+
2
|
413
|
+
sage: h = linear_ordering_to_path_decomposition(g, L)
|
414
|
+
sage: sorted(h, key=str)
|
415
|
+
[{0, 1, 5}, {1, 2, 5}, {2, 3, 4}, {2, 4, 5}]
|
416
|
+
sage: sorted(h.edge_iterator(labels=None), key=str)
|
417
|
+
[({0, 1, 5}, {1, 2, 5}), ({1, 2, 5}, {2, 4, 5}), ({2, 4, 5}, {2, 3, 4})]
|
418
|
+
|
419
|
+
|
420
|
+
TESTS::
|
421
|
+
|
422
|
+
sage: linear_ordering_to_path_decomposition(Graph(), [])
|
423
|
+
Graph on 0 vertices
|
424
|
+
sage: linear_ordering_to_path_decomposition(DiGraph(), [])
|
425
|
+
Traceback (most recent call last):
|
426
|
+
...
|
427
|
+
ValueError: the first parameter must be a Graph
|
428
|
+
sage: g = graphs.CycleGraph(6)
|
429
|
+
sage: linear_ordering_to_path_decomposition(g, list(range(7)))
|
430
|
+
Traceback (most recent call last):
|
431
|
+
...
|
432
|
+
ValueError: the input linear vertex ordering L is not valid for G
|
433
|
+
"""
|
434
|
+
from sage.graphs.graph import Graph
|
435
|
+
if not isinstance(G, Graph):
|
436
|
+
raise ValueError("the first parameter must be a Graph")
|
437
|
+
if not G:
|
438
|
+
return Graph()
|
439
|
+
if not is_valid_ordering(G, L):
|
440
|
+
raise ValueError("the input linear vertex ordering L is not valid for G")
|
441
|
+
|
442
|
+
cdef set seen = set() # already treated vertices
|
443
|
+
cdef set covered = set() # vertices in the neighborhood of seen but not in seen
|
444
|
+
cdef list bags = list() # The bags of the path decomposition
|
445
|
+
|
446
|
+
# We build the bags of the path-decomposition, and avoid adding useless bags
|
447
|
+
for u in L:
|
448
|
+
seen.add(u)
|
449
|
+
covered.update(G.neighbor_iterator(u))
|
450
|
+
covered.difference_update(seen)
|
451
|
+
new_bag = covered.union([u])
|
452
|
+
if bags:
|
453
|
+
if new_bag.issubset(bags[-1]):
|
454
|
+
continue
|
455
|
+
if new_bag.issuperset(bags[-1]):
|
456
|
+
bags.pop()
|
457
|
+
|
458
|
+
bags.append(new_bag)
|
459
|
+
|
460
|
+
# We now build a graph whose vertices are bags
|
461
|
+
from sage.sets.set import Set
|
462
|
+
H = Graph()
|
463
|
+
H.add_path([Set(bag) for bag in bags])
|
464
|
+
return H
|
465
|
+
|
466
|
+
|
467
|
+
##################################################################
|
468
|
+
# Front end methods for path decomposition and vertex separation #
|
469
|
+
##################################################################
|
470
|
+
|
471
|
+
def pathwidth(self, k=None, certificate=False, algorithm='BAB', verbose=False,
|
472
|
+
max_prefix_length=20, max_prefix_number=10**6, *, solver=None):
|
473
|
+
r"""
|
474
|
+
Compute the pathwidth of ``self`` (and provides a decomposition).
|
475
|
+
|
476
|
+
INPUT:
|
477
|
+
|
478
|
+
- ``k`` -- integer (default: ``None``); the width to be considered. When
|
479
|
+
``k`` is an integer, the method checks that the graph has pathwidth
|
480
|
+
`\leq k`. If ``k`` is ``None`` (default), the method computes the optimal
|
481
|
+
pathwidth.
|
482
|
+
|
483
|
+
- ``certificate`` -- boolean (default: ``False``); whether to return the
|
484
|
+
path-decomposition itself
|
485
|
+
|
486
|
+
- ``algorithm`` -- string (default: ``'BAB'``); algorithm to use among:
|
487
|
+
|
488
|
+
- ``'BAB'`` -- use a branch-and-bound algorithm. This algorithm has no
|
489
|
+
size restriction but could take a very long time on large graphs. It can
|
490
|
+
also be used to test is the input graph has pathwidth `\leq k`, in which
|
491
|
+
cas it will return the first found solution with width `\leq k` is
|
492
|
+
``certificate==True``.
|
493
|
+
|
494
|
+
- ``exponential`` -- use an exponential time and space algorithm. This
|
495
|
+
algorithm only works of graphs on less than 32 vertices
|
496
|
+
|
497
|
+
- ``MILP`` -- use a mixed integer linear programming formulation. This
|
498
|
+
algorithm has no size restriction but could take a very long time
|
499
|
+
|
500
|
+
- ``verbose`` -- boolean (default: ``False``); whether to display
|
501
|
+
information on the computations
|
502
|
+
|
503
|
+
- ``max_prefix_length`` -- integer (default: 20); limits the length of the
|
504
|
+
stored prefixes to prevent storing too many prefixes. This parameter is
|
505
|
+
used only when ``algorithm=="BAB"``.
|
506
|
+
|
507
|
+
- ``max_prefix_number`` -- integer (default: 10**6); upper bound on the
|
508
|
+
number of stored prefixes used to prevent using too much memory. This
|
509
|
+
parameter is used only when ``algorithm=="BAB"``.
|
510
|
+
|
511
|
+
- ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
|
512
|
+
Programming (MILP) solver to be used. If set to ``None``, the default one
|
513
|
+
is used. For more information on MILP solvers and which default solver is
|
514
|
+
used, see the method :meth:`solve
|
515
|
+
<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
|
516
|
+
:class:`MixedIntegerLinearProgram
|
517
|
+
<sage.numerical.mip.MixedIntegerLinearProgram>`.
|
518
|
+
|
519
|
+
OUTPUT:
|
520
|
+
|
521
|
+
Return the pathwidth of ``self``. When ``k`` is specified, it returns
|
522
|
+
``False`` when no path-decomposition of width `\leq k` exists or ``True``
|
523
|
+
otherwise. When ``certificate=True``, the path-decomposition is also
|
524
|
+
returned.
|
525
|
+
|
526
|
+
.. SEEALSO::
|
527
|
+
|
528
|
+
* :meth:`Graph.treewidth` -- computes the treewidth of a graph
|
529
|
+
* :meth:`~sage.graphs.graph_decompositions.vertex_separation.vertex_separation`
|
530
|
+
-- computes the vertex separation of a (di)graph
|
531
|
+
|
532
|
+
EXAMPLES:
|
533
|
+
|
534
|
+
The pathwidth of a cycle is equal to 2::
|
535
|
+
|
536
|
+
sage: g = graphs.CycleGraph(6)
|
537
|
+
sage: g.pathwidth()
|
538
|
+
2
|
539
|
+
sage: pw, decomp = g.pathwidth(certificate=True)
|
540
|
+
sage: sorted(decomp, key=str)
|
541
|
+
[{0, 1, 5}, {1, 2, 5}, {2, 3, 4}, {2, 4, 5}]
|
542
|
+
|
543
|
+
The pathwidth of a Petersen graph is 5::
|
544
|
+
|
545
|
+
sage: g = graphs.PetersenGraph()
|
546
|
+
sage: g.pathwidth()
|
547
|
+
5
|
548
|
+
sage: g.pathwidth(k=2)
|
549
|
+
False
|
550
|
+
sage: g.pathwidth(k=6)
|
551
|
+
True
|
552
|
+
sage: g.pathwidth(k=6, certificate=True)
|
553
|
+
(True, Graph on 5 vertices)
|
554
|
+
|
555
|
+
TESTS:
|
556
|
+
|
557
|
+
Given anything else than a Graph::
|
558
|
+
|
559
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import pathwidth
|
560
|
+
sage: pathwidth(DiGraph())
|
561
|
+
Traceback (most recent call last):
|
562
|
+
...
|
563
|
+
ValueError: the parameter must be a Graph
|
564
|
+
|
565
|
+
Given a wrong algorithm::
|
566
|
+
|
567
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import pathwidth
|
568
|
+
sage: pathwidth(Graph(), algorithm='SuperFast')
|
569
|
+
Traceback (most recent call last):
|
570
|
+
...
|
571
|
+
ValueError: algorithm "SuperFast" has not been implemented yet, please contribute
|
572
|
+
|
573
|
+
Using a specific solver::
|
574
|
+
|
575
|
+
sage: g = graphs.PetersenGraph()
|
576
|
+
sage: g.pathwidth(solver='SCIP') # optional - pyscipopt
|
577
|
+
5
|
578
|
+
"""
|
579
|
+
from sage.graphs.graph import Graph
|
580
|
+
if not isinstance(self, Graph):
|
581
|
+
raise ValueError("the parameter must be a Graph")
|
582
|
+
|
583
|
+
pw, L = vertex_separation(self, algorithm=algorithm, verbose=verbose,
|
584
|
+
cut_off=k, upper_bound=None if k is None else (k+1),
|
585
|
+
max_prefix_length=max_prefix_length,
|
586
|
+
max_prefix_number=max_prefix_number,
|
587
|
+
solver=solver)
|
588
|
+
|
589
|
+
if k is None:
|
590
|
+
return (pw, linear_ordering_to_path_decomposition(self, L)) if certificate else pw
|
591
|
+
if pw < 0:
|
592
|
+
# no solution found
|
593
|
+
return (False, Graph()) if certificate else False
|
594
|
+
return (pw <= k, linear_ordering_to_path_decomposition(self, L)) if certificate else pw <= k
|
595
|
+
|
596
|
+
|
597
|
+
def path_decomposition(G, algorithm='BAB', cut_off=None, upper_bound=None, verbose=False,
|
598
|
+
max_prefix_length=20, max_prefix_number=10**6):
|
599
|
+
r"""
|
600
|
+
Return the pathwidth of the given graph and the ordering of the vertices
|
601
|
+
resulting in a corresponding path decomposition.
|
602
|
+
|
603
|
+
INPUT:
|
604
|
+
|
605
|
+
- ``G`` -- a Graph
|
606
|
+
|
607
|
+
- ``algorithm`` -- string (default: ``'BAB'``); algorithm to use among:
|
608
|
+
|
609
|
+
- ``'BAB'`` -- use a branch-and-bound algorithm. This algorithm has no
|
610
|
+
size restriction but could take a very long time on large graphs. It can
|
611
|
+
also be used to test is the input (di)graph has vertex separation at
|
612
|
+
most ``upper_bound`` or to return the first found solution with vertex
|
613
|
+
separation less or equal to a ``cut_off`` value.
|
614
|
+
|
615
|
+
- ``exponential`` -- use an exponential time and space algorithm. This
|
616
|
+
algorithm only works of graphs on less than 32 vertices
|
617
|
+
|
618
|
+
- ``MILP`` -- use a mixed integer linear programming formulation. This
|
619
|
+
algorithm has no size restriction but could take a very long time
|
620
|
+
|
621
|
+
- ``upper_bound`` -- integer (default: ``None``); parameter used by the
|
622
|
+
``'BAB'`` algorithm. If specified, the algorithm searches for a solution
|
623
|
+
with ``width < upper_bound``. It helps cutting branches. However, if the
|
624
|
+
given upper bound is too low, the algorithm may not be able to find a
|
625
|
+
solution.
|
626
|
+
|
627
|
+
- ``cut_off`` -- integer (default: ``None``); parameter used by the
|
628
|
+
``'BAB'`` algorithm. This bound allows us to stop the search as soon as a
|
629
|
+
solution with width at most ``cut_off`` is found, if any. If this bound
|
630
|
+
cannot be reached, the best solution found is returned, unless a too low
|
631
|
+
``upper_bound`` is given.
|
632
|
+
|
633
|
+
- ``verbose`` -- boolean (default: ``False``); whether to display
|
634
|
+
information on the computations
|
635
|
+
|
636
|
+
- ``max_prefix_length`` -- integer (default: 20); limits the length of the
|
637
|
+
stored prefixes to prevent storing too many prefixes. This parameter is
|
638
|
+
used only when ``algorithm=="BAB"``.
|
639
|
+
|
640
|
+
- ``max_prefix_number`` -- integer (default: 10**6); upper bound on the
|
641
|
+
number of stored prefixes used to prevent using too much memory. This
|
642
|
+
parameter is used only when ``algorithm=="BAB"``.
|
643
|
+
|
644
|
+
OUTPUT:
|
645
|
+
|
646
|
+
A pair ``(cost, ordering)`` representing the optimal ordering of the
|
647
|
+
vertices and its cost.
|
648
|
+
|
649
|
+
.. SEEALSO::
|
650
|
+
|
651
|
+
* :meth:`Graph.treewidth` -- computes the treewidth of a graph
|
652
|
+
|
653
|
+
EXAMPLES:
|
654
|
+
|
655
|
+
The pathwidth of a cycle is equal to 2::
|
656
|
+
|
657
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import path_decomposition
|
658
|
+
sage: g = graphs.CycleGraph(6)
|
659
|
+
sage: pw, L = path_decomposition(g, algorithm='BAB'); pw
|
660
|
+
2
|
661
|
+
sage: pw, L = path_decomposition(g, algorithm='exponential'); pw
|
662
|
+
2
|
663
|
+
sage: pw, L = path_decomposition(g, algorithm='MILP'); pw # needs sage.numerical.mip
|
664
|
+
2
|
665
|
+
|
666
|
+
TESTS:
|
667
|
+
|
668
|
+
Given anything else than a Graph::
|
669
|
+
|
670
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import path_decomposition
|
671
|
+
sage: path_decomposition(DiGraph())
|
672
|
+
Traceback (most recent call last):
|
673
|
+
...
|
674
|
+
ValueError: the parameter must be a Graph
|
675
|
+
|
676
|
+
Given a wrong algorithm::
|
677
|
+
|
678
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import path_decomposition
|
679
|
+
sage: path_decomposition(Graph(), algorithm='SuperFast')
|
680
|
+
Traceback (most recent call last):
|
681
|
+
...
|
682
|
+
ValueError: algorithm "SuperFast" has not been implemented yet, please contribute
|
683
|
+
"""
|
684
|
+
from sage.graphs.graph import Graph
|
685
|
+
if not isinstance(G, Graph):
|
686
|
+
raise ValueError("the parameter must be a Graph")
|
687
|
+
|
688
|
+
return vertex_separation(G, algorithm=algorithm, cut_off=cut_off, upper_bound=upper_bound,
|
689
|
+
verbose=verbose, max_prefix_length=max_prefix_length,
|
690
|
+
max_prefix_number=max_prefix_number)
|
691
|
+
|
692
|
+
|
693
|
+
def vertex_separation(G, algorithm='BAB', cut_off=None, upper_bound=None, verbose=False,
|
694
|
+
max_prefix_length=20, max_prefix_number=10**6,
|
695
|
+
*, solver=None, integrality_tolerance=1e-3):
|
696
|
+
r"""
|
697
|
+
Return an optimal ordering of the vertices and its cost for
|
698
|
+
vertex-separation.
|
699
|
+
|
700
|
+
INPUT:
|
701
|
+
|
702
|
+
- ``G`` -- a Graph or a DiGraph
|
703
|
+
|
704
|
+
- ``algorithm`` -- string (default: ``'BAB'``); algorithm to use among:
|
705
|
+
|
706
|
+
- ``'BAB'`` -- use a branch-and-bound algorithm. This algorithm has no
|
707
|
+
size restriction but could take a very long time on large graphs. It can
|
708
|
+
also be used to test is the input (di)graph has vertex separation at
|
709
|
+
most ``upper_bound`` or to return the first found solution with vertex
|
710
|
+
separation less or equal to a ``cut_off`` value.
|
711
|
+
|
712
|
+
- ``exponential`` -- use an exponential time and space algorithm. This
|
713
|
+
algorithm only works of graphs on less than 32 vertices
|
714
|
+
|
715
|
+
- ``MILP`` -- use a mixed integer linear programming formulation. This
|
716
|
+
algorithm has no size restriction but could take a very long time
|
717
|
+
|
718
|
+
- ``upper_bound`` -- integer (default: ``None``); parameter used by the
|
719
|
+
``'BAB'`` algorithm. If specified, the algorithm searches for a solution
|
720
|
+
with ``width < upper_bound``. It helps cutting branches. However, if the
|
721
|
+
given upper bound is too low, the algorithm may not be able to find a
|
722
|
+
solution.
|
723
|
+
|
724
|
+
- ``cut_off`` -- integer (default: ``None``); parameter used by the
|
725
|
+
``'BAB'`` algorithm. This bound allows us to stop the search as soon as a
|
726
|
+
solution with width at most ``cut_off`` is found, if any. If this bound
|
727
|
+
cannot be reached, the best solution found is returned, unless a too low
|
728
|
+
``upper_bound`` is given.
|
729
|
+
|
730
|
+
- ``verbose`` -- boolean (default: ``False``); whether to display
|
731
|
+
information on the computations
|
732
|
+
|
733
|
+
- ``max_prefix_length`` -- integer (default: 20); limits the length of the
|
734
|
+
stored prefixes to prevent storing too many prefixes. This parameter is
|
735
|
+
used only when ``algorithm=="BAB"``.
|
736
|
+
|
737
|
+
- ``max_prefix_number`` -- integer (default: 10**6); upper bound on the
|
738
|
+
number of stored prefixes used to prevent using too much memory. This
|
739
|
+
parameter is used only when ``algorithm=="BAB"``.
|
740
|
+
|
741
|
+
- ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
|
742
|
+
Programming (MILP) solver to be used. If set to ``None``, the default one
|
743
|
+
is used. For more information on MILP solvers and which default solver is
|
744
|
+
used, see the method :meth:`solve
|
745
|
+
<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
|
746
|
+
:class:`MixedIntegerLinearProgram
|
747
|
+
<sage.numerical.mip.MixedIntegerLinearProgram>`.
|
748
|
+
|
749
|
+
- ``integrality_tolerance`` -- float; parameter for use with MILP solvers
|
750
|
+
over an inexact base ring; see
|
751
|
+
:meth:`MixedIntegerLinearProgram.get_values`.
|
752
|
+
|
753
|
+
OUTPUT:
|
754
|
+
|
755
|
+
A pair ``(cost, ordering)`` representing the optimal ordering of the
|
756
|
+
vertices and its cost.
|
757
|
+
|
758
|
+
EXAMPLES:
|
759
|
+
|
760
|
+
Comparison of methods::
|
761
|
+
|
762
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
|
763
|
+
|
764
|
+
sage: # needs sage.combinat
|
765
|
+
sage: G = digraphs.DeBruijn(2,3)
|
766
|
+
sage: vs,L = vertex_separation(G, algorithm='BAB'); vs
|
767
|
+
2
|
768
|
+
sage: vs,L = vertex_separation(G, algorithm='exponential'); vs
|
769
|
+
2
|
770
|
+
sage: vs,L = vertex_separation(G, algorithm='MILP'); vs # needs sage.numerical.mip
|
771
|
+
2
|
772
|
+
|
773
|
+
sage: G = graphs.Grid2dGraph(3,3)
|
774
|
+
sage: vs,L = vertex_separation(G, algorithm='BAB'); vs
|
775
|
+
3
|
776
|
+
sage: vs,L = vertex_separation(G, algorithm='exponential'); vs
|
777
|
+
3
|
778
|
+
sage: vs,L = vertex_separation(G, algorithm='MILP'); vs # needs sage.numerical.mip
|
779
|
+
3
|
780
|
+
|
781
|
+
Digraphs with multiple strongly connected components::
|
782
|
+
|
783
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
|
784
|
+
sage: D = digraphs.Path(8)
|
785
|
+
sage: print(vertex_separation(D))
|
786
|
+
(0, [7, 6, 5, 4, 3, 2, 1, 0])
|
787
|
+
sage: D = digraphs.RandomDirectedAcyclicGraph(10, .5)
|
788
|
+
sage: vs,L = vertex_separation(D); vs
|
789
|
+
0
|
790
|
+
sage: K4 = DiGraph( graphs.CompleteGraph(4) )
|
791
|
+
sage: D = K4+K4
|
792
|
+
sage: D.add_edge(0, 4)
|
793
|
+
sage: print(vertex_separation(D))
|
794
|
+
(3, [4, 5, 6, 7, 0, 1, 2, 3])
|
795
|
+
sage: D = K4+K4+K4
|
796
|
+
sage: D.add_edge(0, 4)
|
797
|
+
sage: D.add_edge(0, 8)
|
798
|
+
sage: print(vertex_separation(D))
|
799
|
+
(3, [10, 11, 8, 9, 4, 5, 6, 7, 0, 1, 2, 3])
|
800
|
+
|
801
|
+
Using a specific MILP solver::
|
802
|
+
|
803
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
|
804
|
+
sage: G = graphs.PetersenGraph()
|
805
|
+
sage: vs, L = vertex_separation(G, algorithm='MILP', solver='SCIP'); vs # optional - pyscipopt, needs sage.numerical.mip
|
806
|
+
5
|
807
|
+
|
808
|
+
TESTS:
|
809
|
+
|
810
|
+
Given a wrong algorithm::
|
811
|
+
|
812
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
|
813
|
+
sage: vertex_separation(Graph(), algorithm='SuperFast')
|
814
|
+
Traceback (most recent call last):
|
815
|
+
...
|
816
|
+
ValueError: algorithm "SuperFast" has not been implemented yet, please contribute
|
817
|
+
|
818
|
+
Given anything else than a Graph or a DiGraph::
|
819
|
+
|
820
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
|
821
|
+
sage: vertex_separation(range(4))
|
822
|
+
Traceback (most recent call last):
|
823
|
+
...
|
824
|
+
ValueError: the parameter must be a Graph or a DiGraph
|
825
|
+
"""
|
826
|
+
from sage.graphs.graph import Graph
|
827
|
+
from sage.graphs.digraph import DiGraph
|
828
|
+
|
829
|
+
CC = []
|
830
|
+
if isinstance(G, Graph):
|
831
|
+
if not G.is_connected():
|
832
|
+
# We decompose the graph into connected components.
|
833
|
+
CC = G.connected_components()
|
834
|
+
|
835
|
+
elif isinstance(G, DiGraph):
|
836
|
+
if not G.is_strongly_connected():
|
837
|
+
# We decompose the digraph into strongly connected components and
|
838
|
+
# arrange them in the inverse order of the topological sort of the
|
839
|
+
# digraph of the strongly connected components.
|
840
|
+
scc_digraph = G.strongly_connected_components_digraph()
|
841
|
+
CC = scc_digraph.topological_sort()[::-1]
|
842
|
+
|
843
|
+
else:
|
844
|
+
raise ValueError('the parameter must be a Graph or a DiGraph')
|
845
|
+
|
846
|
+
if cut_off is None:
|
847
|
+
cut_off = 0
|
848
|
+
|
849
|
+
if CC:
|
850
|
+
# The graph has several (strongly) connected components. We solve the
|
851
|
+
# problem on each of them and order partial solutions in the same order
|
852
|
+
# than in list CC. The vertex separation is the maximum over all these
|
853
|
+
# subgraphs.
|
854
|
+
vs, L = 0, []
|
855
|
+
for V in CC:
|
856
|
+
|
857
|
+
if len(V) == 1:
|
858
|
+
# We can directly add this vertex to the solution
|
859
|
+
L.extend(V)
|
860
|
+
|
861
|
+
else:
|
862
|
+
# We build the (strongly) connected subgraph and do a recursive
|
863
|
+
# call to get its vertex separation and corresponding ordering
|
864
|
+
H = G.subgraph(V)
|
865
|
+
vsH, LH = vertex_separation(H, algorithm=algorithm,
|
866
|
+
cut_off=cut_off,
|
867
|
+
upper_bound=upper_bound,
|
868
|
+
verbose=verbose,
|
869
|
+
max_prefix_length=max_prefix_length,
|
870
|
+
max_prefix_number=max_prefix_number,
|
871
|
+
solver=solver,
|
872
|
+
integrality_tolerance=integrality_tolerance)
|
873
|
+
|
874
|
+
if vsH == -1:
|
875
|
+
# We have not been able to find a solution. This case
|
876
|
+
# happens when a too low upper bound is given.
|
877
|
+
return -1, []
|
878
|
+
|
879
|
+
# We update the vertex separation and ordering
|
880
|
+
vs = max(vs, vsH)
|
881
|
+
L.extend(LH)
|
882
|
+
|
883
|
+
# We also update the cut_off parameter that could speed up
|
884
|
+
# resolution for other components (used when algorithm=='BAB')
|
885
|
+
cut_off = max(cut_off, vs)
|
886
|
+
|
887
|
+
return vs, L
|
888
|
+
|
889
|
+
# We have a (strongly) connected graph and we call the desired algorithm
|
890
|
+
if algorithm == "exponential":
|
891
|
+
return vertex_separation_exp(G, verbose=verbose)
|
892
|
+
|
893
|
+
elif algorithm == "MILP":
|
894
|
+
return vertex_separation_MILP(G, solver=solver, verbose=verbose,
|
895
|
+
integrality_tolerance=integrality_tolerance)
|
896
|
+
|
897
|
+
elif algorithm == "BAB":
|
898
|
+
return vertex_separation_BAB(G, cut_off=cut_off, upper_bound=upper_bound, verbose=verbose,
|
899
|
+
max_prefix_length=max_prefix_length, max_prefix_number=max_prefix_number)
|
900
|
+
|
901
|
+
else:
|
902
|
+
raise ValueError('algorithm "{}" has not been implemented yet, please contribute'.format(algorithm))
|
903
|
+
|
904
|
+
|
905
|
+
################################
|
906
|
+
# Exact exponential algorithms #
|
907
|
+
################################
|
908
|
+
|
909
|
+
def vertex_separation_exp(G, verbose=False):
|
910
|
+
r"""
|
911
|
+
Return an optimal ordering of the vertices and its cost for
|
912
|
+
vertex-separation.
|
913
|
+
|
914
|
+
INPUT:
|
915
|
+
|
916
|
+
- ``G`` -- a Graph or a DiGraph
|
917
|
+
|
918
|
+
- ``verbose`` -- boolean (default: ``False``); whether to display
|
919
|
+
information on the computations
|
920
|
+
|
921
|
+
OUTPUT:
|
922
|
+
|
923
|
+
A pair ``(cost, ordering)`` representing the optimal ordering of the
|
924
|
+
vertices and its cost.
|
925
|
+
|
926
|
+
.. NOTE::
|
927
|
+
|
928
|
+
Because of its current implementation, this algorithm only works on
|
929
|
+
graphs on less than 32 vertices. This can be changed to 54 if necessary,
|
930
|
+
but 32 vertices already require 4GB of memory.
|
931
|
+
|
932
|
+
EXAMPLES:
|
933
|
+
|
934
|
+
The vertex separation of a circuit is equal to 1::
|
935
|
+
|
936
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation_exp
|
937
|
+
sage: g = digraphs.Circuit(6)
|
938
|
+
sage: vertex_separation_exp(g)
|
939
|
+
(1, [0, 1, 2, 3, 4, 5])
|
940
|
+
|
941
|
+
TESTS:
|
942
|
+
|
943
|
+
Given anything else than a Graph or a DiGraph::
|
944
|
+
|
945
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation_exp
|
946
|
+
sage: vertex_separation_exp(range(3))
|
947
|
+
Traceback (most recent call last):
|
948
|
+
...
|
949
|
+
ValueError: the parameter must be a Graph or a DiGraph
|
950
|
+
|
951
|
+
Graphs with non-integer vertices::
|
952
|
+
|
953
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation_exp
|
954
|
+
sage: D = digraphs.DeBruijn(2,3) # needs sage.combinat
|
955
|
+
sage: vertex_separation_exp(D) # needs sage.combinat
|
956
|
+
(2, ['000', '001', '100', '010', '101', '011', '110', '111'])
|
957
|
+
|
958
|
+
Given a too large graph::
|
959
|
+
|
960
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation_exp
|
961
|
+
sage: vertex_separation_exp(graphs.PathGraph(50))
|
962
|
+
Traceback (most recent call last):
|
963
|
+
...
|
964
|
+
ValueError: the graph should have at most 31 vertices
|
965
|
+
"""
|
966
|
+
from sage.graphs.graph import Graph
|
967
|
+
from sage.graphs.digraph import DiGraph
|
968
|
+
if not isinstance(G, Graph) and not isinstance(G, DiGraph):
|
969
|
+
raise ValueError("the parameter must be a Graph or a DiGraph")
|
970
|
+
|
971
|
+
if G.order() >= 32:
|
972
|
+
raise ValueError("the graph should have at most 31 vertices")
|
973
|
+
|
974
|
+
cdef FastDigraph g = FastDigraph(G)
|
975
|
+
|
976
|
+
if verbose:
|
977
|
+
print("Memory allocation")
|
978
|
+
g.print_adjacency_matrix()
|
979
|
+
|
980
|
+
cdef unsigned int mem = 1 << g.n
|
981
|
+
cdef uint8_t * neighborhoods = <uint8_t *>check_malloc(mem)
|
982
|
+
|
983
|
+
memset(neighborhoods, <uint8_t> -1, mem)
|
984
|
+
|
985
|
+
cdef int i, k
|
986
|
+
for k in range(g.n):
|
987
|
+
if verbose:
|
988
|
+
print("Looking for a strategy of cost", str(k))
|
989
|
+
|
990
|
+
sig_check()
|
991
|
+
if exists(g, neighborhoods, 0, k) <= k:
|
992
|
+
break
|
993
|
+
|
994
|
+
if verbose:
|
995
|
+
print("... Found !")
|
996
|
+
print("Now computing the ordering")
|
997
|
+
|
998
|
+
cdef list order = find_order(g, neighborhoods, k)
|
999
|
+
|
1000
|
+
sig_free(neighborhoods)
|
1001
|
+
|
1002
|
+
return k, [g.int_to_vertices[i] for i in order]
|
1003
|
+
|
1004
|
+
|
1005
|
+
###############################################################################
|
1006
|
+
# Actual algorithm, breadth-first search and updates of the costs of the sets #
|
1007
|
+
###############################################################################
|
1008
|
+
|
1009
|
+
cdef inline int exists(FastDigraph g, uint8_t* neighborhoods, int current, int cost) noexcept:
|
1010
|
+
"""
|
1011
|
+
Check whether an ordering with the given cost exists, and updates data in
|
1012
|
+
the neighborhoods array at the same time. See the module's documentation.
|
1013
|
+
"""
|
1014
|
+
# If this is true, it means the set has not been evaluated yet
|
1015
|
+
if neighborhoods[current] == <uint8_t> -1:
|
1016
|
+
neighborhoods[current] = compute_out_neighborhood_cardinality(g, current)
|
1017
|
+
|
1018
|
+
# If the cost of this set is too high, there is no point in going further.
|
1019
|
+
# Same thing if the current set is the whole vertex set.
|
1020
|
+
if neighborhoods[current] > cost or (current == (1 << g.n) - 1):
|
1021
|
+
return neighborhoods[current]
|
1022
|
+
|
1023
|
+
# Minimum of the costs of the outneighbors
|
1024
|
+
cdef int mini = g.n
|
1025
|
+
|
1026
|
+
cdef int i
|
1027
|
+
cdef int next_set
|
1028
|
+
|
1029
|
+
for i in range(g.n):
|
1030
|
+
if (current >> i) & 1:
|
1031
|
+
continue
|
1032
|
+
|
1033
|
+
# For each of the out-neighbors next_set of current
|
1034
|
+
next_set = current | 1 << i
|
1035
|
+
|
1036
|
+
# Check whether there exists a cheap path toward {1..n}, and updated the
|
1037
|
+
# cost.
|
1038
|
+
mini = minimum(mini, exists(g, neighborhoods, next_set, cost))
|
1039
|
+
|
1040
|
+
# We have found a path !
|
1041
|
+
if mini <= cost:
|
1042
|
+
return mini
|
1043
|
+
|
1044
|
+
# Updating the cost of the current set with the minimum of the cost of its
|
1045
|
+
# outneighbors.
|
1046
|
+
neighborhoods[current] = mini
|
1047
|
+
|
1048
|
+
return neighborhoods[current]
|
1049
|
+
|
1050
|
+
|
1051
|
+
cdef list find_order(FastDigraph g, uint8_t* neighborhoods, int cost):
|
1052
|
+
"""
|
1053
|
+
Return the ordering once we are sure it exists
|
1054
|
+
"""
|
1055
|
+
cdef list ordering = []
|
1056
|
+
cdef int current = 0
|
1057
|
+
cdef int n = g.n
|
1058
|
+
cdef int i
|
1059
|
+
|
1060
|
+
while n:
|
1061
|
+
# We look for n vertices
|
1062
|
+
for i in range(g.n):
|
1063
|
+
if (current >> i) & 1:
|
1064
|
+
continue
|
1065
|
+
|
1066
|
+
# Find the next set with small cost (we know it exists)
|
1067
|
+
next_set = current | 1 << i
|
1068
|
+
if neighborhoods[next_set] <= cost:
|
1069
|
+
ordering.append(i)
|
1070
|
+
current = next_set
|
1071
|
+
break
|
1072
|
+
|
1073
|
+
# One less to find
|
1074
|
+
n -= 1
|
1075
|
+
|
1076
|
+
return ordering
|
1077
|
+
|
1078
|
+
|
1079
|
+
# Min/Max functions
|
1080
|
+
|
1081
|
+
cdef inline int minimum(int a, int b) noexcept:
|
1082
|
+
if a < b:
|
1083
|
+
return a
|
1084
|
+
else:
|
1085
|
+
return b
|
1086
|
+
|
1087
|
+
|
1088
|
+
cdef inline int maximum(int a, int b) noexcept:
|
1089
|
+
if a > b:
|
1090
|
+
return a
|
1091
|
+
else:
|
1092
|
+
return b
|
1093
|
+
|
1094
|
+
|
1095
|
+
#################################################################
|
1096
|
+
# Function for testing the validity of a linear vertex ordering #
|
1097
|
+
#################################################################
|
1098
|
+
|
1099
|
+
def is_valid_ordering(G, L):
|
1100
|
+
r"""
|
1101
|
+
Test if the linear vertex ordering `L` is valid for (di)graph `G`.
|
1102
|
+
|
1103
|
+
A linear ordering `L` of the vertices of a (di)graph `G` is valid if all
|
1104
|
+
vertices of `G` are in `L`, and if `L` contains no other vertex and no
|
1105
|
+
duplicated vertices.
|
1106
|
+
|
1107
|
+
INPUT:
|
1108
|
+
|
1109
|
+
- ``G`` -- a Graph or a DiGraph
|
1110
|
+
|
1111
|
+
- ``L`` -- an ordered list of the vertices of ``G``
|
1112
|
+
|
1113
|
+
OUTPUT:
|
1114
|
+
|
1115
|
+
Returns ``True`` if `L` is a valid vertex ordering for `G`, and ``False``
|
1116
|
+
otherwise.
|
1117
|
+
|
1118
|
+
EXAMPLES:
|
1119
|
+
|
1120
|
+
Path decomposition of a cycle::
|
1121
|
+
|
1122
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1123
|
+
sage: G = graphs.CycleGraph(6)
|
1124
|
+
sage: L = G.vertices(sort=True)
|
1125
|
+
sage: vertex_separation.is_valid_ordering(G, L)
|
1126
|
+
True
|
1127
|
+
sage: vertex_separation.is_valid_ordering(G, [1,2])
|
1128
|
+
False
|
1129
|
+
|
1130
|
+
TESTS:
|
1131
|
+
|
1132
|
+
Giving anything else than a Graph or a DiGraph::
|
1133
|
+
|
1134
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1135
|
+
sage: vertex_separation.is_valid_ordering(2, [])
|
1136
|
+
Traceback (most recent call last):
|
1137
|
+
...
|
1138
|
+
ValueError: the input parameter must be a Graph or a DiGraph
|
1139
|
+
|
1140
|
+
Giving anything else than a list::
|
1141
|
+
|
1142
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1143
|
+
sage: G = graphs.CycleGraph(6)
|
1144
|
+
sage: vertex_separation.is_valid_ordering(G, {})
|
1145
|
+
Traceback (most recent call last):
|
1146
|
+
...
|
1147
|
+
ValueError: the second parameter must be of type 'list'
|
1148
|
+
"""
|
1149
|
+
from sage.graphs.graph import Graph
|
1150
|
+
from sage.graphs.digraph import DiGraph
|
1151
|
+
if not isinstance(G, Graph) and not isinstance(G, DiGraph):
|
1152
|
+
raise ValueError("the input parameter must be a Graph or a DiGraph")
|
1153
|
+
if not isinstance(L, list):
|
1154
|
+
raise ValueError("the second parameter must be of type 'list'")
|
1155
|
+
|
1156
|
+
return set(L) == set(G)
|
1157
|
+
|
1158
|
+
|
1159
|
+
####################################################################
|
1160
|
+
# Measurement functions of the widths of some graph decompositions #
|
1161
|
+
####################################################################
|
1162
|
+
|
1163
|
+
def width_of_path_decomposition(G, L):
|
1164
|
+
r"""
|
1165
|
+
Return the width of the path decomposition induced by the linear ordering
|
1166
|
+
`L` of the vertices of `G`.
|
1167
|
+
|
1168
|
+
If `G` is an instance of :mod:`Graph <sage.graphs.graph>`, this function
|
1169
|
+
returns the width `pw_L(G)` of the path decomposition induced by the linear
|
1170
|
+
ordering `L` of the vertices of `G`. If `G` is a :mod:`DiGraph
|
1171
|
+
<sage.graphs.digraph>`, it returns instead the width `vs_L(G)` of the
|
1172
|
+
directed path decomposition induced by the linear ordering `L` of the
|
1173
|
+
vertices of `G`, where
|
1174
|
+
|
1175
|
+
.. MATH::
|
1176
|
+
|
1177
|
+
vs_L(G) & = \max_{0\leq i< |V|-1} | N^+(L[:i])\setminus L[:i] |\\
|
1178
|
+
pw_L(G) & = \max_{0\leq i< |V|-1} | N(L[:i])\setminus L[:i] |\\
|
1179
|
+
|
1180
|
+
INPUT:
|
1181
|
+
|
1182
|
+
- ``G`` -- a Graph or a DiGraph
|
1183
|
+
|
1184
|
+
- ``L`` -- a linear ordering of the vertices of ``G``
|
1185
|
+
|
1186
|
+
EXAMPLES:
|
1187
|
+
|
1188
|
+
Path decomposition of a cycle::
|
1189
|
+
|
1190
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1191
|
+
sage: G = graphs.CycleGraph(6)
|
1192
|
+
sage: L = G.vertices(sort=True)
|
1193
|
+
sage: vertex_separation.width_of_path_decomposition(G, L)
|
1194
|
+
2
|
1195
|
+
|
1196
|
+
Directed path decomposition of a circuit::
|
1197
|
+
|
1198
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1199
|
+
sage: G = digraphs.Circuit(6)
|
1200
|
+
sage: L = G.vertices(sort=True)
|
1201
|
+
sage: vertex_separation.width_of_path_decomposition(G, L)
|
1202
|
+
1
|
1203
|
+
|
1204
|
+
TESTS:
|
1205
|
+
|
1206
|
+
Path decomposition of a BalancedTree::
|
1207
|
+
|
1208
|
+
sage: # needs networkx
|
1209
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1210
|
+
sage: G = graphs.BalancedTree(3,2)
|
1211
|
+
sage: pw, L = vertex_separation.path_decomposition(G)
|
1212
|
+
sage: pw == vertex_separation.width_of_path_decomposition(G, L)
|
1213
|
+
True
|
1214
|
+
sage: L.reverse()
|
1215
|
+
sage: pw == vertex_separation.width_of_path_decomposition(G, L)
|
1216
|
+
False
|
1217
|
+
|
1218
|
+
Directed path decomposition of a circuit::
|
1219
|
+
|
1220
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1221
|
+
sage: G = digraphs.Circuit(8)
|
1222
|
+
sage: vs, L = vertex_separation.vertex_separation(G)
|
1223
|
+
sage: vs == vertex_separation.width_of_path_decomposition(G, L)
|
1224
|
+
True
|
1225
|
+
sage: L = [0,4,6,3,1,5,2,7]
|
1226
|
+
sage: vs == vertex_separation.width_of_path_decomposition(G, L)
|
1227
|
+
False
|
1228
|
+
|
1229
|
+
Giving a wrong linear ordering::
|
1230
|
+
|
1231
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1232
|
+
sage: G = Graph()
|
1233
|
+
sage: vertex_separation.width_of_path_decomposition(G, ['a','b'])
|
1234
|
+
Traceback (most recent call last):
|
1235
|
+
...
|
1236
|
+
ValueError: the input linear vertex ordering L is not valid for G
|
1237
|
+
"""
|
1238
|
+
if not is_valid_ordering(G, L):
|
1239
|
+
raise ValueError("the input linear vertex ordering L is not valid for G")
|
1240
|
+
|
1241
|
+
neighbors = G.neighbors_out if G.is_directed() else G.neighbors
|
1242
|
+
|
1243
|
+
cdef int vsL = 0
|
1244
|
+
cdef set S = set()
|
1245
|
+
cdef set neighbors_of_S_in_V_minus_S = set()
|
1246
|
+
|
1247
|
+
for u in L:
|
1248
|
+
|
1249
|
+
# We remove u from the neighbors of S
|
1250
|
+
neighbors_of_S_in_V_minus_S.discard(u)
|
1251
|
+
|
1252
|
+
# We add vertex u to the set S
|
1253
|
+
S.add(u)
|
1254
|
+
|
1255
|
+
# We add the (out-)neighbors of u to the neighbors of S
|
1256
|
+
for v in neighbors(u):
|
1257
|
+
if v not in S:
|
1258
|
+
neighbors_of_S_in_V_minus_S.add(v)
|
1259
|
+
|
1260
|
+
# We update the cost of the vertex separation
|
1261
|
+
vsL = max(vsL, len(neighbors_of_S_in_V_minus_S))
|
1262
|
+
|
1263
|
+
return vsL
|
1264
|
+
|
1265
|
+
|
1266
|
+
##########################################
|
1267
|
+
# MILP formulation for vertex separation #
|
1268
|
+
##########################################
|
1269
|
+
|
1270
|
+
def _vertex_separation_MILP_formulation(G, integrality=False, solver=None):
|
1271
|
+
r"""
|
1272
|
+
MILP formulation of the vertex separation of `G` and the optimal ordering of its vertices.
|
1273
|
+
|
1274
|
+
This MILP is an improved version of the formulation proposed in [SP2010]_. See the
|
1275
|
+
:mod:`module's documentation <sage.graphs.graph_decompositions.vertex_separation>` for
|
1276
|
+
more details on this MILP formulation.
|
1277
|
+
|
1278
|
+
INPUT:
|
1279
|
+
|
1280
|
+
- ``G`` -- a Graph or a DiGraph
|
1281
|
+
|
1282
|
+
- ``integrality`` -- boolean (default: ``False``); specify if variables
|
1283
|
+
`x_v^t` and `u_v^t` must be integral or if they can be relaxed. This has
|
1284
|
+
no impact on the validity of the solution, but it is sometimes faster to
|
1285
|
+
solve the problem using binary variables only.
|
1286
|
+
|
1287
|
+
- ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
|
1288
|
+
Programming (MILP) solver to be used. If set to ``None``, the default one
|
1289
|
+
is used. For more information on MILP solvers and which default solver is
|
1290
|
+
used, see the method :meth:`solve
|
1291
|
+
<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
|
1292
|
+
:class:`MixedIntegerLinearProgram
|
1293
|
+
<sage.numerical.mip.MixedIntegerLinearProgram>`.
|
1294
|
+
|
1295
|
+
OUTPUT: the :class:`~sage.numerical.mip.MixedIntegerLinearProgram`
|
1296
|
+
|
1297
|
+
- :class:`sage.numerical.mip.MIPVariable` objects ``x``, ``u``, ``y``, ``z``.
|
1298
|
+
|
1299
|
+
EXAMPLES::
|
1300
|
+
|
1301
|
+
sage: from sage.graphs.graph_decompositions.vertex_separation import _vertex_separation_MILP_formulation
|
1302
|
+
sage: G = digraphs.DeBruijn(2,3) # needs sage.combinat
|
1303
|
+
sage: p, x, u, y, z = _vertex_separation_MILP_formulation(G) # needs sage.combinat sage.numerical.mip
|
1304
|
+
sage: p # needs sage.combinat sage.numerical.mip
|
1305
|
+
Mixed Integer Program (minimization, 193 variables, 449 constraints)
|
1306
|
+
"""
|
1307
|
+
from sage.graphs.graph import Graph
|
1308
|
+
from sage.graphs.digraph import DiGraph
|
1309
|
+
if not isinstance(G, Graph) and not isinstance(G, DiGraph):
|
1310
|
+
raise ValueError("the first input parameter must be a Graph or a DiGraph")
|
1311
|
+
|
1312
|
+
from sage.numerical.mip import MixedIntegerLinearProgram
|
1313
|
+
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
|
1314
|
+
|
1315
|
+
# Declaration of variables.
|
1316
|
+
x = p.new_variable(binary=integrality, nonnegative=True)
|
1317
|
+
u = p.new_variable(binary=integrality, nonnegative=True)
|
1318
|
+
y = p.new_variable(binary=True)
|
1319
|
+
z = p.new_variable(integer=True, nonnegative=True)
|
1320
|
+
|
1321
|
+
N = G.order()
|
1322
|
+
V = list(G)
|
1323
|
+
neighbors_out = G.neighbors_out if G.is_directed() else G.neighbors
|
1324
|
+
|
1325
|
+
# (2) x[v,t] <= x[v,t+1] for all v in V, and for t:=0..N-2
|
1326
|
+
# (3) y[v,t] <= y[v,t+1] for all v in V, and for t:=0..N-2
|
1327
|
+
for v in V:
|
1328
|
+
for t in range(N - 1):
|
1329
|
+
p.add_constraint(x[v, t] - x[v, t + 1] <= 0)
|
1330
|
+
p.add_constraint(y[v, t] - y[v, t + 1] <= 0)
|
1331
|
+
|
1332
|
+
# (4) y[v,t] <= x[w,t] for all v in V, for all w in N^+(v), and for all t:=0..N-1
|
1333
|
+
for v in V:
|
1334
|
+
for w in neighbors_out(v):
|
1335
|
+
for t in range(N):
|
1336
|
+
p.add_constraint(y[v, t] - x[w, t] <= 0)
|
1337
|
+
|
1338
|
+
# (5) sum_{v in V} y[v,t] == t+1 for t:=0..N-1
|
1339
|
+
for t in range(N):
|
1340
|
+
p.add_constraint(p.sum(y[v, t] for v in V) == t + 1)
|
1341
|
+
|
1342
|
+
# (6) u[v,t] >= x[v,t]-y[v,t] for all v in V, and for all t:=0..N-1
|
1343
|
+
for v in V:
|
1344
|
+
for t in range(N):
|
1345
|
+
p.add_constraint(x[v, t] - y[v, t] - u[v, t] <= 0)
|
1346
|
+
|
1347
|
+
# (7) z >= sum_{v in V} u[v,t] for all t:=0..N-1
|
1348
|
+
for t in range(N):
|
1349
|
+
p.add_constraint(p.sum(u[v, t] for v in V) - z['z'] <= 0)
|
1350
|
+
|
1351
|
+
# (8)(9) 0 <= x[v,t] and u[v,t] <= 1
|
1352
|
+
if not integrality:
|
1353
|
+
for v in V:
|
1354
|
+
for t in range(N):
|
1355
|
+
p.add_constraint(x[v, t], min=0, max=1)
|
1356
|
+
p.add_constraint(u[v, t], min=0, max=1)
|
1357
|
+
|
1358
|
+
# (10) y[v,t] in {0,1}
|
1359
|
+
# already declared
|
1360
|
+
|
1361
|
+
# (11) 0 <= z <= |V|
|
1362
|
+
p.add_constraint(z['z'] <= N)
|
1363
|
+
|
1364
|
+
# (1) Minimize z
|
1365
|
+
p.set_objective(z['z'])
|
1366
|
+
|
1367
|
+
return p, x, u, y, z
|
1368
|
+
|
1369
|
+
|
1370
|
+
def vertex_separation_MILP(G, integrality=False, solver=None, verbose=0,
|
1371
|
+
*, integrality_tolerance=1e-3):
|
1372
|
+
r"""
|
1373
|
+
Compute the vertex separation of `G` and the optimal ordering of its
|
1374
|
+
vertices using an MILP formulation.
|
1375
|
+
|
1376
|
+
This function uses a mixed integer linear program (MILP) for determining an
|
1377
|
+
optimal layout for the vertex separation of `G`. This MILP is an improved
|
1378
|
+
version of the formulation proposed in [SP2010]_. See the :mod:`module's
|
1379
|
+
documentation <sage.graphs.graph_decompositions.vertex_separation>` for more
|
1380
|
+
details on this MILP formulation.
|
1381
|
+
|
1382
|
+
INPUT:
|
1383
|
+
|
1384
|
+
- ``G`` -- a Graph or a DiGraph
|
1385
|
+
|
1386
|
+
- ``integrality`` -- boolean (default: ``False``); specify if variables
|
1387
|
+
`x_v^t` and `u_v^t` must be integral or if they can be relaxed. This has
|
1388
|
+
no impact on the validity of the solution, but it is sometimes faster to
|
1389
|
+
solve the problem using binary variables only.
|
1390
|
+
|
1391
|
+
- ``solver`` -- string (default: ``None``); specifies a Mixed Integer Linear
|
1392
|
+
Programming (MILP) solver to be used. If set to ``None``, the default one
|
1393
|
+
is used. For more information on MILP solvers and which default solver is
|
1394
|
+
used, see the method :meth:`solve
|
1395
|
+
<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
|
1396
|
+
:class:`MixedIntegerLinearProgram
|
1397
|
+
<sage.numerical.mip.MixedIntegerLinearProgram>`.
|
1398
|
+
|
1399
|
+
- ``verbose`` -- integer (default: 0); sets the level of verbosity. Set
|
1400
|
+
to 0 by default, which means quiet.
|
1401
|
+
|
1402
|
+
- ``integrality_tolerance`` -- float; parameter for use with MILP solvers
|
1403
|
+
over an inexact base ring; see
|
1404
|
+
:meth:`MixedIntegerLinearProgram.get_values`.
|
1405
|
+
|
1406
|
+
OUTPUT:
|
1407
|
+
|
1408
|
+
A pair ``(cost, ordering)`` representing the optimal ordering of the
|
1409
|
+
vertices and its cost.
|
1410
|
+
|
1411
|
+
EXAMPLES:
|
1412
|
+
|
1413
|
+
Vertex separation of a De Bruijn digraph::
|
1414
|
+
|
1415
|
+
sage: # needs sage.combinat
|
1416
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1417
|
+
sage: G = digraphs.DeBruijn(2,3)
|
1418
|
+
sage: vs, L = vertex_separation.vertex_separation_MILP(G); vs # needs sage.numerical.mip
|
1419
|
+
2
|
1420
|
+
sage: vs == vertex_separation.width_of_path_decomposition(G, L) # needs sage.numerical.mip
|
1421
|
+
True
|
1422
|
+
sage: vse, Le = vertex_separation.vertex_separation(G); vse
|
1423
|
+
2
|
1424
|
+
|
1425
|
+
The vertex separation of a circuit is 1::
|
1426
|
+
|
1427
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1428
|
+
sage: G = digraphs.Circuit(6)
|
1429
|
+
sage: vs, L = vertex_separation.vertex_separation_MILP(G); vs # needs sage.numerical.mip
|
1430
|
+
1
|
1431
|
+
|
1432
|
+
TESTS:
|
1433
|
+
|
1434
|
+
Comparison with exponential algorithm::
|
1435
|
+
|
1436
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1437
|
+
sage: for i in range(10): # needs sage.numerical.mip
|
1438
|
+
....: G = digraphs.RandomDirectedGNP(10, 0.2)
|
1439
|
+
....: ve, le = vertex_separation.vertex_separation(G)
|
1440
|
+
....: vm, lm = vertex_separation.vertex_separation_MILP(G)
|
1441
|
+
....: if ve != vm:
|
1442
|
+
....: raise ValueError("the solution is not optimal")
|
1443
|
+
|
1444
|
+
Comparison with different values of the integrality parameter::
|
1445
|
+
|
1446
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1447
|
+
sage: for i in range(10): # long time (11s on sage.math, 2012), needs sage.numerical.mip
|
1448
|
+
....: G = digraphs.RandomDirectedGNP(10, 0.2)
|
1449
|
+
....: va, la = vertex_separation.vertex_separation_MILP(G, integrality=False)
|
1450
|
+
....: vb, lb = vertex_separation.vertex_separation_MILP(G, integrality=True)
|
1451
|
+
....: if va != vb:
|
1452
|
+
....: raise ValueError("the integrality parameter changes the result")
|
1453
|
+
|
1454
|
+
Giving anything else than a Graph or a DiGraph::
|
1455
|
+
|
1456
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation
|
1457
|
+
sage: vertex_separation.vertex_separation_MILP([]) # needs sage.numerical.mip
|
1458
|
+
Traceback (most recent call last):
|
1459
|
+
...
|
1460
|
+
ValueError: the first input parameter must be a Graph or a DiGraph
|
1461
|
+
"""
|
1462
|
+
from sage.numerical.mip import MIPSolverException
|
1463
|
+
|
1464
|
+
p, _, _, y, z = _vertex_separation_MILP_formulation(G, integrality=integrality, solver=solver)
|
1465
|
+
N = G.order()
|
1466
|
+
V = list(G)
|
1467
|
+
|
1468
|
+
try:
|
1469
|
+
_ = p.solve(log=verbose)
|
1470
|
+
except MIPSolverException:
|
1471
|
+
if integrality:
|
1472
|
+
raise ValueError("unbounded or unexpected error")
|
1473
|
+
else:
|
1474
|
+
raise ValueError("unbounded or unexpected error, try with 'integrality = True'")
|
1475
|
+
|
1476
|
+
taby = p.get_values(y, convert=bool, tolerance=integrality_tolerance)
|
1477
|
+
vs = p.get_values(z, convert=True, tolerance=integrality_tolerance)['z']
|
1478
|
+
# since exactly one vertex is processed per step, we can reconstruct the sequence
|
1479
|
+
seq = []
|
1480
|
+
seen = set()
|
1481
|
+
for t in range(N):
|
1482
|
+
for v in V:
|
1483
|
+
if taby[v, t] and v not in seen:
|
1484
|
+
seq.append(v)
|
1485
|
+
seen.add(v)
|
1486
|
+
break
|
1487
|
+
|
1488
|
+
return vs, seq
|
1489
|
+
|
1490
|
+
|
1491
|
+
##########################################
|
1492
|
+
# Branch and Bound for vertex separation #
|
1493
|
+
##########################################
|
1494
|
+
|
1495
|
+
def vertex_separation_BAB(G,
|
1496
|
+
cut_off=None,
|
1497
|
+
upper_bound=None,
|
1498
|
+
max_prefix_length=20,
|
1499
|
+
max_prefix_number=10**6,
|
1500
|
+
verbose=False):
|
1501
|
+
r"""
|
1502
|
+
Branch and Bound algorithm for the vertex separation.
|
1503
|
+
|
1504
|
+
This method implements the branch and bound algorithm for the vertex
|
1505
|
+
separation of directed graphs and the pathwidth of undirected graphs
|
1506
|
+
proposed in [CMN2014]_. The implementation is valid for both Graph and
|
1507
|
+
DiGraph. See the documentation of the
|
1508
|
+
:mod:`~sage.graphs.graph_decompositions.vertex_separation` module.
|
1509
|
+
|
1510
|
+
INPUT:
|
1511
|
+
|
1512
|
+
- ``G`` -- a Graph or a DiGraph
|
1513
|
+
|
1514
|
+
- ``cut_off`` -- integer (default: ``None``); bound to consider in the
|
1515
|
+
branch and bound algorithm. This allows us to stop the search as soon as a
|
1516
|
+
solution with width at most ``cut_off`` is found, if any. If this bound
|
1517
|
+
cannot be reached, the best solution found is returned, unless a too low
|
1518
|
+
``upper_bound`` is given.
|
1519
|
+
|
1520
|
+
- ``upper_bound`` -- integer (default: ``None``); if specified, the
|
1521
|
+
algorithm searches for a solution with ``width < upper_bound``. It helps
|
1522
|
+
cutting branches. However, if the given upper bound is too low, the
|
1523
|
+
algorithm may not be able to find a solution.
|
1524
|
+
|
1525
|
+
- ``max_prefix_length`` -- integer (default: 20); limits the length of the
|
1526
|
+
stored prefixes to prevent storing too many prefixes
|
1527
|
+
|
1528
|
+
- ``max_prefix_number`` -- integer (default: 10**6); upper bound on the
|
1529
|
+
number of stored prefixes used to prevent using too much memory
|
1530
|
+
|
1531
|
+
- ``verbose`` -- boolean (default: ``False``); display some information when
|
1532
|
+
set to ``True``
|
1533
|
+
|
1534
|
+
OUTPUT: ``width`` -- the computed vertex separation
|
1535
|
+
|
1536
|
+
- ``seq`` -- an ordering of the vertices of width ``width``
|
1537
|
+
|
1538
|
+
EXAMPLES:
|
1539
|
+
|
1540
|
+
The algorithm is valid for the vertex separation::
|
1541
|
+
|
1542
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
|
1543
|
+
sage: D = digraphs.RandomDirectedGNP(15, .2)
|
1544
|
+
sage: vb, seqb = VS.vertex_separation_BAB(D)
|
1545
|
+
sage: vd, seqd = VS.vertex_separation_exp(D)
|
1546
|
+
sage: vb == vd
|
1547
|
+
True
|
1548
|
+
sage: vb == VS.width_of_path_decomposition(D, seqb)
|
1549
|
+
True
|
1550
|
+
|
1551
|
+
The vertex separation of a `N\times N` grid is `N`::
|
1552
|
+
|
1553
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
|
1554
|
+
sage: G = graphs.Grid2dGraph(4,4)
|
1555
|
+
sage: vs, seq = VS.vertex_separation_BAB(G); vs
|
1556
|
+
4
|
1557
|
+
sage: vs == VS.width_of_path_decomposition(G, seq)
|
1558
|
+
True
|
1559
|
+
|
1560
|
+
The vertex separation of a `N\times M` grid with `N<M` is `N`::
|
1561
|
+
|
1562
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
|
1563
|
+
sage: G = graphs.Grid2dGraph(3,5)
|
1564
|
+
sage: vs, seq = VS.vertex_separation_BAB(G); vs
|
1565
|
+
3
|
1566
|
+
sage: vs == VS.width_of_path_decomposition(G, seq)
|
1567
|
+
True
|
1568
|
+
|
1569
|
+
The vertex separation of circuit of order `N\geq 2` is 1::
|
1570
|
+
|
1571
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
|
1572
|
+
sage: D = digraphs.Circuit(10)
|
1573
|
+
sage: vs, seq = VS.vertex_separation_BAB(D); vs
|
1574
|
+
1
|
1575
|
+
sage: vs == VS.width_of_path_decomposition(D, seq)
|
1576
|
+
True
|
1577
|
+
|
1578
|
+
The vertex separation of cycle of order `N\geq 3` is 2::
|
1579
|
+
|
1580
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
|
1581
|
+
sage: G = graphs.CycleGraph(10)
|
1582
|
+
sage: vs, seq = VS.vertex_separation_BAB(G); vs
|
1583
|
+
2
|
1584
|
+
|
1585
|
+
The vertex separation of ``MycielskiGraph(5)`` is 10::
|
1586
|
+
|
1587
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
|
1588
|
+
sage: G = graphs.MycielskiGraph(5)
|
1589
|
+
sage: vs, seq = VS.vertex_separation_BAB(G); vs
|
1590
|
+
10
|
1591
|
+
|
1592
|
+
Searching for any solution with width less or equal to ``cut_off``::
|
1593
|
+
|
1594
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
|
1595
|
+
sage: G = graphs.MycielskiGraph(5)
|
1596
|
+
sage: VS.vertex_separation_BAB(G, cut_off=11)[0] <= 11
|
1597
|
+
True
|
1598
|
+
sage: VS.vertex_separation_BAB(G, cut_off=10)[0] <= 10
|
1599
|
+
True
|
1600
|
+
sage: VS.vertex_separation_BAB(G, cut_off=9)[0] <= 9
|
1601
|
+
False
|
1602
|
+
|
1603
|
+
Testing for the existence of a solution with width strictly less than ``upper_bound``::
|
1604
|
+
|
1605
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
|
1606
|
+
sage: G = graphs.MycielskiGraph(5)
|
1607
|
+
sage: vs, seq = VS.vertex_separation_BAB(G, upper_bound=11); vs
|
1608
|
+
10
|
1609
|
+
sage: vs, seq = VS.vertex_separation_BAB(G, upper_bound=10); vs
|
1610
|
+
-1
|
1611
|
+
sage: vs, seq = VS.vertex_separation_BAB(G, cut_off=11, upper_bound=10); vs
|
1612
|
+
-1
|
1613
|
+
|
1614
|
+
Changing the parameters of the prefix storage::
|
1615
|
+
|
1616
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
|
1617
|
+
sage: G = graphs.MycielskiGraph(5)
|
1618
|
+
sage: vs, seq = VS.vertex_separation_BAB(G, max_prefix_length=0); vs
|
1619
|
+
10
|
1620
|
+
sage: vs, seq = VS.vertex_separation_BAB(G, max_prefix_number=5); vs
|
1621
|
+
10
|
1622
|
+
sage: vs, seq = VS.vertex_separation_BAB(G, max_prefix_number=0); vs
|
1623
|
+
10
|
1624
|
+
|
1625
|
+
TESTS:
|
1626
|
+
|
1627
|
+
Giving anything else than a Graph or a DiGraph::
|
1628
|
+
|
1629
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
|
1630
|
+
sage: VS.vertex_separation_BAB(range(5))
|
1631
|
+
Traceback (most recent call last):
|
1632
|
+
...
|
1633
|
+
ValueError: the input parameter must be a Graph or a DiGraph
|
1634
|
+
|
1635
|
+
Giving an empty Graph or DiGraph::
|
1636
|
+
|
1637
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
|
1638
|
+
sage: VS.vertex_separation_BAB(Graph())
|
1639
|
+
(0, [])
|
1640
|
+
sage: VS.vertex_separation_BAB(DiGraph())
|
1641
|
+
(0, [])
|
1642
|
+
|
1643
|
+
Giving a too low upper bound::
|
1644
|
+
|
1645
|
+
sage: from sage.graphs.graph_decompositions import vertex_separation as VS
|
1646
|
+
sage: VS.vertex_separation_BAB(digraphs.Circuit(3), upper_bound=0)
|
1647
|
+
Traceback (most recent call last):
|
1648
|
+
...
|
1649
|
+
ValueError: the input upper bound must be at least 1
|
1650
|
+
"""
|
1651
|
+
from sage.graphs.graph import Graph
|
1652
|
+
from sage.graphs.digraph import DiGraph
|
1653
|
+
if not isinstance(G, DiGraph) and not isinstance(G, Graph):
|
1654
|
+
raise ValueError("the input parameter must be a Graph or a DiGraph")
|
1655
|
+
|
1656
|
+
cdef int n = G.order()
|
1657
|
+
if not n:
|
1658
|
+
return 0, []
|
1659
|
+
|
1660
|
+
cut_off = 0 if cut_off is None else cut_off
|
1661
|
+
upper_bound = n if upper_bound is None else upper_bound
|
1662
|
+
if upper_bound < 1:
|
1663
|
+
raise ValueError("the input upper bound must be at least 1")
|
1664
|
+
|
1665
|
+
# ==> Allocate and initialize some data structures
|
1666
|
+
|
1667
|
+
# We use a binary matrix to store the (di)graph. This way the neighborhood
|
1668
|
+
# of a vertex is stored in one bitset.
|
1669
|
+
cdef binary_matrix_t H
|
1670
|
+
cdef int i
|
1671
|
+
cdef list int_to_vertex = list(G)
|
1672
|
+
cdef dict vertex_to_int = {v: i for i, v in enumerate(int_to_vertex)}
|
1673
|
+
dense_graph_init(H, G, translation=vertex_to_int)
|
1674
|
+
|
1675
|
+
# We need 2 bitsets here + 3 per call to vertex_separation_BAB_C, so overall
|
1676
|
+
# 3*n + 2. We use another binary matrix as a pool of bitsets.
|
1677
|
+
cdef binary_matrix_t bm_pool
|
1678
|
+
binary_matrix_init(bm_pool, 3 * n + 2, n)
|
1679
|
+
|
1680
|
+
cdef int * prefix = <int *>sig_malloc(n * sizeof(int))
|
1681
|
+
cdef int * positions = <int *>sig_malloc(n * sizeof(int))
|
1682
|
+
if not prefix or not positions:
|
1683
|
+
sig_free(prefix)
|
1684
|
+
sig_free(positions)
|
1685
|
+
binary_matrix_free(H)
|
1686
|
+
binary_matrix_free(bm_pool)
|
1687
|
+
raise MemoryError("unable to allocate data structures")
|
1688
|
+
|
1689
|
+
cdef list best_seq = list(range(n))
|
1690
|
+
for i in range(n):
|
1691
|
+
prefix[i] = i
|
1692
|
+
positions[i] = i
|
1693
|
+
|
1694
|
+
cdef int width = upper_bound
|
1695
|
+
cdef list order = []
|
1696
|
+
cdef set prefix_storage = set()
|
1697
|
+
|
1698
|
+
try:
|
1699
|
+
# ==> Call the cython method
|
1700
|
+
sig_on()
|
1701
|
+
width = vertex_separation_BAB_C(H=H,
|
1702
|
+
n=n,
|
1703
|
+
prefix=prefix,
|
1704
|
+
positions=positions,
|
1705
|
+
best_seq=best_seq,
|
1706
|
+
level=0,
|
1707
|
+
b_prefix=bm_pool.rows[3 * n],
|
1708
|
+
b_prefix_and_neighborhood=bm_pool.rows[3 * n + 1],
|
1709
|
+
cut_off=cut_off,
|
1710
|
+
upper_bound=upper_bound,
|
1711
|
+
current_cost=0,
|
1712
|
+
bm_pool=bm_pool,
|
1713
|
+
prefix_storage=prefix_storage,
|
1714
|
+
max_prefix_length=max_prefix_length,
|
1715
|
+
max_prefix_number=max_prefix_number,
|
1716
|
+
verbose=verbose)
|
1717
|
+
|
1718
|
+
sig_off()
|
1719
|
+
|
1720
|
+
# ==> Build the final ordering
|
1721
|
+
order = [int_to_vertex[best_seq[i]] for i in range(n)]
|
1722
|
+
|
1723
|
+
finally:
|
1724
|
+
if verbose:
|
1725
|
+
print('Stored prefixes: {}'.format(len(prefix_storage)))
|
1726
|
+
sig_free(prefix)
|
1727
|
+
sig_free(positions)
|
1728
|
+
binary_matrix_free(H)
|
1729
|
+
binary_matrix_free(bm_pool)
|
1730
|
+
|
1731
|
+
return (width if width < upper_bound else -1), order
|
1732
|
+
|
1733
|
+
|
1734
|
+
cdef inline _my_invert_positions(int *prefix, int *positions, int pos_a, int pos_b):
|
1735
|
+
"""
|
1736
|
+
Permute vertices at positions ``pos_a`` and ``pos_b`` in array ``prefix``,
|
1737
|
+
and record the new positions in array ``positions``.
|
1738
|
+
"""
|
1739
|
+
if pos_a != pos_b:
|
1740
|
+
positions[prefix[pos_a]], positions[prefix[pos_b]] = positions[prefix[pos_b]], positions[prefix[pos_a]]
|
1741
|
+
prefix[pos_a], prefix[pos_b] = prefix[pos_b], prefix[pos_a]
|
1742
|
+
|
1743
|
+
|
1744
|
+
cdef int vertex_separation_BAB_C(binary_matrix_t H,
|
1745
|
+
int n,
|
1746
|
+
int *prefix,
|
1747
|
+
int *positions,
|
1748
|
+
list best_seq,
|
1749
|
+
int level,
|
1750
|
+
bitset_t b_prefix,
|
1751
|
+
bitset_t b_prefix_and_neighborhood,
|
1752
|
+
int cut_off,
|
1753
|
+
int upper_bound,
|
1754
|
+
int current_cost,
|
1755
|
+
binary_matrix_t bm_pool,
|
1756
|
+
set prefix_storage,
|
1757
|
+
int max_prefix_length,
|
1758
|
+
int max_prefix_number,
|
1759
|
+
bint verbose) noexcept:
|
1760
|
+
r"""
|
1761
|
+
Branch and Bound algorithm for the process number and the vertex separation.
|
1762
|
+
|
1763
|
+
INPUT:
|
1764
|
+
|
1765
|
+
- ``H`` -- a binary matrix storing the adjacency of the (di)graph
|
1766
|
+
|
1767
|
+
- ``n`` -- integer; the number of vertices of the (di)graph
|
1768
|
+
|
1769
|
+
- ``prefix`` -- array of `n` integers; contains a permutation of the
|
1770
|
+
vertices. The vertices forming the current prefix under consideration are
|
1771
|
+
stored in cells ``[0, level - 1]``.
|
1772
|
+
|
1773
|
+
- ``positions`` -- array of `n` integers; associates to each vertex its
|
1774
|
+
index in array ``prefix``
|
1775
|
+
|
1776
|
+
- ``best_seq`` -- array of `n` integers; stores the best ordering found so
|
1777
|
+
far
|
1778
|
+
|
1779
|
+
- ``level`` -- integer; specifies the length of the current prefix
|
1780
|
+
|
1781
|
+
- ``b_prefix`` -- bitset of size `n`; records the vertices in the current
|
1782
|
+
prefix (in cells ``[0,level-1]``)
|
1783
|
+
|
1784
|
+
- ``b_prefix_and_neighborhood`` -- bitset of size `n`; records the
|
1785
|
+
vertices in the current prefix and the vertices in its neighborhood
|
1786
|
+
|
1787
|
+
- ``cut_off`` -- integer; bound to consider in the branch and bound
|
1788
|
+
algorithm. This allows us to stop the search as soon as a solution with
|
1789
|
+
width at most ``cut_off`` is found, if any.
|
1790
|
+
|
1791
|
+
- ``upper_bound`` -- integer; the algorithm searches for a solution with
|
1792
|
+
``width < upper_bound``. It helps cutting branches. Each time a new
|
1793
|
+
solution is found, the upper bound is reduced.
|
1794
|
+
|
1795
|
+
- ``bm_pool`` -- a binary matrix with `3*n+2` rows of size `n`; each rows is
|
1796
|
+
a bitset of size `n`. This data structure is used as a pool of
|
1797
|
+
initialized bitsets. Each call of this method needs 3 bitsets for local
|
1798
|
+
operations, so it uses rows ``[3 * level, 3 * level + 2]``.
|
1799
|
+
|
1800
|
+
- ``prefix_storage`` -- set; used to store prefixes
|
1801
|
+
|
1802
|
+
- ``max_prefix_length`` -- integer; maximum length of the stored prefixes to
|
1803
|
+
prevent storing too many prefixes
|
1804
|
+
|
1805
|
+
- ``max_prefix_number`` -- integer; upper bound on the number of stored
|
1806
|
+
prefixes used to prevent using too much memory
|
1807
|
+
|
1808
|
+
- ``verbose`` -- boolean (default: ``False``); display some information when
|
1809
|
+
set to ``True``
|
1810
|
+
"""
|
1811
|
+
cdef int i
|
1812
|
+
|
1813
|
+
# ==> Test termination
|
1814
|
+
|
1815
|
+
if level == n:
|
1816
|
+
if current_cost < upper_bound:
|
1817
|
+
for i in range(n):
|
1818
|
+
best_seq[i] = prefix[i]
|
1819
|
+
if verbose:
|
1820
|
+
print("New upper bound: {}".format(current_cost))
|
1821
|
+
|
1822
|
+
return current_cost
|
1823
|
+
|
1824
|
+
cdef int delta_i, j, v, select_it
|
1825
|
+
cdef list delta = list()
|
1826
|
+
cdef int loc_level = level
|
1827
|
+
|
1828
|
+
# ==> Allocate local data structures
|
1829
|
+
|
1830
|
+
cdef bitset_s *loc_b_prefix = bm_pool.rows[3 * level]
|
1831
|
+
cdef bitset_s *loc_b_pref_and_neigh = bm_pool.rows[3 * level + 1]
|
1832
|
+
cdef bitset_s *b_tmp = bm_pool.rows[3 * level + 2]
|
1833
|
+
bitset_copy(loc_b_prefix, b_prefix)
|
1834
|
+
bitset_copy(loc_b_pref_and_neigh, b_prefix_and_neighborhood)
|
1835
|
+
|
1836
|
+
# ==> Greedy steps
|
1837
|
+
#
|
1838
|
+
# We extend the current prefix with all vertices u such that either
|
1839
|
+
# (i) All out-neighbors of u are in the prefix or in its out-neighborhood
|
1840
|
+
# (ii) or u is an out-neighbor of the prefix and all but one of its
|
1841
|
+
# out-neighbors are in the prefix or in its out-neighborhood.
|
1842
|
+
|
1843
|
+
select_it = 0
|
1844
|
+
i = loc_level
|
1845
|
+
while i < n:
|
1846
|
+
|
1847
|
+
j = prefix[i]
|
1848
|
+
|
1849
|
+
if bitset_issubset(H.rows[j], loc_b_pref_and_neigh):
|
1850
|
+
# (i) Vertex j is such that all its out-neighbors are in the prefix
|
1851
|
+
# or in its out-neighborhood (so in loc_b_pref_and_neigh).
|
1852
|
+
bitset_add(loc_b_pref_and_neigh, j)
|
1853
|
+
select_it = 1
|
1854
|
+
|
1855
|
+
elif bitset_in(loc_b_pref_and_neigh, j) and not bitset_in(loc_b_prefix, j):
|
1856
|
+
bitset_difference(b_tmp, H.rows[j], loc_b_pref_and_neigh)
|
1857
|
+
if bitset_len(b_tmp) == 1:
|
1858
|
+
# (ii) Vertex j is an out-neighbor of the prefix and all but one
|
1859
|
+
# of its out-neighbors are in the prefix or in its
|
1860
|
+
# out-neighborhood.
|
1861
|
+
v = bitset_first(b_tmp)
|
1862
|
+
bitset_add(loc_b_pref_and_neigh, v)
|
1863
|
+
select_it = 1
|
1864
|
+
|
1865
|
+
if select_it:
|
1866
|
+
# We add j to the prefix and update neighborhoods
|
1867
|
+
_my_invert_positions(prefix, positions, i, loc_level)
|
1868
|
+
loc_level += 1
|
1869
|
+
bitset_add(loc_b_prefix, j)
|
1870
|
+
select_it = 0
|
1871
|
+
# We search for vertices that can now be selected
|
1872
|
+
i = loc_level
|
1873
|
+
else:
|
1874
|
+
i += 1
|
1875
|
+
|
1876
|
+
# ==> Test termination
|
1877
|
+
#
|
1878
|
+
if loc_level == n:
|
1879
|
+
if current_cost < upper_bound:
|
1880
|
+
for i in range(n):
|
1881
|
+
best_seq[i] = prefix[i]
|
1882
|
+
if verbose:
|
1883
|
+
print("New upper bound: {}".format(current_cost))
|
1884
|
+
|
1885
|
+
return current_cost
|
1886
|
+
|
1887
|
+
# ==> Test if the prefix is in prefix_storage
|
1888
|
+
#
|
1889
|
+
# The set S of vertices of a prefix P is in prefix_storage if the branch
|
1890
|
+
# with prefix P is such that c(P)<\min_{L\in{\cal L}_P(V)} c(L). In such
|
1891
|
+
# case, there is no need to continue exploration for the current branch.
|
1892
|
+
cdef frozenset frozen_prefix
|
1893
|
+
|
1894
|
+
if loc_level <= max_prefix_length:
|
1895
|
+
frozen_prefix = frozenset(prefix[i] for i in range(loc_level))
|
1896
|
+
if frozen_prefix in prefix_storage:
|
1897
|
+
return upper_bound
|
1898
|
+
|
1899
|
+
# ==> Sort and Prune
|
1900
|
+
#
|
1901
|
+
# We compute for each remaining vertex v a lower bound on the width of any
|
1902
|
+
# ordering with prefix prefix+v
|
1903
|
+
for i in range(loc_level, n):
|
1904
|
+
j = prefix[i]
|
1905
|
+
bitset_union(b_tmp, loc_b_pref_and_neigh, H.rows[j])
|
1906
|
+
bitset_difference(b_tmp, b_tmp, loc_b_prefix)
|
1907
|
+
bitset_discard(b_tmp, j)
|
1908
|
+
delta_i = bitset_len(b_tmp)
|
1909
|
+
if delta_i < upper_bound:
|
1910
|
+
delta.append((delta_i, j))
|
1911
|
+
|
1912
|
+
delta.sort()
|
1913
|
+
|
1914
|
+
# ==> Recursion
|
1915
|
+
for delta_i, i in delta:
|
1916
|
+
|
1917
|
+
delta_i = max(current_cost, delta_i)
|
1918
|
+
|
1919
|
+
if delta_i >= upper_bound:
|
1920
|
+
break
|
1921
|
+
|
1922
|
+
# We extend the current prefix with vertex i and explore the branch
|
1923
|
+
bitset_union(b_tmp, loc_b_pref_and_neigh, H.rows[i])
|
1924
|
+
bitset_discard(b_tmp, i)
|
1925
|
+
_my_invert_positions(prefix, positions, positions[i], loc_level)
|
1926
|
+
bitset_add(loc_b_prefix, i)
|
1927
|
+
|
1928
|
+
cost_i = vertex_separation_BAB_C(H=H,
|
1929
|
+
n=n,
|
1930
|
+
prefix=prefix,
|
1931
|
+
positions=positions,
|
1932
|
+
best_seq=best_seq,
|
1933
|
+
level=loc_level + 1,
|
1934
|
+
b_prefix=loc_b_prefix,
|
1935
|
+
b_prefix_and_neighborhood=b_tmp,
|
1936
|
+
cut_off=cut_off,
|
1937
|
+
upper_bound=upper_bound,
|
1938
|
+
current_cost=delta_i,
|
1939
|
+
bm_pool=bm_pool,
|
1940
|
+
prefix_storage=prefix_storage,
|
1941
|
+
max_prefix_length=max_prefix_length,
|
1942
|
+
max_prefix_number=max_prefix_number,
|
1943
|
+
verbose=verbose)
|
1944
|
+
|
1945
|
+
bitset_discard(loc_b_prefix, i)
|
1946
|
+
|
1947
|
+
if cost_i < upper_bound:
|
1948
|
+
upper_bound = cost_i
|
1949
|
+
if upper_bound <= cut_off:
|
1950
|
+
# We are satisfied with current solution.
|
1951
|
+
break
|
1952
|
+
|
1953
|
+
# ==> Update prefix_storage
|
1954
|
+
#
|
1955
|
+
# If the prefix P is such that c(P)<\min_{L\in{\cal L}_P(V)} c(L), no other
|
1956
|
+
# prefix P' on the same set S=V(P) of vertices can lead to a better
|
1957
|
+
# solution.
|
1958
|
+
if (loc_level <= max_prefix_length
|
1959
|
+
and current_cost < upper_bound
|
1960
|
+
and len(prefix_storage) < max_prefix_number):
|
1961
|
+
prefix_storage.add(frozen_prefix)
|
1962
|
+
|
1963
|
+
return upper_bound
|