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,1704 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
# cython: binding=True
|
3
|
+
r"""
|
4
|
+
Hyperbolicity
|
5
|
+
|
6
|
+
**Definition** :
|
7
|
+
|
8
|
+
The hyperbolicity `\delta` of a graph `G` has been defined by Gromov
|
9
|
+
[Gro1987]_ as follows (we give here the so-called 4-points condition):
|
10
|
+
|
11
|
+
Let `a, b, c, d` be vertices of the graph, let `S_1`, `S_2` and `S_3` be
|
12
|
+
defined by
|
13
|
+
|
14
|
+
.. MATH::
|
15
|
+
|
16
|
+
S_1 = dist(a, b) + dist(d, c)\\
|
17
|
+
S_2 = dist(a, c) + dist(b, d)\\
|
18
|
+
S_3 = dist(a, d) + dist(b, c)\\
|
19
|
+
|
20
|
+
and let `M_1` and `M_2` be the two largest values among `S_1`, `S_2`, and
|
21
|
+
`S_3`. We define `hyp(a, b, c, d) = M_1 - M_2`, and the hyperbolicity
|
22
|
+
`\delta(G)` of the graph is the maximum of `hyp` over all possible
|
23
|
+
4-tuples `(a, b, c, d)` divided by 2. That is, the graph is said
|
24
|
+
`\delta`-hyperbolic when
|
25
|
+
|
26
|
+
.. MATH::
|
27
|
+
|
28
|
+
\delta(G) = \frac{1}{2}\max_{a,b,c,d\in V(G)}hyp(a, b, c, d)
|
29
|
+
|
30
|
+
(note that `hyp(a, b, c, d)=0` whenever two elements among `a,b,c,d` are
|
31
|
+
equal)
|
32
|
+
|
33
|
+
**Some known results** :
|
34
|
+
|
35
|
+
- Trees and cliques are `0`-hyperbolic
|
36
|
+
|
37
|
+
- `n\times n` grids are `n-1`-hyperbolic
|
38
|
+
|
39
|
+
- Cycles are approximately `n/4`-hyperbolic
|
40
|
+
|
41
|
+
- Chordal graphs are `\leq 1`-hyperbolic
|
42
|
+
|
43
|
+
Besides, the hyperbolicity of a graph is the maximum over all its
|
44
|
+
biconnected components.
|
45
|
+
|
46
|
+
**Algorithms and complexity** :
|
47
|
+
|
48
|
+
The time complexity of the naive implementation (i.e. testing all 4-tuples)
|
49
|
+
is `O( n^4 )`, and an algorithm with time complexity `O(n^{3.69})` has been
|
50
|
+
proposed in [FIV2012]_. This remains very long for large-scale graphs, and
|
51
|
+
much harder to implement.
|
52
|
+
|
53
|
+
Several improvements over the naive algorithm have been proposed and are
|
54
|
+
implemented in the current module.
|
55
|
+
|
56
|
+
- Another upper bound on `hyp(a, b, c, d)` has been proved in [CCL2015]_. It
|
57
|
+
is used to design an algorithm with worse case time complexity in
|
58
|
+
`O(n^4)` but that behaves much better in practice.
|
59
|
+
|
60
|
+
Assume that `S_1 = dist(a, b) + dist(c, d)` is the largest sum among
|
61
|
+
`S_1,S_2,S_3`. We have
|
62
|
+
|
63
|
+
.. MATH::
|
64
|
+
|
65
|
+
S_2 + S_3 =& dist(a, c) + dist(b, d) + dist(a, d) + dist(b, c)\\
|
66
|
+
=& [ dist(a, c) + dist(b, c) ] + [ dist(a, d) + dist(b, d)]\\
|
67
|
+
\geq &dist(a,b) + dist(a,b)\\
|
68
|
+
\geq &2dist(a,b)\\
|
69
|
+
|
70
|
+
Now, since `S_1` is the largest sum, we have
|
71
|
+
|
72
|
+
.. MATH::
|
73
|
+
|
74
|
+
hyp(a, b, c, d) =& S_1 - \max\{S_2, S_3\}\\
|
75
|
+
\leq& S_1 - \frac{S_2+ S_3}{2}\\
|
76
|
+
\leq& S_1 - dist(a, b)\\
|
77
|
+
=& dist(c, d)\\
|
78
|
+
|
79
|
+
We obtain similarly that `hyp(a, b, c, d) \leq dist(a, b)`. Consequently,
|
80
|
+
in the implementation of the 'CCL' algorithm, we ensure that `S_1` is
|
81
|
+
larger than `S_2` and `S_3` using an ordering of the pairs by decreasing
|
82
|
+
lengths. Then, we use the best value `h` found so far to stop exploration
|
83
|
+
as soon as `dist(a, b) \leq h`.
|
84
|
+
|
85
|
+
The worst case time complexity of this algorithm is `O(n^4)`, but it
|
86
|
+
performs very well in practice since it cuts the search space. This
|
87
|
+
algorithm can be turned into an approximation algorithm since at any step
|
88
|
+
of its execution we maintain an upper and a lower bound. We can thus stop
|
89
|
+
execution as soon as a multiplicative approximation factor or an additive
|
90
|
+
one is proven.
|
91
|
+
|
92
|
+
- The notion of ''far-apart pairs'' has been introduced in [Sot2011]_ to
|
93
|
+
further reduce the number of 4-tuples to consider. We say that the pair
|
94
|
+
`(a,b)` is far-apart if for every `w` in `V\setminus\{a,b\}` we have
|
95
|
+
|
96
|
+
.. MATH::
|
97
|
+
|
98
|
+
dist(w,a)+dist(a,b) > dist(w,b) \text{ and }dist(w,b)+dist(a,b) > dist(w,a)
|
99
|
+
|
100
|
+
Determining the set of far-apart pairs can be done in time `O(nm)` using
|
101
|
+
BFS. Now, it is proved in [Sot2011]_ that there exists two far-apart pairs
|
102
|
+
`(a,b)` and `(c,d)` satisfying `\delta(G) = hyp(a, b, c, d)/2`. For
|
103
|
+
instance, the `n\times m`-grid has only two far-apart pairs, and so
|
104
|
+
computing its hyperbolicity is immediate once the far-apart pairs are
|
105
|
+
found. The 'CCL+FA' or 'CCL+' algorithm improves the 'CCL' algorithm
|
106
|
+
since it uses far-apart pairs.
|
107
|
+
|
108
|
+
- This algorithm was further improved in [BCCM2015]_: instead of iterating
|
109
|
+
twice over all pairs of vertices, in the "inner" loop, we cut several
|
110
|
+
pairs by exploiting properties of the underlying graph.
|
111
|
+
|
112
|
+
.. TODO::
|
113
|
+
|
114
|
+
- Add exact methods for the hyperbolicity of chordal graphs
|
115
|
+
|
116
|
+
- Add method for partitioning the graph with clique separators
|
117
|
+
|
118
|
+
**This module contains the following functions**
|
119
|
+
|
120
|
+
At Python level :
|
121
|
+
|
122
|
+
.. csv-table::
|
123
|
+
:class: contentstable
|
124
|
+
:widths: 30, 70
|
125
|
+
:delim: |
|
126
|
+
|
127
|
+
:meth:`~hyperbolicity` | Return the hyperbolicity of the graph or an approximation of this value.
|
128
|
+
:meth:`~hyperbolicity_distribution` | Return the hyperbolicity distribution of the graph or a sampling of it.
|
129
|
+
|
130
|
+
AUTHORS:
|
131
|
+
|
132
|
+
- David Coudert (2012): initial version, exact and approximate algorithm,
|
133
|
+
distribution, sampling
|
134
|
+
- David Coudert (2014): improved exact algorithm using far-apart pairs
|
135
|
+
- Michele Borassi (2015): cleaned the code and implemented the new algorithm
|
136
|
+
- Karan Desai (2016): fixed minor typo in documentation
|
137
|
+
|
138
|
+
|
139
|
+
Methods
|
140
|
+
-------
|
141
|
+
"""
|
142
|
+
|
143
|
+
# ****************************************************************************
|
144
|
+
# Copyright (C) 2012 David Coudert <david.coudert@inria.fr>
|
145
|
+
#
|
146
|
+
# This program is free software: you can redistribute it and/or modify
|
147
|
+
# it under the terms of the GNU General Public License as published by
|
148
|
+
# the Free Software Foundation, either version 2 of the License, or
|
149
|
+
# (at your option) any later version.
|
150
|
+
# https://www.gnu.org/licenses/
|
151
|
+
# ****************************************************************************
|
152
|
+
|
153
|
+
from libc.string cimport memset
|
154
|
+
from cysignals.memory cimport check_allocarray, sig_free
|
155
|
+
from cysignals.signals cimport sig_on, sig_off
|
156
|
+
from memory_allocator cimport MemoryAllocator
|
157
|
+
|
158
|
+
from sage.graphs.distances_all_pairs cimport c_distances_all_pairs
|
159
|
+
from sage.arith.misc import binomial
|
160
|
+
from sage.rings.integer_ring import ZZ
|
161
|
+
from sage.graphs.base.static_sparse_graph cimport short_digraph
|
162
|
+
from sage.graphs.base.static_sparse_graph cimport init_short_digraph
|
163
|
+
from sage.graphs.base.static_sparse_graph cimport free_short_digraph
|
164
|
+
from libc.stdint cimport uint16_t, uint32_t, uint64_t
|
165
|
+
from sage.data_structures.bitset_base cimport *
|
166
|
+
|
167
|
+
|
168
|
+
# Defining a pair of vertices as a C struct
|
169
|
+
ctypedef struct pair:
|
170
|
+
uint32_t s
|
171
|
+
uint32_t t
|
172
|
+
|
173
|
+
|
174
|
+
######################################################################
|
175
|
+
# Speedup functions
|
176
|
+
######################################################################
|
177
|
+
|
178
|
+
def _my_subgraph(G, vertices, relabel=False, return_map=False):
|
179
|
+
r"""
|
180
|
+
Return the subgraph containing the given vertices.
|
181
|
+
|
182
|
+
This method considers only the connectivity. Therefore, edge labels are
|
183
|
+
ignored as well as any other decoration of the graph (vertex position,
|
184
|
+
etc.).
|
185
|
+
|
186
|
+
If ``relabel`` is ``True``, the vertices of the new graph are relabeled with
|
187
|
+
integers in the range '0\cdots \mid vertices \mid -1'. The relabeling map is
|
188
|
+
returned if ``return_map`` is also ``True``.
|
189
|
+
|
190
|
+
TESTS:
|
191
|
+
|
192
|
+
Giving anything else than a Graph::
|
193
|
+
|
194
|
+
sage: from sage.graphs.hyperbolicity import _my_subgraph as mysub
|
195
|
+
sage: mysub([], [])
|
196
|
+
Traceback (most recent call last):
|
197
|
+
...
|
198
|
+
ValueError: the input parameter must be a Graph
|
199
|
+
|
200
|
+
Subgraph of a PetersenGraph::
|
201
|
+
|
202
|
+
sage: from sage.graphs.hyperbolicity import _my_subgraph as mysub
|
203
|
+
sage: H = mysub(graphs.PetersenGraph(), [0,2,4,6])
|
204
|
+
sage: H.edges(sort=True, labels=None)
|
205
|
+
[(0, 4)]
|
206
|
+
sage: H.vertices(sort=True)
|
207
|
+
[0, 2, 4, 6]
|
208
|
+
"""
|
209
|
+
from sage.graphs.graph import Graph
|
210
|
+
if not isinstance(G, Graph):
|
211
|
+
raise ValueError("the input parameter must be a Graph")
|
212
|
+
H = Graph()
|
213
|
+
if not vertices:
|
214
|
+
return (H, {}) if (relabel and return_map) else H
|
215
|
+
|
216
|
+
if relabel:
|
217
|
+
map = dict(zip(iter(vertices), range(len(vertices))))
|
218
|
+
else:
|
219
|
+
map = dict(zip(iter(vertices), iter(vertices)))
|
220
|
+
|
221
|
+
B = {}
|
222
|
+
for v in G.vertex_iterator():
|
223
|
+
B[v] = False
|
224
|
+
for v in vertices:
|
225
|
+
B[v] = True
|
226
|
+
H.add_vertex(map[v])
|
227
|
+
|
228
|
+
for u in vertices:
|
229
|
+
for v in G.neighbor_iterator(u):
|
230
|
+
if B[v]:
|
231
|
+
H.add_edge(map[u], map[v])
|
232
|
+
|
233
|
+
return (H, map) if (relabel and return_map) else H
|
234
|
+
|
235
|
+
|
236
|
+
######################################################################
|
237
|
+
# Building blocks
|
238
|
+
######################################################################
|
239
|
+
|
240
|
+
cdef inline int __hyp__(unsigned short** distances, int a, int b, int c, int d) noexcept:
|
241
|
+
"""
|
242
|
+
Return the hyperbolicity of the given 4-tuple.
|
243
|
+
"""
|
244
|
+
cdef int S1, S2, S3, h
|
245
|
+
S1 = distances[a][b] + distances[c][d]
|
246
|
+
S2 = distances[a][c] + distances[b][d]
|
247
|
+
S3 = distances[a][d] + distances[b][c]
|
248
|
+
if S1 >= S2:
|
249
|
+
if S2 > S3:
|
250
|
+
h = S1 - S2
|
251
|
+
else:
|
252
|
+
h = abs(S1 - S3)
|
253
|
+
else:
|
254
|
+
if S1 > S3:
|
255
|
+
h = S2 - S1
|
256
|
+
else:
|
257
|
+
h = abs(S2 - S3)
|
258
|
+
return h
|
259
|
+
|
260
|
+
|
261
|
+
######################################################################
|
262
|
+
# Basic algorithm for the hyperbolicity
|
263
|
+
######################################################################
|
264
|
+
|
265
|
+
cdef tuple hyperbolicity_basic_algorithm(int N,
|
266
|
+
unsigned short** distances,
|
267
|
+
verbose):
|
268
|
+
"""
|
269
|
+
Return **twice** the hyperbolicity of a graph, and a certificate.
|
270
|
+
|
271
|
+
This method implements the basic algorithm for computing the hyperbolicity
|
272
|
+
of a graph which tests all 4-tuples of vertices not satisfying a cutting
|
273
|
+
rule proposed in [Sot2011]_.
|
274
|
+
|
275
|
+
INPUT:
|
276
|
+
|
277
|
+
- ``N`` -- number of vertices of the graph
|
278
|
+
|
279
|
+
- ``distances`` -- path distance matrix (see the distance_all_pairs
|
280
|
+
module)
|
281
|
+
|
282
|
+
- ``verbose`` -- boolean (default: ``False``); set to ``True`` to display
|
283
|
+
some information during execution
|
284
|
+
|
285
|
+
OUTPUT:
|
286
|
+
|
287
|
+
This function returns a tuple ( h, certificate ), where:
|
288
|
+
|
289
|
+
- ``h`` -- the maximum computed value over all 4-tuples, and so is twice
|
290
|
+
the hyperbolicity of the graph. If no such 4-tuple is found, -1 is
|
291
|
+
returned.
|
292
|
+
|
293
|
+
- ``certificate`` -- 4-tuple of vertices maximizing the value `h`. If no
|
294
|
+
such 4-tuple is found, the empty list [] is returned.
|
295
|
+
"""
|
296
|
+
cdef int a, b, c, d, hh, h_LB
|
297
|
+
cdef list certificate
|
298
|
+
|
299
|
+
h_LB = -1
|
300
|
+
|
301
|
+
for a in range(N - 3):
|
302
|
+
for b in range(a + 1, N - 2):
|
303
|
+
|
304
|
+
# We use the cutting rule proposed in [Sot2011]_
|
305
|
+
if 2 * distances[a][b] <= h_LB:
|
306
|
+
continue
|
307
|
+
|
308
|
+
for c in range(b + 1, N - 1):
|
309
|
+
|
310
|
+
# We use the cutting rule proposed in [Sot2011]_
|
311
|
+
if 2 * distances[a][c] <= h_LB or 2 * distances[b][c] <= h_LB:
|
312
|
+
continue
|
313
|
+
|
314
|
+
for d in range(c + 1, N):
|
315
|
+
|
316
|
+
# We compute the hyperbolicity of the 4-tuple
|
317
|
+
hh = __hyp__(distances, a, b, c, d)
|
318
|
+
|
319
|
+
# We compare the value with previously known bound
|
320
|
+
if hh > h_LB:
|
321
|
+
h_LB = hh
|
322
|
+
certificate = [a, b, c, d]
|
323
|
+
|
324
|
+
if verbose:
|
325
|
+
print('New lower bound:', ZZ(hh)/2)
|
326
|
+
|
327
|
+
# Last, we return the computed value and the certificate
|
328
|
+
if h_LB != -1:
|
329
|
+
return (h_LB, certificate)
|
330
|
+
return (-1, [])
|
331
|
+
|
332
|
+
|
333
|
+
######################################################################
|
334
|
+
# Greedy dominating set
|
335
|
+
######################################################################
|
336
|
+
|
337
|
+
def _greedy_dominating_set(H, verbose=False):
|
338
|
+
r"""
|
339
|
+
Return a greedy approximation of a dominating set.
|
340
|
+
|
341
|
+
EXAMPLES::
|
342
|
+
|
343
|
+
sage: from sage.graphs.hyperbolicity import _greedy_dominating_set
|
344
|
+
sage: G = graphs.PetersenGraph()
|
345
|
+
sage: _greedy_dominating_set(G)
|
346
|
+
[0, 2, 6]
|
347
|
+
"""
|
348
|
+
cdef list V = sorted([(d, u) for u, d in H.degree_iterator(labels=True)],
|
349
|
+
reverse=True, key=lambda x: x[0])
|
350
|
+
cdef list DOM = []
|
351
|
+
cdef set seen = set()
|
352
|
+
for _, u in V:
|
353
|
+
if u not in seen:
|
354
|
+
seen.add(u)
|
355
|
+
DOM.append(u)
|
356
|
+
seen.update(H.neighbor_iterator(u))
|
357
|
+
|
358
|
+
if verbose:
|
359
|
+
print("Greedy dominating set: {}".format(DOM))
|
360
|
+
|
361
|
+
return DOM
|
362
|
+
|
363
|
+
|
364
|
+
######################################################################
|
365
|
+
# Distances and far-apart pairs
|
366
|
+
######################################################################
|
367
|
+
|
368
|
+
cdef inline distances_and_far_apart_pairs(gg,
|
369
|
+
unsigned short* distances,
|
370
|
+
unsigned short* far_apart_pairs,
|
371
|
+
list int_to_vertex):
|
372
|
+
"""
|
373
|
+
Compute both distances between all pairs and far-apart pairs.
|
374
|
+
|
375
|
+
See the module's documentation for the definition of far-apart pairs.
|
376
|
+
|
377
|
+
This method assumes that:
|
378
|
+
|
379
|
+
- The input graph gg is connected. If not, the result will be incorrect.
|
380
|
+
|
381
|
+
- The arrays distances and far_apart_pairs have already been allocated with
|
382
|
+
size `n^2`.
|
383
|
+
"""
|
384
|
+
cdef uint32_t n = gg.order()
|
385
|
+
cdef uint32_t i
|
386
|
+
|
387
|
+
if not distances or not far_apart_pairs:
|
388
|
+
raise ValueError("distances or far_apart_pairs is a NULL pointer")
|
389
|
+
elif n > <unsigned short> -1:
|
390
|
+
# Computing the distances/far_apart_pairs can only be done if we have
|
391
|
+
# less than MAX_UNSIGNED_SHORT vertices.
|
392
|
+
raise ValueError("The graph backend contains more than {} nodes and "
|
393
|
+
"we cannot compute the matrix of distances/far-apart "
|
394
|
+
"pairs on something"
|
395
|
+
"like that!".format(<unsigned short> -1))
|
396
|
+
|
397
|
+
# The list of waiting vertices
|
398
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
399
|
+
cdef uint32_t* waiting_list = <uint32_t*>mem.allocarray(n, sizeof(uint32_t))
|
400
|
+
cdef unsigned short** c_far_apart = <unsigned short**>mem.allocarray(n, sizeof(unsigned short*))
|
401
|
+
|
402
|
+
# The vertices which have already been visited
|
403
|
+
cdef bitset_t seen
|
404
|
+
bitset_init(seen, n)
|
405
|
+
|
406
|
+
# the beginning and the end of the list stored in waiting_list
|
407
|
+
cdef uint32_t waiting_beginning, waiting_end
|
408
|
+
|
409
|
+
cdef uint32_t source
|
410
|
+
cdef uint32_t v, u
|
411
|
+
|
412
|
+
# All pairs are initially far-apart
|
413
|
+
memset(far_apart_pairs, 1, n * n * sizeof(unsigned short))
|
414
|
+
for i in range(n):
|
415
|
+
c_far_apart[i] = far_apart_pairs + i * n
|
416
|
+
c_far_apart[i][i] = 0
|
417
|
+
|
418
|
+
# Copying the whole graph to obtain the list of neighbors quicker than by
|
419
|
+
# calling out_neighbors. This data structure is well documented in the
|
420
|
+
# module sage.graphs.base.static_sparse_graph
|
421
|
+
cdef short_digraph sd
|
422
|
+
init_short_digraph(sd, gg, edge_labelled=False, vertex_list=int_to_vertex)
|
423
|
+
cdef uint32_t** p_vertices = sd.neighbors
|
424
|
+
cdef uint32_t* p_tmp
|
425
|
+
cdef uint32_t* end
|
426
|
+
|
427
|
+
cdef unsigned short* c_distances = distances
|
428
|
+
|
429
|
+
memset(distances, -1, n * n * sizeof(unsigned short))
|
430
|
+
|
431
|
+
# We run n different BFS taking each vertex as a source
|
432
|
+
for source in range(n):
|
433
|
+
|
434
|
+
# The source is seen
|
435
|
+
bitset_clear(seen)
|
436
|
+
bitset_add(seen, source)
|
437
|
+
c_distances[source] = 0
|
438
|
+
|
439
|
+
# and added to the queue
|
440
|
+
waiting_list[0] = source
|
441
|
+
waiting_beginning = 0
|
442
|
+
waiting_end = 0
|
443
|
+
|
444
|
+
# For as long as there are vertices left to explore
|
445
|
+
while waiting_beginning <= waiting_end:
|
446
|
+
|
447
|
+
# We pick the first one
|
448
|
+
v = waiting_list[waiting_beginning]
|
449
|
+
|
450
|
+
p_tmp = p_vertices[v]
|
451
|
+
end = p_vertices[v+1]
|
452
|
+
|
453
|
+
# Iterating over all the outneighbors u of v
|
454
|
+
while p_tmp < end:
|
455
|
+
u = p_tmp[0]
|
456
|
+
|
457
|
+
# If we notice one of these neighbors is not seen yet, we set
|
458
|
+
# its parameters and add it to the queue to be explored later.
|
459
|
+
if not bitset_in(seen, u):
|
460
|
+
c_distances[u] = c_distances[v] + 1
|
461
|
+
bitset_add(seen, u)
|
462
|
+
waiting_end += 1
|
463
|
+
waiting_list[waiting_end] = u
|
464
|
+
|
465
|
+
if c_distances[u] == c_distances[v] + 1:
|
466
|
+
# v is on the path from source to u
|
467
|
+
c_far_apart[source][v] = 0
|
468
|
+
c_far_apart[v][source] = 0
|
469
|
+
|
470
|
+
p_tmp += 1
|
471
|
+
|
472
|
+
waiting_beginning += 1
|
473
|
+
|
474
|
+
c_distances += n
|
475
|
+
|
476
|
+
bitset_free(seen)
|
477
|
+
free_short_digraph(sd)
|
478
|
+
|
479
|
+
|
480
|
+
cdef inline pair** sort_pairs(uint32_t N,
|
481
|
+
uint16_t D,
|
482
|
+
unsigned short** values,
|
483
|
+
unsigned short** to_include,
|
484
|
+
uint32_t* nb_p,
|
485
|
+
uint32_t* nb_pairs_of_length) noexcept:
|
486
|
+
"""
|
487
|
+
Return an array of unordered pairs {i,j} in increasing order of values.
|
488
|
+
|
489
|
+
Uses counting sort to list pairs {i,j} in increasing order of values(i,j).
|
490
|
+
If to_include[i][j] = 0, the pair is ignored. We assume N and D to be
|
491
|
+
correct with respect to the arrays values and to_include, that values and
|
492
|
+
to_include are symmetric (that is, values[i][j] = values[j][i] and
|
493
|
+
to_include[i][j] = to_include[j][i], and that nb_p, nb_pairs_of_length are
|
494
|
+
already allocated.
|
495
|
+
|
496
|
+
INPUT:
|
497
|
+
|
498
|
+
- ``N`` -- the range of i and j (that is, the square root of the number
|
499
|
+
of pairs to be sorted);
|
500
|
+
|
501
|
+
- ``D`` -- the maximum value of an element;
|
502
|
+
|
503
|
+
- ``values`` -- an array containing in position (i,j) the value of the
|
504
|
+
pair (i,j);
|
505
|
+
|
506
|
+
- ``to_include`` -- an array such that to_include[i][j] contains "1" if
|
507
|
+
pair (i,j) should be included, "0" otherwise. If NULL, all elements are
|
508
|
+
included;
|
509
|
+
|
510
|
+
OUTPUT:
|
511
|
+
|
512
|
+
- ``nb_p`` -- the number of pairs to be included;
|
513
|
+
|
514
|
+
- ``nb_pairs_of_length`` -- an array containing in position k the number
|
515
|
+
of pairs (i,j) that are included and such that values[i][j] = k
|
516
|
+
|
517
|
+
- ``pairs_of_length`` -- this function returns this array, containing in
|
518
|
+
position k a pointer to the first included pair (i,j) such that
|
519
|
+
values[i][j] = k.
|
520
|
+
"""
|
521
|
+
# pairs_of_length[d] is the list of pairs of vertices at distance d
|
522
|
+
cdef pair** pairs_of_length = <pair**>check_allocarray(D + 1, sizeof(pair*))
|
523
|
+
cdef unsigned short* p_to_include
|
524
|
+
cdef uint32_t i, j, k
|
525
|
+
nb_p[0] = 0
|
526
|
+
|
527
|
+
# fills nb_pairs_of_length and nb_p
|
528
|
+
memset(nb_pairs_of_length, 0, (D + 1) * sizeof(uint32_t))
|
529
|
+
|
530
|
+
if not to_include:
|
531
|
+
nb_p[0] = (N * (N - 1)) / 2
|
532
|
+
for i in range(N):
|
533
|
+
for j in range(i + 1, N):
|
534
|
+
nb_pairs_of_length[values[i][j]] += 1
|
535
|
+
else:
|
536
|
+
for i in range(N):
|
537
|
+
p_to_include = to_include[i]
|
538
|
+
for j in range(i + 1, N):
|
539
|
+
if p_to_include[j]:
|
540
|
+
nb_p[0] += 1
|
541
|
+
nb_pairs_of_length[values[i][j]] += 1
|
542
|
+
|
543
|
+
pairs_of_length[0] = <pair*>check_allocarray(nb_p[0], sizeof(pair))
|
544
|
+
|
545
|
+
# temporary variable used to fill pairs_of_length
|
546
|
+
cdef uint32_t* cpt_pairs = <uint32_t*>check_calloc(D + 1, sizeof(uint32_t))
|
547
|
+
|
548
|
+
# ==> Defines pairs_of_length[d] for all d
|
549
|
+
for i in range(1, D + 1):
|
550
|
+
pairs_of_length[i] = pairs_of_length[i - 1] + nb_pairs_of_length[i - 1]
|
551
|
+
|
552
|
+
# ==> Fills pairs_of_length[d] for all d
|
553
|
+
if not to_include:
|
554
|
+
for i in range(N):
|
555
|
+
for j in range(i + 1, N):
|
556
|
+
k = values[i][j]
|
557
|
+
if k:
|
558
|
+
pairs_of_length[k][cpt_pairs[k]].s = i
|
559
|
+
pairs_of_length[k][cpt_pairs[k]].t = j
|
560
|
+
cpt_pairs[k] += 1
|
561
|
+
else:
|
562
|
+
for i in range(N):
|
563
|
+
p_to_include = to_include[i]
|
564
|
+
for j in range(i + 1, N):
|
565
|
+
if p_to_include[j]:
|
566
|
+
k = values[i][j]
|
567
|
+
pairs_of_length[k][cpt_pairs[k]].s = i
|
568
|
+
pairs_of_length[k][cpt_pairs[k]].t = j
|
569
|
+
cpt_pairs[k] += 1
|
570
|
+
|
571
|
+
sig_free(cpt_pairs)
|
572
|
+
return pairs_of_length
|
573
|
+
|
574
|
+
|
575
|
+
######################################################################
|
576
|
+
# Compute the hyperbolicity using the algorithm of [BCCM2015]_
|
577
|
+
######################################################################
|
578
|
+
|
579
|
+
cdef tuple hyperbolicity_BCCM(int N,
|
580
|
+
unsigned short** distances,
|
581
|
+
unsigned short** far_apart_pairs,
|
582
|
+
int D,
|
583
|
+
int h_LB,
|
584
|
+
float approximation_factor,
|
585
|
+
float additive_gap,
|
586
|
+
verbose=False):
|
587
|
+
"""
|
588
|
+
Return the hyperbolicity of a graph.
|
589
|
+
|
590
|
+
This method implements the exact and the approximate algorithms proposed in
|
591
|
+
[BCCM2015]_. See the module's documentation for more details.
|
592
|
+
|
593
|
+
This method assumes that the graph under consideration is connected.
|
594
|
+
|
595
|
+
INPUT:
|
596
|
+
|
597
|
+
- ``N`` -- number of vertices of the graph
|
598
|
+
|
599
|
+
- ``distances`` -- path distance matrix
|
600
|
+
|
601
|
+
- ``far_apart_pairs`` -- 0/1 matrix of far-apart pairs. Pair ``(i,j)`` is
|
602
|
+
far-apart if ``far_apart_pairs[i][j]\neq 0``
|
603
|
+
|
604
|
+
- ``D`` -- diameter of the graph
|
605
|
+
|
606
|
+
- ``h_LB`` -- lower bound on the hyperbolicity
|
607
|
+
|
608
|
+
- ``approximation_factor`` -- when the approximation factor is set to some
|
609
|
+
value larger than 1.0, the function stop computations as soon as the
|
610
|
+
ratio between the upper bound and the best found solution is less than
|
611
|
+
the approximation factor. When the approximation factor is 1.0, the
|
612
|
+
problem is solved optimally.
|
613
|
+
|
614
|
+
- ``additive_gap`` -- when set to a positive number, the function stop
|
615
|
+
computations as soon as the difference between the upper bound and the
|
616
|
+
best found solution is less than additive gap. When the gap is 0.0, the
|
617
|
+
problem is solved optimally.
|
618
|
+
|
619
|
+
- ``verbose`` -- boolean (default: ``False``); set to ``True`` to display
|
620
|
+
some information during execution
|
621
|
+
|
622
|
+
OUTPUT:
|
623
|
+
|
624
|
+
This function returns a tuple ( h, certificate, h_UB ), where:
|
625
|
+
|
626
|
+
- ``h`` -- integer; when 4-tuples with hyperbolicity larger or equal
|
627
|
+
to `h_LB are found, h is the maximum computed value and so twice the
|
628
|
+
hyperbolicity of the graph. If no such 4-tuple is found, it returns -1.
|
629
|
+
|
630
|
+
- ``certificate`` -- is a list of vertices; when 4-tuples with
|
631
|
+
hyperbolicity larger that h_LB are found, certificate is the list of the
|
632
|
+
4 vertices for which the maximum value (and so the hyperbolicity of the
|
633
|
+
graph) has been computed. If no such 4-tuple is found, it returns the
|
634
|
+
empty list [].
|
635
|
+
|
636
|
+
- ``h_UB`` -- integer equal to the proven upper bound for `h`; when
|
637
|
+
``h == h_UB``, the returned solution is optimal
|
638
|
+
"""
|
639
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
640
|
+
cdef int h = 0, hh # can get negative value
|
641
|
+
cdef int a, b, c, d, h_UB, n_val, n_acc, i, j
|
642
|
+
cdef int hplusone
|
643
|
+
cdef int condacc
|
644
|
+
cdef int x, S1, S2, S3
|
645
|
+
cdef list certificate = []
|
646
|
+
cdef uint32_t nb_p # The total number of pairs.
|
647
|
+
cdef unsigned short *dist_a
|
648
|
+
cdef unsigned short *dist_b
|
649
|
+
cdef bint GOTO_RETURN = 0
|
650
|
+
|
651
|
+
# Variable used to store "mates".
|
652
|
+
cdef int **mate = <int**> mem.malloc(N * sizeof(int*))
|
653
|
+
for i in range(N):
|
654
|
+
mate[i] = <int*> mem.malloc(N * sizeof(int))
|
655
|
+
cdef int *cont_mate = <int*> mem.calloc(N, sizeof(int))
|
656
|
+
|
657
|
+
# The farness of all vertices (the farness of v is the sum of the distances
|
658
|
+
# between v and all other vertices).
|
659
|
+
cdef uint64_t *farness = <uint64_t*> mem.calloc(N, sizeof(uint64_t))
|
660
|
+
cdef short *ecc = <short*> mem.calloc(N, sizeof(short))
|
661
|
+
cdef int central = 0
|
662
|
+
cdef int **mates_decr_order_value = <int**> mem.malloc(N * sizeof(int*))
|
663
|
+
cdef int *value = <int*> mem.malloc(N * sizeof(int))
|
664
|
+
cdef int *nvalues = <int*> mem.malloc((D + 1) * sizeof(int))
|
665
|
+
cdef short *acc_bool = <short*> mem.calloc(N, sizeof(short))
|
666
|
+
cdef int *acc = <int*> mem.malloc(N * sizeof(int))
|
667
|
+
cdef int *val = <int*> mem.malloc(N * sizeof(int))
|
668
|
+
cdef int *nvalues_cum = <int*> mem.malloc((D + 1) * sizeof(int))
|
669
|
+
cdef uint64_t nq = 0
|
670
|
+
|
671
|
+
# We compute the farness and the eccentricity of all vertices.
|
672
|
+
# We set central as the vertex with minimum farness
|
673
|
+
for a in range(N):
|
674
|
+
dist_a = distances[a]
|
675
|
+
for b in range(N):
|
676
|
+
farness[a] += dist_a[b]
|
677
|
+
ecc[a] = max(ecc[a], dist_a[b])
|
678
|
+
if dist_a[b] >= N:
|
679
|
+
raise ValueError("the input graph must be connected")
|
680
|
+
if farness[a] < farness[central]:
|
681
|
+
central = a
|
682
|
+
cdef unsigned short *dist_central = distances[central]
|
683
|
+
|
684
|
+
# We put in variable mates_decr_order_value[a] all vertices b, in
|
685
|
+
# decreasing order of ecc[b]-distances[a][b]
|
686
|
+
for a in range(N):
|
687
|
+
mates_decr_order_value[a] = <int*> mem.malloc(N * sizeof(int))
|
688
|
+
dist_a = distances[a]
|
689
|
+
memset(nvalues, 0, (D + 1) * sizeof(int))
|
690
|
+
|
691
|
+
for b in range(N):
|
692
|
+
value[b] = ecc[b] - dist_a[b]
|
693
|
+
nvalues[value[b]] += 1
|
694
|
+
nvalues_cum[D] = 0
|
695
|
+
|
696
|
+
for b in range(D - 1, -1, -1):
|
697
|
+
nvalues_cum[b] = nvalues_cum[b + 1] + nvalues[b + 1]
|
698
|
+
|
699
|
+
for b in range(N):
|
700
|
+
mates_decr_order_value[a][nvalues_cum[value[b]]] = b
|
701
|
+
nvalues_cum[value[b]] += 1
|
702
|
+
|
703
|
+
# We sort pairs, in increasing order of distance
|
704
|
+
cdef uint32_t * nb_pairs_of_length = <uint32_t *> mem.calloc(D + 1, sizeof(uint32_t))
|
705
|
+
|
706
|
+
cdef pair ** pairs_of_length = sort_pairs(N, D, distances, far_apart_pairs,
|
707
|
+
&nb_p, nb_pairs_of_length)
|
708
|
+
|
709
|
+
if verbose:
|
710
|
+
print("Current 2 connected component has %d vertices and diameter %d" % (N, D))
|
711
|
+
if not far_apart_pairs:
|
712
|
+
print("Number of pairs: %d" % (nb_p))
|
713
|
+
print("Repartition of pairs:",
|
714
|
+
[(i, nb_pairs_of_length[i]) for i in range(1, D + 1) if nb_pairs_of_length[i] > 0])
|
715
|
+
else:
|
716
|
+
print("Number of far-apart pairs: %d\t(%d pairs in total)" % (nb_p, binomial(N, 2)))
|
717
|
+
print("Repartition of far-apart pairs:",
|
718
|
+
[(i, nb_pairs_of_length[i]) for i in range(1, D + 1) if nb_pairs_of_length[i] > 0])
|
719
|
+
|
720
|
+
cdef pair * sorted_pairs = pairs_of_length[0]
|
721
|
+
|
722
|
+
approximation_factor = min(approximation_factor, D)
|
723
|
+
additive_gap = min(additive_gap, D)
|
724
|
+
|
725
|
+
# We start iterating from pairs with maximum distance.
|
726
|
+
for x in range(nb_p - 1, -1, -1):
|
727
|
+
a = sorted_pairs[x].s
|
728
|
+
b = sorted_pairs[x].t
|
729
|
+
|
730
|
+
# Without loss of generality, a has smaller farness than b.
|
731
|
+
if farness[a] < farness[b]:
|
732
|
+
a, b = b, a
|
733
|
+
|
734
|
+
dist_a = distances[a]
|
735
|
+
dist_b = distances[b]
|
736
|
+
h_UB = distances[a][b]
|
737
|
+
|
738
|
+
# If we cannot improve further, we stop
|
739
|
+
if h_UB <= h:
|
740
|
+
h_UB = h
|
741
|
+
GOTO_RETURN = 1
|
742
|
+
break
|
743
|
+
|
744
|
+
# Termination if required approximation is found
|
745
|
+
if (h_UB <= h * approximation_factor) or (h_UB - h <= additive_gap):
|
746
|
+
GOTO_RETURN = 1
|
747
|
+
break
|
748
|
+
|
749
|
+
# We update variable mate, adding pair (a,b)
|
750
|
+
mate[a][cont_mate[a]] = b
|
751
|
+
cont_mate[a] += 1
|
752
|
+
mate[b][cont_mate[b]] = a
|
753
|
+
cont_mate[b] += 1
|
754
|
+
|
755
|
+
# We compute acceptable and valuable vertices
|
756
|
+
n_acc = 0
|
757
|
+
n_val = 0
|
758
|
+
|
759
|
+
hplusone = h + 1
|
760
|
+
condacc = 3 * hplusone - 2 * h_UB
|
761
|
+
|
762
|
+
for i in range(N):
|
763
|
+
c = mates_decr_order_value[a][i]
|
764
|
+
if cont_mate[c] > 0:
|
765
|
+
if 2 * (ecc[c] - dist_a[c]) >= condacc:
|
766
|
+
if 2 * (ecc[c] - dist_b[c]) >= condacc:
|
767
|
+
if 2 * dist_a[c] >= hplusone and 2 * dist_b[c] >= hplusone:
|
768
|
+
if 2 * ecc[c] >= 2 * hplusone - h_UB + dist_a[c] + dist_b[c]:
|
769
|
+
# Vertex c is acceptable
|
770
|
+
acc_bool[c] = 1
|
771
|
+
acc[n_acc] = c
|
772
|
+
n_acc += 1
|
773
|
+
if 2 * dist_central[c] + h_UB - h > dist_a[c] + dist_b[c]:
|
774
|
+
# Vertex c is valuable
|
775
|
+
val[n_val] = c
|
776
|
+
n_val += 1
|
777
|
+
else:
|
778
|
+
break
|
779
|
+
|
780
|
+
# For each pair (c,d) where c is valuable and d is acceptable, we
|
781
|
+
# compute the hyperbolicity of (a,b,c,d), and we update h if necessary
|
782
|
+
for i in range(n_val):
|
783
|
+
c = val[i]
|
784
|
+
for j in range(cont_mate[c]):
|
785
|
+
d = mate[c][j]
|
786
|
+
if (acc_bool[d]):
|
787
|
+
nq += 1
|
788
|
+
S1 = h_UB + distances[c][d]
|
789
|
+
S2 = dist_a[c] + dist_b[d]
|
790
|
+
S3 = dist_a[d] + dist_b[c]
|
791
|
+
if S2 > S3:
|
792
|
+
hh = S1 - S2
|
793
|
+
else:
|
794
|
+
hh = S1 - S3
|
795
|
+
|
796
|
+
if h < hh or not certificate:
|
797
|
+
# We update current bound on the hyperbolicity and the
|
798
|
+
# search space.
|
799
|
+
#
|
800
|
+
# Note that if hh==0, we first make sure that a,b,c,d are
|
801
|
+
# all distinct and are a valid certificate.
|
802
|
+
if hh > 0 or not (a == c or a == d or b == c or b == d):
|
803
|
+
h = hh
|
804
|
+
certificate = [a, b, c, d]
|
805
|
+
|
806
|
+
if verbose:
|
807
|
+
print("New lower bound:", ZZ(hh)/2)
|
808
|
+
|
809
|
+
# We reset acc_bool
|
810
|
+
for v in range(n_acc):
|
811
|
+
acc_bool[acc[v]] = 0
|
812
|
+
|
813
|
+
# Needed because sometimes h_UB is not updated, if the analysis is no cut.
|
814
|
+
if not GOTO_RETURN:
|
815
|
+
h_UB = h
|
816
|
+
|
817
|
+
# We now free the memory
|
818
|
+
sig_free(pairs_of_length[0])
|
819
|
+
sig_free(pairs_of_length)
|
820
|
+
|
821
|
+
if verbose:
|
822
|
+
print("Visited 4-tuples:", nq)
|
823
|
+
|
824
|
+
# Last, we return the computed value and the certificate
|
825
|
+
if not certificate:
|
826
|
+
return (-1, [], h_UB)
|
827
|
+
|
828
|
+
# When using far-apart pairs, the loops may end before improving the
|
829
|
+
# upper-bound
|
830
|
+
return (h, certificate, h_UB)
|
831
|
+
|
832
|
+
|
833
|
+
######################################################################
|
834
|
+
# Compute the hyperbolicity using the algorithm of [CCL2015]_
|
835
|
+
######################################################################
|
836
|
+
|
837
|
+
cdef tuple hyperbolicity_CCL(int N,
|
838
|
+
unsigned short** distances,
|
839
|
+
unsigned short** far_apart_pairs,
|
840
|
+
int D,
|
841
|
+
int h_LB,
|
842
|
+
float approximation_factor,
|
843
|
+
float additive_gap,
|
844
|
+
verbose=False):
|
845
|
+
"""
|
846
|
+
Return the hyperbolicity of a graph.
|
847
|
+
|
848
|
+
This method implements the exact and the approximate algorithms proposed in
|
849
|
+
[CCL2015]_. See the module's documentation for more details.
|
850
|
+
|
851
|
+
This method assumes that the graph under consideration is connected.
|
852
|
+
|
853
|
+
INPUT:
|
854
|
+
|
855
|
+
- ``N`` -- number of vertices of the graph
|
856
|
+
|
857
|
+
- ``distances`` -- path distance matrix
|
858
|
+
|
859
|
+
- ``far_apart_pairs`` -- 0/1 matrix of far-apart pairs. Pair ``(i,j)`` is
|
860
|
+
far-apart if ``far_apart_pairs[i][j]\neq 0``
|
861
|
+
|
862
|
+
- ``D`` -- diameter of the graph
|
863
|
+
|
864
|
+
- ``h_LB`` -- lower bound on the hyperbolicity
|
865
|
+
|
866
|
+
- ``approximation_factor`` -- when the approximation factor is set to some
|
867
|
+
value larger than 1.0, the function stop computations as soon as the
|
868
|
+
ratio between the upper bound and the best found solution is less than
|
869
|
+
the approximation factor. When the approximation factor is 1.0, the
|
870
|
+
problem is solved optimally.
|
871
|
+
|
872
|
+
- ``additive_gap`` -- when set to a positive number, the function stop
|
873
|
+
computations as soon as the difference between the upper bound and the
|
874
|
+
best found solution is less than additive gap. When the gap is 0.0, the
|
875
|
+
problem is solved optimally.
|
876
|
+
|
877
|
+
- ``verbose`` -- boolean (default: ``False``); set to ``True`` to display
|
878
|
+
some information during execution
|
879
|
+
|
880
|
+
OUTPUT:
|
881
|
+
|
882
|
+
This function returns a tuple ( h, certificate, h_UB ), where:
|
883
|
+
|
884
|
+
- ``h`` -- integer; when 4-tuples with hyperbolicity larger or equal
|
885
|
+
to `h_LB are found, h is the maximum computed value and so twice the
|
886
|
+
hyperbolicity of the graph. If no such 4-tuple is found, it returns -1.
|
887
|
+
|
888
|
+
- ``certificate`` -- is a list of vertices; when 4-tuples with
|
889
|
+
hyperbolicity larger that h_LB are found, certificate is the list of the
|
890
|
+
4 vertices for which the maximum value (and so the hyperbolicity of the
|
891
|
+
graph) has been computed. If no such 4-tuple is found, it returns the
|
892
|
+
empty list [].
|
893
|
+
|
894
|
+
- ``h_UB`` -- integer equal to the proven upper bound for `h`; when
|
895
|
+
``h == h_UB``, the returned solution is optimal
|
896
|
+
"""
|
897
|
+
cdef int hh # can get negative value
|
898
|
+
cdef int a, b, c, d, h, h_UB
|
899
|
+
cdef int l1, l2, S1, S2, S3
|
900
|
+
cdef uint32_t x, y
|
901
|
+
cdef list certificate = []
|
902
|
+
cdef uint32_t nb_p # The total number of pairs
|
903
|
+
|
904
|
+
# Test if the distance matrix corresponds to a connected graph, i.e., if
|
905
|
+
# distances from node 0 are all less or equal to N-1.
|
906
|
+
for a in range(N):
|
907
|
+
if distances[0][a] >= N:
|
908
|
+
raise ValueError("the input graph must be connected")
|
909
|
+
|
910
|
+
# nb_pairs_of_length[d] is the number of pairs of vertices at distance d
|
911
|
+
cdef uint32_t* nb_pairs_of_length = <uint32_t*>check_allocarray(D + 1, sizeof(uint32_t))
|
912
|
+
|
913
|
+
if not nb_pairs_of_length:
|
914
|
+
raise MemoryError
|
915
|
+
|
916
|
+
cdef pair** pairs_of_length = sort_pairs(N, D, distances, far_apart_pairs,
|
917
|
+
&nb_p, nb_pairs_of_length)
|
918
|
+
|
919
|
+
if verbose:
|
920
|
+
print("Current 2 connected component has %d vertices and diameter %d" % (N, D))
|
921
|
+
if not far_apart_pairs:
|
922
|
+
print("Number of pairs: %d" % (nb_p))
|
923
|
+
print("Repartition of pairs:",
|
924
|
+
[(i, nb_pairs_of_length[i]) for i in range(1, D + 1) if nb_pairs_of_length[i] > 0])
|
925
|
+
else:
|
926
|
+
print("Number of far-apart pairs: %d\t(%d pairs in total)" % (nb_p, binomial(N, 2)))
|
927
|
+
print("Repartition of far-apart pairs:",
|
928
|
+
[(i, nb_pairs_of_length[i]) for i in range(1, D + 1) if nb_pairs_of_length[i] > 0])
|
929
|
+
|
930
|
+
approximation_factor = min(approximation_factor, D)
|
931
|
+
additive_gap = min(additive_gap, D)
|
932
|
+
|
933
|
+
# We create the list of triples (sum,length1,length2) sorted in decreasing
|
934
|
+
# lexicographic order: decreasing by sum, decreasing by length2, decreasing
|
935
|
+
# length1. This is to ensure a valid ordering for S1, to avoid some tests,
|
936
|
+
# and to ease computation of bounds.
|
937
|
+
cdef list triples = []
|
938
|
+
for l2 in range(D, 0, -1):
|
939
|
+
if nb_pairs_of_length[l2]:
|
940
|
+
for l1 in range(D, l2 - 1, -1):
|
941
|
+
if nb_pairs_of_length[l1]:
|
942
|
+
triples.append((l1 + l2, l1, l2))
|
943
|
+
|
944
|
+
# We use some short-cut variables for efficiency
|
945
|
+
cdef pair* pairs_of_length_l1
|
946
|
+
cdef pair* pairs_of_length_l2
|
947
|
+
cdef uint32_t nb_pairs_of_length_l1, nb_pairs_of_length_l2
|
948
|
+
cdef unsigned short * dist_a
|
949
|
+
cdef unsigned short * dist_b
|
950
|
+
h = h_LB
|
951
|
+
h_UB = D
|
952
|
+
cdef int GOTO_RETURN = 0
|
953
|
+
|
954
|
+
# S1 = l1+l2
|
955
|
+
# l1 = dist(a,b)
|
956
|
+
# l2 = dist(c,d)
|
957
|
+
# l1 >= l2
|
958
|
+
for S1, l1, l2 in triples:
|
959
|
+
|
960
|
+
if h_UB > l2:
|
961
|
+
h_UB = l2
|
962
|
+
|
963
|
+
if verbose:
|
964
|
+
print("New upper bound:", ZZ(h_UB) / 2)
|
965
|
+
|
966
|
+
# Termination if required approximation is found
|
967
|
+
if certificate and ((h_UB <= h * approximation_factor) or (h_UB-h <= additive_gap)):
|
968
|
+
GOTO_RETURN = 1
|
969
|
+
break
|
970
|
+
|
971
|
+
# If we cannot improve further, we stop
|
972
|
+
#
|
973
|
+
# See the module's documentation for a proof that this cut is
|
974
|
+
# valid. Remember that the triples are sorted in a specific order.
|
975
|
+
if h_UB <= h:
|
976
|
+
h_UB = h
|
977
|
+
break
|
978
|
+
|
979
|
+
pairs_of_length_l1 = pairs_of_length[l1]
|
980
|
+
pairs_of_length_l2 = pairs_of_length[l2]
|
981
|
+
nb_pairs_of_length_l1 = nb_pairs_of_length[l1]
|
982
|
+
nb_pairs_of_length_l2 = nb_pairs_of_length[l2]
|
983
|
+
|
984
|
+
for x in range(nb_pairs_of_length_l1):
|
985
|
+
a = pairs_of_length_l1[x].s
|
986
|
+
b = pairs_of_length_l1[x].t
|
987
|
+
dist_a = distances[a]
|
988
|
+
dist_b = distances[b]
|
989
|
+
|
990
|
+
# We do not want to test pairs of pairs twice if l1 == l2
|
991
|
+
for y in range((x + 1) if l1 == l2 else 0, nb_pairs_of_length_l2):
|
992
|
+
c = pairs_of_length_l2[y].s
|
993
|
+
d = pairs_of_length_l2[y].t
|
994
|
+
|
995
|
+
# We compute the hyperbolicity of the 4-tuple. We have S1 = l1 +
|
996
|
+
# l2, and the order in which pairs are visited allow us to claim
|
997
|
+
# that S1 = max( S1, S2, S3 ). Indeed, if S1 is not the maximum
|
998
|
+
# value, the order ensures that the maximum value has previously
|
999
|
+
# been checked.
|
1000
|
+
S2 = dist_a[c] + dist_b[d]
|
1001
|
+
S3 = dist_a[d] + dist_b[c]
|
1002
|
+
if S2 > S3:
|
1003
|
+
hh = S1 - S2
|
1004
|
+
else:
|
1005
|
+
hh = S1 - S3
|
1006
|
+
|
1007
|
+
if h < hh or not certificate:
|
1008
|
+
# We update current bound on the hyperbolicity and the
|
1009
|
+
# search space.
|
1010
|
+
#
|
1011
|
+
# Note that if hh==0, we first make sure that a,b,c,d are
|
1012
|
+
# all distinct and are a valid certificate.
|
1013
|
+
if hh > 0 or not (a == c or a == d or b == c or b == d):
|
1014
|
+
h = hh
|
1015
|
+
certificate = [a, b, c, d]
|
1016
|
+
|
1017
|
+
if verbose:
|
1018
|
+
print("New lower bound:", ZZ(hh) / 2)
|
1019
|
+
|
1020
|
+
# If we cannot improve further, we stop
|
1021
|
+
if l2 <= h:
|
1022
|
+
GOTO_RETURN = 1
|
1023
|
+
h_UB = h
|
1024
|
+
break
|
1025
|
+
|
1026
|
+
# Termination if required approximation is found
|
1027
|
+
if (h_UB <= h * approximation_factor) or (h_UB - h <= additive_gap):
|
1028
|
+
GOTO_RETURN = 1
|
1029
|
+
break
|
1030
|
+
|
1031
|
+
if GOTO_RETURN:
|
1032
|
+
break
|
1033
|
+
|
1034
|
+
if GOTO_RETURN:
|
1035
|
+
break
|
1036
|
+
|
1037
|
+
# We now free the memory
|
1038
|
+
sig_free(nb_pairs_of_length)
|
1039
|
+
sig_free(pairs_of_length[0])
|
1040
|
+
sig_free(pairs_of_length)
|
1041
|
+
|
1042
|
+
# Last, we return the computed value and the certificate
|
1043
|
+
if not certificate:
|
1044
|
+
return (-1, [], h_UB)
|
1045
|
+
|
1046
|
+
# When using far-apart pairs, the loops may end before improving the
|
1047
|
+
# upper-bound
|
1048
|
+
return (h, certificate, h_UB if GOTO_RETURN else h)
|
1049
|
+
|
1050
|
+
|
1051
|
+
def hyperbolicity(G,
|
1052
|
+
algorithm='BCCM',
|
1053
|
+
approximation_factor=None,
|
1054
|
+
additive_gap=None,
|
1055
|
+
verbose=False):
|
1056
|
+
r"""
|
1057
|
+
Return the hyperbolicity of the graph or an approximation of this value.
|
1058
|
+
|
1059
|
+
The hyperbolicity of a graph has been defined by Gromov [Gro1987]_ as
|
1060
|
+
follows: Let `a, b, c, d` be vertices of the graph, let `S_1 = dist(a, b) +
|
1061
|
+
dist(b, c)`, `S_2 = dist(a, c) + dist(b, d)`, and `S_3 = dist(a, d) +
|
1062
|
+
dist(b, c)`, and let `M_1` and `M_2` be the two largest values among `S_1`,
|
1063
|
+
`S_2`, and `S_3`. We have `hyp(a, b, c, d) = |M_1 - M_2|`, and the
|
1064
|
+
hyperbolicity of the graph is the maximum over all possible 4-tuples `(a,b,
|
1065
|
+
c,d)` divided by 2. The worst case time complexity is in `O( n^4 )`.
|
1066
|
+
|
1067
|
+
See the documentation of :mod:`sage.graphs.hyperbolicity` for more
|
1068
|
+
information.
|
1069
|
+
|
1070
|
+
INPUT:
|
1071
|
+
|
1072
|
+
- ``G`` -- a connected Graph
|
1073
|
+
|
1074
|
+
- ``algorithm`` -- (default: ``'BCCM'``) specifies the algorithm to use
|
1075
|
+
among:
|
1076
|
+
|
1077
|
+
- ``'basic'`` is an exhaustive algorithm considering all possible
|
1078
|
+
4-tuples and so have time complexity in `O(n^4)`.
|
1079
|
+
|
1080
|
+
- ``'CCL'`` is an exact algorithm proposed in [CCL2015]_. It considers
|
1081
|
+
the 4-tuples in an ordering allowing to cut the search space as soon
|
1082
|
+
as a new lower bound is found (see the module's documentation). This
|
1083
|
+
algorithm can be turned into a approximation algorithm.
|
1084
|
+
|
1085
|
+
- ``'CCL+FA'`` or ``'CCL+'`` uses the notion of far-apart pairs as
|
1086
|
+
proposed in [Sot2011]_ to significantly reduce the overall
|
1087
|
+
computation time of the ``'CCL'`` algorithm.
|
1088
|
+
|
1089
|
+
- ``'BCCM'`` is an exact algorithm proposed in [BCCM2015]_. It
|
1090
|
+
improves ``'CCL+FA'`` by cutting several 4-tuples (for more
|
1091
|
+
information, see the module's documentation).
|
1092
|
+
|
1093
|
+
- ``'dom'`` is an approximation with additive constant four. It
|
1094
|
+
computes the hyperbolicity of the vertices of a dominating set of
|
1095
|
+
the graph. This is sometimes slower than ``'CCL'`` and sometimes
|
1096
|
+
faster. Try it to know if it is interesting for you.
|
1097
|
+
The ``additive_gap`` and ``approximation_factor`` parameters cannot
|
1098
|
+
be used in combination with this method and so are ignored.
|
1099
|
+
|
1100
|
+
- ``approximation_factor`` -- (default: ``None``) when the approximation factor
|
1101
|
+
is set to some value (larger than 1.0), the function stop computations as
|
1102
|
+
soon as the ratio between the upper bound and the best found solution is
|
1103
|
+
less than the approximation factor. When the approximation factor is 1.0,
|
1104
|
+
the problem is solved optimally. This parameter is used only when the
|
1105
|
+
chosen algorithm is ``'CCL'``, ``'CCL+FA'``, or ``'BCCM'``.
|
1106
|
+
|
1107
|
+
- ``additive_gap`` -- (default: ``None``) when set to a positive number, the
|
1108
|
+
function stop computations as soon as the difference between the upper
|
1109
|
+
bound and the best found solution is less than additive gap. When the gap
|
1110
|
+
is 0.0, the problem is solved optimally. This parameter is used only when
|
1111
|
+
the chosen algorithm is ``'CCL'`` or ``'CCL+FA'``, or ``'BCCM'``.
|
1112
|
+
|
1113
|
+
- ``verbose`` -- boolean (default: ``False``); set to ``True`` to display
|
1114
|
+
some information during execution: new upper and lower bounds, etc.
|
1115
|
+
|
1116
|
+
OUTPUT:
|
1117
|
+
|
1118
|
+
This function returns the tuple ( delta, certificate, delta_UB ), where:
|
1119
|
+
|
1120
|
+
- ``delta`` -- the hyperbolicity of the graph (half-integer value)
|
1121
|
+
|
1122
|
+
- ``certificate`` -- is the list of the 4 vertices for which the maximum
|
1123
|
+
value has been computed, and so the hyperbolicity of the graph
|
1124
|
+
|
1125
|
+
- ``delta_UB`` -- is an upper bound for ``delta``. When ``delta ==
|
1126
|
+
delta_UB``, the returned solution is optimal. Otherwise, the approximation
|
1127
|
+
factor if ``delta_UB/delta``.
|
1128
|
+
|
1129
|
+
EXAMPLES:
|
1130
|
+
|
1131
|
+
Hyperbolicity of a `3\times 3` grid::
|
1132
|
+
|
1133
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity
|
1134
|
+
sage: G = graphs.Grid2dGraph(3, 3)
|
1135
|
+
sage: L,C,U = hyperbolicity(G, algorithm='BCCM'); L,sorted(C),U
|
1136
|
+
(2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2)
|
1137
|
+
sage: L,C,U = hyperbolicity(G, algorithm='CCL'); L,sorted(C),U
|
1138
|
+
(2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2)
|
1139
|
+
sage: L,C,U = hyperbolicity(G, algorithm='basic'); L,sorted(C),U
|
1140
|
+
(2, [(0, 0), (0, 2), (2, 0), (2, 2)], 2)
|
1141
|
+
|
1142
|
+
Hyperbolicity of a PetersenGraph::
|
1143
|
+
|
1144
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity
|
1145
|
+
sage: G = graphs.PetersenGraph()
|
1146
|
+
sage: L,C,U = hyperbolicity(G, algorithm='BCCM'); L,sorted(C),U
|
1147
|
+
(1/2, [6, 7, 8, 9], 1/2)
|
1148
|
+
sage: L,C,U = hyperbolicity(G, algorithm='CCL'); L,sorted(C),U
|
1149
|
+
(1/2, [0, 1, 2, 3], 1/2)
|
1150
|
+
sage: L,C,U = hyperbolicity(G, algorithm='CCL+'); L,sorted(C),U
|
1151
|
+
(1/2, [0, 1, 2, 3], 1/2)
|
1152
|
+
sage: L,C,U = hyperbolicity(G, algorithm='CCL+FA'); L,sorted(C),U
|
1153
|
+
(1/2, [0, 1, 2, 3], 1/2)
|
1154
|
+
sage: L,C,U = hyperbolicity(G, algorithm='basic'); L,sorted(C),U
|
1155
|
+
(1/2, [0, 1, 2, 3], 1/2)
|
1156
|
+
sage: L,C,U = hyperbolicity(G, algorithm='dom'); L,U
|
1157
|
+
(0, 1)
|
1158
|
+
sage: sorted(C) # random
|
1159
|
+
[0, 1, 2, 6]
|
1160
|
+
|
1161
|
+
Asking for an approximation in a grid graph::
|
1162
|
+
|
1163
|
+
sage: # needs sage.rings.real_mpfr
|
1164
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity
|
1165
|
+
sage: G = graphs.Grid2dGraph(2, 10)
|
1166
|
+
sage: L,C,U = hyperbolicity(G, algorithm='CCL', approximation_factor=1.5); L,U
|
1167
|
+
(1, 3/2)
|
1168
|
+
sage: L,C,U = hyperbolicity(G, algorithm='CCL+', approximation_factor=1.5); L,U
|
1169
|
+
(1, 1)
|
1170
|
+
sage: L,C,U = hyperbolicity(G, algorithm='CCL', approximation_factor=4); L,U
|
1171
|
+
(1, 4)
|
1172
|
+
sage: L,C,U = hyperbolicity(G, algorithm='CCL', additive_gap=2); L,U
|
1173
|
+
(1, 3)
|
1174
|
+
sage: L,C,U = hyperbolicity(G, algorithm='dom'); L,U
|
1175
|
+
(1, 5)
|
1176
|
+
|
1177
|
+
Asking for an approximation in a cycle graph::
|
1178
|
+
|
1179
|
+
sage: # needs sage.rings.real_mpfr
|
1180
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity
|
1181
|
+
sage: G = graphs.CycleGraph(10)
|
1182
|
+
sage: L,C,U = hyperbolicity(G, algorithm='CCL', approximation_factor=1.5); L,U
|
1183
|
+
(2, 5/2)
|
1184
|
+
sage: L,C,U = hyperbolicity(G, algorithm='CCL+FA', approximation_factor=1.5); L,U
|
1185
|
+
(2, 5/2)
|
1186
|
+
sage: L,C,U = hyperbolicity(G, algorithm='CCL+FA', additive_gap=1); L,U
|
1187
|
+
(2, 5/2)
|
1188
|
+
|
1189
|
+
Comparison of results::
|
1190
|
+
|
1191
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity
|
1192
|
+
sage: for i in range(10): # long time # needs networkx
|
1193
|
+
....: G = graphs.RandomBarabasiAlbert(100,2)
|
1194
|
+
....: d1,_,_ = hyperbolicity(G, algorithm='basic')
|
1195
|
+
....: d2,_,_ = hyperbolicity(G, algorithm='CCL')
|
1196
|
+
....: d3,_,_ = hyperbolicity(G, algorithm='CCL+')
|
1197
|
+
....: d4,_,_ = hyperbolicity(G, algorithm='CCL+FA')
|
1198
|
+
....: d5,_,_ = hyperbolicity(G, algorithm='BCCM')
|
1199
|
+
....: l3,_,u3 = hyperbolicity(G, approximation_factor=2)
|
1200
|
+
....: if (not d1==d2==d3==d4==d5) or l3>d1 or u3<d1:
|
1201
|
+
....: print("That's not good!")
|
1202
|
+
|
1203
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity
|
1204
|
+
sage: import random
|
1205
|
+
sage: random.seed()
|
1206
|
+
sage: for i in range(10): # long time # needs networkx
|
1207
|
+
....: n = random.randint(2, 20)
|
1208
|
+
....: m = random.randint(0, n*(n-1) / 2)
|
1209
|
+
....: G = graphs.RandomGNM(n, m)
|
1210
|
+
....: for cc in G.connected_components_subgraphs():
|
1211
|
+
....: d1,_,_ = hyperbolicity(cc, algorithm='basic')
|
1212
|
+
....: d2,_,_ = hyperbolicity(cc, algorithm='CCL')
|
1213
|
+
....: d3,_,_ = hyperbolicity(cc, algorithm='CCL+')
|
1214
|
+
....: d4,_,_ = hyperbolicity(cc, algorithm='CCL+FA')
|
1215
|
+
....: d5,_,_ = hyperbolicity(cc, algorithm='BCCM')
|
1216
|
+
....: l3,_,u3 = hyperbolicity(cc, approximation_factor=2)
|
1217
|
+
....: if (not d1==d2==d3==d4==d5) or l3>d1 or u3<d1:
|
1218
|
+
....: print("Error in graph ", cc.edges(sort=True))
|
1219
|
+
|
1220
|
+
The hyperbolicity of a graph is the maximum value over all its biconnected
|
1221
|
+
components::
|
1222
|
+
|
1223
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity
|
1224
|
+
sage: G = graphs.PetersenGraph() * 2
|
1225
|
+
sage: G.add_edge(0, 11)
|
1226
|
+
sage: L,C,U = hyperbolicity(G); L,sorted(C),U
|
1227
|
+
(1/2, [6, 7, 8, 9], 1/2)
|
1228
|
+
|
1229
|
+
TESTS:
|
1230
|
+
|
1231
|
+
Giving anything else than a Graph::
|
1232
|
+
|
1233
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity
|
1234
|
+
sage: hyperbolicity([])
|
1235
|
+
Traceback (most recent call last):
|
1236
|
+
...
|
1237
|
+
ValueError: the input parameter must be a Graph
|
1238
|
+
|
1239
|
+
Giving a non connected graph::
|
1240
|
+
|
1241
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity
|
1242
|
+
sage: G = Graph([(0,1),(2,3)])
|
1243
|
+
sage: hyperbolicity(G)
|
1244
|
+
Traceback (most recent call last):
|
1245
|
+
...
|
1246
|
+
ValueError: the input Graph must be connected
|
1247
|
+
|
1248
|
+
Giving wrong approximation factor::
|
1249
|
+
|
1250
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity
|
1251
|
+
sage: G = graphs.PetersenGraph()
|
1252
|
+
sage: hyperbolicity(G, algorithm='CCL', approximation_factor=0.1)
|
1253
|
+
Traceback (most recent call last):
|
1254
|
+
...
|
1255
|
+
ValueError: the approximation factor must be >= 1.0
|
1256
|
+
|
1257
|
+
Giving negative additive gap::
|
1258
|
+
|
1259
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity
|
1260
|
+
sage: G = Graph()
|
1261
|
+
sage: hyperbolicity(G, algorithm='CCL', additive_gap=-1)
|
1262
|
+
Traceback (most recent call last):
|
1263
|
+
...
|
1264
|
+
ValueError: the additive gap must be a real positive number
|
1265
|
+
|
1266
|
+
Asking for an unknown algorithm::
|
1267
|
+
|
1268
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity
|
1269
|
+
sage: G = Graph()
|
1270
|
+
sage: hyperbolicity(G, algorithm='tip top')
|
1271
|
+
Traceback (most recent call last):
|
1272
|
+
...
|
1273
|
+
ValueError: algorithm 'tip top' not yet implemented, please contribute
|
1274
|
+
"""
|
1275
|
+
|
1276
|
+
# Abbreviations for algorithms are expanded.
|
1277
|
+
if algorithm == "CCL+":
|
1278
|
+
algorithm = "CCL+FA"
|
1279
|
+
|
1280
|
+
from sage.graphs.graph import Graph
|
1281
|
+
if not isinstance(G, Graph):
|
1282
|
+
raise ValueError("the input parameter must be a Graph")
|
1283
|
+
if algorithm not in ['basic', 'CCL', 'CCL+FA', 'BCCM', 'dom']:
|
1284
|
+
raise ValueError("algorithm '%s' not yet implemented, please contribute" % (algorithm))
|
1285
|
+
if approximation_factor is None:
|
1286
|
+
approximation_factor = 1.0
|
1287
|
+
elif approximation_factor == 1.0:
|
1288
|
+
pass
|
1289
|
+
elif algorithm in ['CCL', 'CCL+FA', 'BCCM']:
|
1290
|
+
from sage.rings.real_mpfr import RR
|
1291
|
+
|
1292
|
+
if approximation_factor not in RR or approximation_factor < 1.0:
|
1293
|
+
raise ValueError("the approximation factor must be >= 1.0")
|
1294
|
+
else:
|
1295
|
+
raise ValueError("the approximation_factor is ignored when using"
|
1296
|
+
"the '%s' algorithm" % (algorithm))
|
1297
|
+
if additive_gap is None:
|
1298
|
+
additive_gap = 0.0
|
1299
|
+
elif additive_gap == 0.0:
|
1300
|
+
pass
|
1301
|
+
elif algorithm in ['CCL', 'CCL+FA', 'BCCM']:
|
1302
|
+
from sage.rings.real_mpfr import RR
|
1303
|
+
|
1304
|
+
if additive_gap not in RR or additive_gap < 0.0:
|
1305
|
+
raise ValueError("the additive gap must be a real positive number")
|
1306
|
+
else:
|
1307
|
+
raise ValueError("the additive_gap is ignored when using the '%s' algorithm." % (algorithm))
|
1308
|
+
|
1309
|
+
# The hyperbolicity is defined on connected graphs
|
1310
|
+
if not G.is_connected():
|
1311
|
+
raise ValueError("the input Graph must be connected")
|
1312
|
+
|
1313
|
+
# The hyperbolicity of some classes of graphs is known. If it is easy and
|
1314
|
+
# fast to test that a graph belongs to one of these classes, we do it.
|
1315
|
+
if G.num_verts() <= 3:
|
1316
|
+
# The hyperbolicity of a graph with 3 vertices is 0.
|
1317
|
+
# The certificate is the set of vertices.
|
1318
|
+
return 0, list(G), 0
|
1319
|
+
|
1320
|
+
elif G.num_verts() == G.num_edges() + 1:
|
1321
|
+
# G is a tree
|
1322
|
+
# Any set of 4 vertices is a valid certificate
|
1323
|
+
return 0, list(G)[:4], 0
|
1324
|
+
|
1325
|
+
elif G.is_clique():
|
1326
|
+
# Any set of 4 vertices is a valid certificate
|
1327
|
+
return 0, list(G)[:4], 0
|
1328
|
+
|
1329
|
+
cdef int i, j, D
|
1330
|
+
cdef list certificate = []
|
1331
|
+
cdef list certif
|
1332
|
+
|
1333
|
+
cdef int N = G.num_verts()
|
1334
|
+
hyp = 0
|
1335
|
+
hyp_UB = 0
|
1336
|
+
|
1337
|
+
#
|
1338
|
+
# The hyperbolicity of a graph is the maximum over its 2-connected
|
1339
|
+
# components.
|
1340
|
+
#
|
1341
|
+
B, _ = G.blocks_and_cut_vertices()
|
1342
|
+
if len(B) > 1:
|
1343
|
+
|
1344
|
+
if verbose:
|
1345
|
+
# we compute the distribution of size of the blocks
|
1346
|
+
L = [len(V) for V in B]
|
1347
|
+
print("Graph with %d blocks" % (len(B)))
|
1348
|
+
print("Blocks size distribution:", {x: L.count(x) for x in L})
|
1349
|
+
|
1350
|
+
for V in B:
|
1351
|
+
|
1352
|
+
# The hyperbolicity of a graph with 3 vertices is 0, and a graph
|
1353
|
+
# cannot have hyperbolicity larger than N/2. So we consider only
|
1354
|
+
# larger 2-connected subgraphs.
|
1355
|
+
if len(V) > max(3, 2 * hyp):
|
1356
|
+
|
1357
|
+
hh, certif, hh_UB = hyperbolicity(_my_subgraph(G, V), algorithm=algorithm,
|
1358
|
+
approximation_factor=approximation_factor,
|
1359
|
+
additive_gap=additive_gap, verbose=verbose)
|
1360
|
+
|
1361
|
+
# We test if the new computed value improves upon previous value.
|
1362
|
+
if hh > hyp or (hh == hyp and not certificate):
|
1363
|
+
hyp = hh
|
1364
|
+
certificate = certif
|
1365
|
+
|
1366
|
+
# We update independently the upper bound for cases in which we
|
1367
|
+
# are asking for an approximation.
|
1368
|
+
hyp_UB = max(hyp_UB, hh_UB)
|
1369
|
+
|
1370
|
+
# Last, we return the computed value and the certificate
|
1371
|
+
return hyp, certificate, hyp_UB
|
1372
|
+
|
1373
|
+
#
|
1374
|
+
# Now the graph is 2-connected, has at least 4 vertices and is not a clique.
|
1375
|
+
#
|
1376
|
+
|
1377
|
+
cdef unsigned short* _distances_
|
1378
|
+
cdef unsigned short** distances
|
1379
|
+
cdef unsigned short* _far_apart_pairs_
|
1380
|
+
cdef unsigned short** far_apart_pairs
|
1381
|
+
cdef list int_to_vertex = list(G)
|
1382
|
+
|
1383
|
+
# We compute the distances and store the results in a 2D array
|
1384
|
+
distances = <unsigned short **>check_allocarray(N, sizeof(unsigned short *))
|
1385
|
+
if not distances:
|
1386
|
+
raise MemoryError("Unable to allocate array 'distances'.")
|
1387
|
+
|
1388
|
+
if algorithm == 'CCL+FA' or algorithm == 'BCCM':
|
1389
|
+
_distances_ = <unsigned short *>check_allocarray(N * N, sizeof(unsigned short))
|
1390
|
+
_far_apart_pairs_ = <unsigned short *>check_allocarray(N * N, sizeof(unsigned short))
|
1391
|
+
far_apart_pairs = <unsigned short **>check_allocarray(N, sizeof(unsigned short *))
|
1392
|
+
|
1393
|
+
distances_and_far_apart_pairs(G, _distances_, _far_apart_pairs_, int_to_vertex)
|
1394
|
+
|
1395
|
+
for i in range(N):
|
1396
|
+
far_apart_pairs[i] = _far_apart_pairs_ + i*N
|
1397
|
+
|
1398
|
+
else:
|
1399
|
+
_distances_ = c_distances_all_pairs(G, vertex_list=int_to_vertex)
|
1400
|
+
_far_apart_pairs_ = NULL
|
1401
|
+
far_apart_pairs = NULL
|
1402
|
+
|
1403
|
+
D = 0
|
1404
|
+
for i in range(N):
|
1405
|
+
distances[i] = _distances_ + i * N
|
1406
|
+
for j in range(i + 1, N):
|
1407
|
+
if distances[i][j] > D:
|
1408
|
+
D = distances[i][j]
|
1409
|
+
|
1410
|
+
# We call the cython function for computing the hyperbolicity with the
|
1411
|
+
# required parameters.
|
1412
|
+
if algorithm in ['CCL', 'CCL+FA']:
|
1413
|
+
sig_on()
|
1414
|
+
hyp, certif, hyp_UB = hyperbolicity_CCL(N, distances, far_apart_pairs, D, hyp,
|
1415
|
+
approximation_factor, 2 * additive_gap, verbose)
|
1416
|
+
sig_off()
|
1417
|
+
|
1418
|
+
elif algorithm == 'BCCM':
|
1419
|
+
sig_on()
|
1420
|
+
hyp, certif, hyp_UB = hyperbolicity_BCCM(N, distances, far_apart_pairs,
|
1421
|
+
D, hyp, approximation_factor,
|
1422
|
+
2 * additive_gap, verbose)
|
1423
|
+
sig_off()
|
1424
|
+
|
1425
|
+
elif algorithm == 'dom':
|
1426
|
+
# Computes a dominating set DOM of G, and computes the hyperbolicity
|
1427
|
+
# considering only vertices in DOM
|
1428
|
+
DOM = set(_greedy_dominating_set(G, verbose=verbose))
|
1429
|
+
# We need at least 4 vertices
|
1430
|
+
while len(DOM) < 4:
|
1431
|
+
DOM.add(G.random_vertex())
|
1432
|
+
# We map the dominating set to [0..N-1]
|
1433
|
+
v_to_int = {v: i for i, v in enumerate(G.vertex_iterator())}
|
1434
|
+
DOM_int = set(v_to_int[v] for v in DOM)
|
1435
|
+
# We set null distances to vertices outside DOM. This way these
|
1436
|
+
# vertices will not be considered anymore.
|
1437
|
+
for i in range(N):
|
1438
|
+
if i not in DOM_int:
|
1439
|
+
for j in range(N):
|
1440
|
+
distances[i][j] = 0
|
1441
|
+
distances[j][i] = 0
|
1442
|
+
sig_on()
|
1443
|
+
hyp, certif, hyp_UB = hyperbolicity_CCL(N, distances, NULL, D, hyp, 1.0, 0.0, verbose)
|
1444
|
+
sig_off()
|
1445
|
+
hyp_UB = min(hyp + 8, D)
|
1446
|
+
|
1447
|
+
elif algorithm == 'basic':
|
1448
|
+
sig_on()
|
1449
|
+
hyp, certif = hyperbolicity_basic_algorithm(N, distances, verbose=verbose)
|
1450
|
+
sig_off()
|
1451
|
+
hyp_UB = hyp
|
1452
|
+
|
1453
|
+
# We now release the memory
|
1454
|
+
sig_free(distances)
|
1455
|
+
sig_free(_distances_)
|
1456
|
+
sig_free(_far_apart_pairs_)
|
1457
|
+
sig_free(far_apart_pairs)
|
1458
|
+
|
1459
|
+
# Map the certificate 'certif' with the corresponding vertices in the graph
|
1460
|
+
certificate = [int_to_vertex[i] for i in certif]
|
1461
|
+
|
1462
|
+
# Last, we return the computed value and the certificate
|
1463
|
+
return ZZ(hyp)/2, certificate, ZZ(hyp_UB)/2
|
1464
|
+
|
1465
|
+
|
1466
|
+
######################################################################
|
1467
|
+
# Distribution of the hyperbolicity of 4-tuples
|
1468
|
+
######################################################################
|
1469
|
+
|
1470
|
+
cdef dict __hyperbolicity_distribution__(int N, unsigned short** distances):
|
1471
|
+
"""
|
1472
|
+
Return the distribution of the hyperbolicity of the 4-tuples of the graph.
|
1473
|
+
|
1474
|
+
The hyperbolicity of a graph has been defined by Gromov [Gro1987]_ as
|
1475
|
+
follows: Let `a, b, c, d` be vertices of the graph, let `S_1 = dist(a, b) +
|
1476
|
+
dist(b, c)`, `S_2 = dist(a, c) + dist(b, d)`, and `S_3 = dist(a, d) +
|
1477
|
+
dist(b, c)`, and let `M_1` and `M_2` be the two largest values among `S_1`,
|
1478
|
+
`S_2`, and `S_3`. We have `hyp(a, b, c, d) = |M_1 - M_2|`, and the
|
1479
|
+
hyperbolicity of the graph is the maximum over all possible 4-tuples `(a, b,
|
1480
|
+
c, d)` divided by 2.
|
1481
|
+
|
1482
|
+
The computation of the hyperbolicity of each 4-tuple, and so the
|
1483
|
+
hyperbolicity distribution, takes time in `O( n^4 )`.
|
1484
|
+
|
1485
|
+
We use ``unsigned long int`` on 64 bits, so ``uint64_t``, to count the
|
1486
|
+
number of 4-tuples of given hyperbolicity. So we cannot exceed `2^64-1`.
|
1487
|
+
This value should be sufficient for most users.
|
1488
|
+
|
1489
|
+
INPUT:
|
1490
|
+
|
1491
|
+
- ``N`` -- number of vertices of the graph (and side of the matrix)
|
1492
|
+
|
1493
|
+
- ``distances`` -- matrix of distances in the graph
|
1494
|
+
|
1495
|
+
OUTPUT:
|
1496
|
+
|
1497
|
+
- ``hdict`` -- dictionary such that hdict[i] is the number of 4-tuples of
|
1498
|
+
hyperbolicity i among the considered 4-tuples
|
1499
|
+
"""
|
1500
|
+
# We initialize the table of hyperbolicity. We use an array of unsigned long
|
1501
|
+
# int instead of a dictionary since it is much faster.
|
1502
|
+
cdef int i
|
1503
|
+
|
1504
|
+
cdef uint64_t* hdistr = <uint64_t*>check_calloc(N + 1, sizeof(uint64_t))
|
1505
|
+
if not hdistr:
|
1506
|
+
raise MemoryError
|
1507
|
+
|
1508
|
+
# We now compute the hyperbolicity of each 4-tuple
|
1509
|
+
cdef int a, b, c, d
|
1510
|
+
for a in range(N - 3):
|
1511
|
+
for b in range(a + 1, N - 2):
|
1512
|
+
for c in range(b + 1, N - 1):
|
1513
|
+
for d in range(c + 1, N):
|
1514
|
+
hdistr[__hyp__(distances, a, b, c, d)] += 1
|
1515
|
+
|
1516
|
+
# We prepare the dictionary of hyperbolicity distribution to return
|
1517
|
+
Nchoose4 = binomial(N, 4)
|
1518
|
+
cdef dict hdict = {ZZ(i)/2: (ZZ(hdistr[i]) / Nchoose4) for i in range(N + 1) if hdistr[i] > 0}
|
1519
|
+
|
1520
|
+
sig_free(hdistr)
|
1521
|
+
|
1522
|
+
return hdict
|
1523
|
+
|
1524
|
+
|
1525
|
+
# We use this trick since it is way faster than using the sage randint function.
|
1526
|
+
cdef extern from "stdlib.h":
|
1527
|
+
long c_libc_random "random"()
|
1528
|
+
void c_libc_srandom "srandom"(unsigned int seed)
|
1529
|
+
|
1530
|
+
|
1531
|
+
cdef dict __hyperbolicity_sampling__(int N, unsigned short** distances, uint64_t sampling_size):
|
1532
|
+
"""
|
1533
|
+
Return a sampling of the hyperbolicity distribution of the graph.
|
1534
|
+
|
1535
|
+
The hyperbolicity of a graph has been defined by Gromov [Gro1987]_ as
|
1536
|
+
follows: Let `a, b, c, d` be vertices of the graph, let `S_1 = dist(a, b) +
|
1537
|
+
dist(b, c)`, `S_2 = dist(a, c) + dist(b, d)`, and `S_3 = dist(a, d) +
|
1538
|
+
dist(b, c)`, and let `M_1` and `M_2` be the two largest values among `S_1`,
|
1539
|
+
`S_2`, and `S_3`. We have `hyp(a, b, c, d) = |M_1 - M_2|`, and the
|
1540
|
+
hyperbolicity of the graph is the maximum over all possible 4-tuples `(a, b,
|
1541
|
+
c, d)` divided by 2.
|
1542
|
+
|
1543
|
+
We use ``unsigned long int`` on 64 bits, so ``uint64_t``, to count the
|
1544
|
+
number of 4-tuples of given hyperbolicity. So we cannot exceed `2^64-1`.
|
1545
|
+
This value should be sufficient for most users.
|
1546
|
+
|
1547
|
+
INPUT:
|
1548
|
+
|
1549
|
+
- ``N`` -- number of vertices of the graph (and side of the matrix)
|
1550
|
+
|
1551
|
+
- ``distances`` -- matrix of distances in the graph
|
1552
|
+
|
1553
|
+
- ``sampling_size`` -- number of 4-tuples considered. Default value is 1000
|
1554
|
+
|
1555
|
+
OUTPUT:
|
1556
|
+
|
1557
|
+
- ``hdict`` -- dictionary such that hdict[i] is the number of 4-tuples of
|
1558
|
+
hyperbolicity i among the considered 4-tuples
|
1559
|
+
"""
|
1560
|
+
cdef int i, a, b, c, d
|
1561
|
+
cdef uint64_t j
|
1562
|
+
|
1563
|
+
if N < 4:
|
1564
|
+
raise ValueError("N must be at least 4")
|
1565
|
+
|
1566
|
+
# We initialize the table of hyperbolicity. We use an array of unsigned long
|
1567
|
+
# int instead of a dictionary since it is much faster.
|
1568
|
+
cdef uint64_t* hdistr = <uint64_t*>check_calloc(N + 1, sizeof(uint64_t))
|
1569
|
+
if not hdistr:
|
1570
|
+
raise MemoryError
|
1571
|
+
|
1572
|
+
# We now compute the hyperbolicity of each quadruple
|
1573
|
+
for j in range(sampling_size):
|
1574
|
+
a = c_libc_random() % N
|
1575
|
+
b = c_libc_random() % N
|
1576
|
+
c = c_libc_random() % N
|
1577
|
+
d = c_libc_random() % N
|
1578
|
+
while a == b:
|
1579
|
+
b = c_libc_random() % N
|
1580
|
+
while a == c or b == c:
|
1581
|
+
c = c_libc_random() % N
|
1582
|
+
while a == d or b == d or c == d:
|
1583
|
+
d = c_libc_random() % N
|
1584
|
+
|
1585
|
+
hdistr[__hyp__(distances, a, b, c, d)] += 1
|
1586
|
+
|
1587
|
+
# We prepare the dictionary of hyperbolicity distribution from sampling
|
1588
|
+
cdef dict hdict = {ZZ(i)/2: ZZ(hdistr[i])/ZZ(sampling_size) for i in range(N + 1) if hdistr[i] > 0}
|
1589
|
+
|
1590
|
+
sig_free(hdistr)
|
1591
|
+
|
1592
|
+
return hdict
|
1593
|
+
|
1594
|
+
|
1595
|
+
def hyperbolicity_distribution(G, algorithm='sampling', sampling_size=10**6):
|
1596
|
+
r"""
|
1597
|
+
Return the hyperbolicity distribution of the graph or a sampling of it.
|
1598
|
+
|
1599
|
+
The hyperbolicity of a graph has been defined by Gromov [Gro1987]_ as
|
1600
|
+
follows: Let `a, b, c, d` be vertices of the graph, let `S_1 = dist(a, b) +
|
1601
|
+
dist(b, c)`, `S_2 = dist(a, c) + dist(b, d)`, and `S_3 = dist(a, d) +
|
1602
|
+
dist(b, c)`, and let `M_1` and `M_2` be the two largest values among `S_1`,
|
1603
|
+
`S_2`, and `S_3`. We have `hyp(a, b, c, d) = |M_1 - M_2|`, and the
|
1604
|
+
hyperbolicity of the graph is the maximum over all possible 4-tuples `(a, b,
|
1605
|
+
c, d)` divided by 2.
|
1606
|
+
|
1607
|
+
The computation of the hyperbolicity of each 4-tuple, and so the
|
1608
|
+
hyperbolicity distribution, takes time in `O( n^4 )`.
|
1609
|
+
|
1610
|
+
INPUT:
|
1611
|
+
|
1612
|
+
- ``G`` -- a Graph
|
1613
|
+
|
1614
|
+
- ``algorithm`` -- (default: ``'sampling'``) when algorithm is 'sampling', it
|
1615
|
+
returns the distribution of the hyperbolicity over a sample of
|
1616
|
+
``sampling_size`` 4-tuples. When algorithm is 'exact', it computes the
|
1617
|
+
distribution of the hyperbolicity over all 4-tuples. Be aware that the
|
1618
|
+
computation time can be HUGE.
|
1619
|
+
|
1620
|
+
- ``sampling_size`` -- (default: `10^6`) number of 4-tuples considered in
|
1621
|
+
the sampling. Used only when ``algorithm == 'sampling'``
|
1622
|
+
|
1623
|
+
OUTPUT:
|
1624
|
+
|
1625
|
+
- ``hdict`` -- dictionary such that hdict[i] is the number of 4-tuples of
|
1626
|
+
hyperbolicity i
|
1627
|
+
|
1628
|
+
EXAMPLES:
|
1629
|
+
|
1630
|
+
Exact hyperbolicity distribution of the Petersen Graph::
|
1631
|
+
|
1632
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity_distribution
|
1633
|
+
sage: G = graphs.PetersenGraph()
|
1634
|
+
sage: hyperbolicity_distribution(G,algorithm='exact')
|
1635
|
+
{0: 3/7, 1/2: 4/7}
|
1636
|
+
|
1637
|
+
Exact hyperbolicity distribution of a `3\times 3` grid::
|
1638
|
+
|
1639
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity_distribution
|
1640
|
+
sage: G = graphs.GridGraph([3,3])
|
1641
|
+
sage: hyperbolicity_distribution(G,algorithm='exact')
|
1642
|
+
{0: 11/18, 1: 8/21, 2: 1/126}
|
1643
|
+
|
1644
|
+
TESTS:
|
1645
|
+
|
1646
|
+
Giving anything else than a Graph::
|
1647
|
+
|
1648
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity_distribution
|
1649
|
+
sage: hyperbolicity_distribution([])
|
1650
|
+
Traceback (most recent call last):
|
1651
|
+
...
|
1652
|
+
ValueError: the input parameter must be a Graph
|
1653
|
+
|
1654
|
+
Giving a non connected graph::
|
1655
|
+
|
1656
|
+
sage: from sage.graphs.hyperbolicity import hyperbolicity_distribution
|
1657
|
+
sage: G = Graph([(0,1),(2,3)])
|
1658
|
+
sage: hyperbolicity_distribution(G)
|
1659
|
+
Traceback (most recent call last):
|
1660
|
+
...
|
1661
|
+
ValueError: the input Graph must be connected
|
1662
|
+
"""
|
1663
|
+
from sage.graphs.graph import Graph
|
1664
|
+
if not isinstance(G, Graph):
|
1665
|
+
raise ValueError("the input parameter must be a Graph")
|
1666
|
+
# The hyperbolicity is defined on connected graphs
|
1667
|
+
if not G.is_connected():
|
1668
|
+
raise ValueError("the input Graph must be connected")
|
1669
|
+
|
1670
|
+
# The hyperbolicity distribution of some classes of graphs is known. If it
|
1671
|
+
# is easy and fast to test that a graph belongs to one of these classes, we
|
1672
|
+
# do it.
|
1673
|
+
if (G.num_verts() == G.num_edges() + 1) or G.is_clique():
|
1674
|
+
return {0: sampling_size if algorithm=='sampling' else binomial(G.num_verts(), 4)}
|
1675
|
+
|
1676
|
+
cdef int N = G.num_verts()
|
1677
|
+
cdef int i
|
1678
|
+
cdef unsigned short** distances
|
1679
|
+
cdef unsigned short* _distances_
|
1680
|
+
cdef dict hdict
|
1681
|
+
|
1682
|
+
# We compute the all pairs shortest path and store the result in a 2D array
|
1683
|
+
# for faster access.
|
1684
|
+
_distances_ = c_distances_all_pairs(G, vertex_list=list(G))
|
1685
|
+
distances = <unsigned short**>check_allocarray(N, sizeof(unsigned short*))
|
1686
|
+
if not distances:
|
1687
|
+
sig_free(_distances_)
|
1688
|
+
raise MemoryError
|
1689
|
+
|
1690
|
+
for i in range(N):
|
1691
|
+
distances[i] = _distances_ + i * N
|
1692
|
+
|
1693
|
+
if algorithm == 'exact':
|
1694
|
+
hdict = __hyperbolicity_distribution__(N, distances)
|
1695
|
+
elif algorithm == 'sampling':
|
1696
|
+
hdict = __hyperbolicity_sampling__(N, distances, sampling_size)
|
1697
|
+
else:
|
1698
|
+
raise ValueError("algorithm '%s' not yet implemented, please contribute" % (algorithm))
|
1699
|
+
|
1700
|
+
# We release memory
|
1701
|
+
sig_free(distances)
|
1702
|
+
sig_free(_distances_)
|
1703
|
+
|
1704
|
+
return hdict
|