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,2938 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
# cython: binding=True
|
3
|
+
r"""
|
4
|
+
Distances/shortest paths between all pairs of vertices
|
5
|
+
|
6
|
+
This module implements a few functions that deal with the computation of
|
7
|
+
distances or shortest paths between all pairs of vertices.
|
8
|
+
|
9
|
+
**Efficiency** : Because these functions involve listing many times the
|
10
|
+
(out)-neighborhoods of (di)-graphs, it is useful in terms of efficiency to build
|
11
|
+
a temporary copy of the graph in a data structure that makes it easy to compute
|
12
|
+
quickly. These functions also work on large volume of data, typically dense
|
13
|
+
matrices of size `n^2`, and are expected to return corresponding dictionaries of
|
14
|
+
size `n^2`, where the integers corresponding to the vertices have first been
|
15
|
+
converted to the vertices' labels. Sadly, this last translating operation turns
|
16
|
+
out to be the most time-consuming, and for this reason it is also nice to have a
|
17
|
+
Cython module, and version of these functions that return C arrays, in order to
|
18
|
+
avoid these operations when they are not necessary.
|
19
|
+
|
20
|
+
**Memory cost** : The methods implemented in the current module sometimes need
|
21
|
+
large amounts of memory to return their result. Storing the distances between
|
22
|
+
all pairs of vertices in a graph on `1500` vertices as a dictionary of
|
23
|
+
dictionaries takes around 200MB, while storing the same information as a C array
|
24
|
+
requires 4MB.
|
25
|
+
|
26
|
+
|
27
|
+
The module's main function
|
28
|
+
--------------------------
|
29
|
+
|
30
|
+
The C function ``all_pairs_shortest_path_BFS`` actually does all the
|
31
|
+
computations, and all the others (except for ``Floyd_Warshall``) are just
|
32
|
+
wrapping it. This function begins with copying the graph in a data structure
|
33
|
+
that makes it fast to query the out-neighbors of a vertex, then starts one
|
34
|
+
Breadth First Search per vertex of the (di)graph.
|
35
|
+
|
36
|
+
**What can this function compute ?**
|
37
|
+
|
38
|
+
- The matrix of predecessors.
|
39
|
+
|
40
|
+
This matrix `P` has size `n^2`, and is such that vertex `P[u,v]` is a
|
41
|
+
predecessor of `v` on a shortest `uv`-path. Hence, this matrix efficiently
|
42
|
+
encodes the information of a shortest `uv`-path for any `u,v\in G` : indeed,
|
43
|
+
to go from `u` to `v` you should first find a shortest `uP[u,v]`-path, then
|
44
|
+
jump from `P[u,v]` to `v` as it is one of its outneighbors. Apply recursively
|
45
|
+
and find out what the whole path is !.
|
46
|
+
|
47
|
+
- The matrix of distances.
|
48
|
+
|
49
|
+
This matrix has size `n^2` and associates to any `uv` the distance
|
50
|
+
from `u` to `v`.
|
51
|
+
|
52
|
+
- The vector of eccentricities.
|
53
|
+
|
54
|
+
This vector of size `n` encodes for each vertex `v` the distance to vertex
|
55
|
+
which is furthest from `v` in the graph. In particular, the diameter of the
|
56
|
+
graph is the maximum of these values.
|
57
|
+
|
58
|
+
**What does it take as input ?**
|
59
|
+
|
60
|
+
- ``gg`` a (Di)Graph.
|
61
|
+
|
62
|
+
- ``unsigned short * predecessors`` -- a pointer toward an array of size
|
63
|
+
`n^2\cdot\text{sizeof(unsigned short)}`. Set to ``NULL`` if you do not want to
|
64
|
+
compute the predecessors.
|
65
|
+
|
66
|
+
- ``unsigned short * distances`` -- a pointer toward an array of size
|
67
|
+
`n^2\cdot\text{sizeof(unsigned short)}`. The computation of the distances is
|
68
|
+
necessary for the algorithm, so this value can **not** be set to ``NULL``.
|
69
|
+
|
70
|
+
- ``int * eccentricity`` -- a pointer toward an array of size
|
71
|
+
`n\cdot\text{sizeof(int)}`. Set to ``NULL`` if you do not want to compute the
|
72
|
+
eccentricity.
|
73
|
+
|
74
|
+
**Technical details**
|
75
|
+
|
76
|
+
- The vertices are encoded as `1, ..., n` as they appear in the ordering of
|
77
|
+
``G.vertices(sort=True)``, unless another ordering is specified by the user.
|
78
|
+
|
79
|
+
- Because this function works on matrices whose size is quadratic compared to
|
80
|
+
the number of vertices when computing all distances or predecessors, it uses
|
81
|
+
short variables to store the vertices' names instead of long ones to divide by
|
82
|
+
2 the size in memory. This means that only the diameter/eccentricities can be
|
83
|
+
computed on a graph of more than 65536 nodes. For information, the current
|
84
|
+
version of the algorithm on a graph with `65536=2^{16}` nodes creates in
|
85
|
+
memory `2` tables on `2^{32}` short elements (2bytes each), for a total of
|
86
|
+
`2^{33}` bytes or `8` gigabytes. In order to support larger sizes, we would
|
87
|
+
have to replace shorts by 32-bits int or 64-bits int, which would then require
|
88
|
+
respectively 16GB or 32GB.
|
89
|
+
|
90
|
+
- In the C version of these functions, infinite distances are represented with
|
91
|
+
``<unsigned short> -1 = 65535`` for ``unsigned short`` variables, and by
|
92
|
+
``INT32_MAX`` otherwise. These case happens when the input is a disconnected
|
93
|
+
graph, or a non-strongly-connected digraph.
|
94
|
+
|
95
|
+
- A memory error is raised when data structures allocation failed. This could
|
96
|
+
happen with large graphs on computers with low memory space.
|
97
|
+
|
98
|
+
.. WARNING::
|
99
|
+
|
100
|
+
The function ``all_pairs_shortest_path_BFS`` has **no reason** to be called
|
101
|
+
by the user, even though he would be writing his code in Cython and look for
|
102
|
+
efficiency. This module contains wrappers for this function that feed it
|
103
|
+
with the good parameters. As the function is inlined, using those wrappers
|
104
|
+
actually saves time as it should avoid testing the parameters again and
|
105
|
+
again in the main function's body.
|
106
|
+
|
107
|
+
AUTHOR:
|
108
|
+
|
109
|
+
- Nathann Cohen (2011)
|
110
|
+
- David Coudert (2014) -- 2sweep, multi-sweep and iFUB for diameter computation
|
111
|
+
|
112
|
+
Functions
|
113
|
+
---------
|
114
|
+
"""
|
115
|
+
|
116
|
+
# ****************************************************************************
|
117
|
+
# Copyright (C) 2011 Nathann Cohen <nathann.cohen@gmail.com>
|
118
|
+
#
|
119
|
+
# This program is free software: you can redistribute it and/or modify
|
120
|
+
# it under the terms of the GNU General Public License as published by
|
121
|
+
# the Free Software Foundation, either version 2 of the License, or
|
122
|
+
# (at your option) any later version.
|
123
|
+
# https://www.gnu.org/licenses/
|
124
|
+
# ****************************************************************************
|
125
|
+
|
126
|
+
from libc.string cimport memset
|
127
|
+
from libc.stdint cimport uint64_t, UINT64_MAX
|
128
|
+
from libc.stdint cimport uint32_t, INT32_MAX, UINT32_MAX
|
129
|
+
from cysignals.memory cimport sig_malloc, sig_calloc, sig_free
|
130
|
+
from cysignals.signals cimport sig_on, sig_off
|
131
|
+
from memory_allocator cimport MemoryAllocator
|
132
|
+
|
133
|
+
from sage.data_structures.binary_matrix cimport *
|
134
|
+
from sage.graphs.base.c_graph cimport CGraphBackend
|
135
|
+
from sage.graphs.base.c_graph cimport CGraph
|
136
|
+
from sage.graphs.base.static_sparse_backend cimport StaticSparseCGraph
|
137
|
+
from sage.graphs.base.static_sparse_backend cimport StaticSparseBackend
|
138
|
+
from sage.graphs.base.static_sparse_graph cimport (short_digraph,
|
139
|
+
init_short_digraph,
|
140
|
+
init_reverse,
|
141
|
+
free_short_digraph,
|
142
|
+
out_degree,
|
143
|
+
has_edge,
|
144
|
+
simple_BFS)
|
145
|
+
|
146
|
+
|
147
|
+
cdef inline c_all_pairs_shortest_path_BFS(short_digraph sd,
|
148
|
+
unsigned short* predecessors,
|
149
|
+
unsigned short* distances,
|
150
|
+
uint32_t* eccentricity):
|
151
|
+
r"""
|
152
|
+
See the module's documentation.
|
153
|
+
"""
|
154
|
+
cdef int n = sd.n
|
155
|
+
|
156
|
+
# Computing the predecessors/distances can only be done if we have less than
|
157
|
+
# MAX_UNSIGNED_SHORT vertices. No problem with the eccentricities though as
|
158
|
+
# we store them on an integer vector.
|
159
|
+
if (predecessors or distances) and n > <unsigned short> -1:
|
160
|
+
raise ValueError("The graph backend contains more than " +
|
161
|
+
str(<unsigned short> -1) + " nodes and we cannot " +
|
162
|
+
"compute the matrix of distances/predecessors on " +
|
163
|
+
"something like that !")
|
164
|
+
|
165
|
+
cdef int i
|
166
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
167
|
+
|
168
|
+
# The vertices which have already been visited
|
169
|
+
cdef bitset_t seen
|
170
|
+
bitset_init(seen, n)
|
171
|
+
|
172
|
+
# The list of waiting vertices, the beginning and the end of the list
|
173
|
+
cdef int* waiting_list = <int*> mem.allocarray(n, sizeof(int))
|
174
|
+
cdef int waiting_beginning = 0
|
175
|
+
cdef int waiting_end = 0
|
176
|
+
|
177
|
+
cdef int source
|
178
|
+
cdef int v, u
|
179
|
+
cdef uint32_t* p_tmp
|
180
|
+
cdef uint32_t* end
|
181
|
+
|
182
|
+
cdef unsigned short *c_predecessors = predecessors
|
183
|
+
cdef int* c_distances = <int*> mem.allocarray(n, sizeof(int))
|
184
|
+
|
185
|
+
# The edges are stored in the vector p_edges. This vector contains, from
|
186
|
+
# left to right The list of the first vertex's outneighbors, then the
|
187
|
+
# second's, then the third's, ...
|
188
|
+
#
|
189
|
+
# The outneighbors of vertex i are enumerated from
|
190
|
+
#
|
191
|
+
# p_vertices[i] to p_vertices[i+1] - 1
|
192
|
+
# (if p_vertices[i] is equal to p_vertices[i+1], then i has no outneighbours)
|
193
|
+
#
|
194
|
+
# This data structure is well documented in the module
|
195
|
+
# sage.graphs.base.static_sparse_graph
|
196
|
+
cdef uint32_t** p_vertices = sd.neighbors
|
197
|
+
|
198
|
+
# We run n different BFS taking each vertex as a source
|
199
|
+
for source in range(n):
|
200
|
+
|
201
|
+
# The source is seen
|
202
|
+
bitset_set_first_n(seen, 0)
|
203
|
+
bitset_add(seen, source)
|
204
|
+
|
205
|
+
# Its parameters can already be set
|
206
|
+
c_distances[source] = 0
|
207
|
+
|
208
|
+
if predecessors != NULL:
|
209
|
+
c_predecessors[source] = source
|
210
|
+
|
211
|
+
# and added to the queue
|
212
|
+
waiting_list[0] = source
|
213
|
+
waiting_beginning = 0
|
214
|
+
waiting_end = 0
|
215
|
+
|
216
|
+
# For as long as there are vertices left to explore
|
217
|
+
while waiting_beginning <= waiting_end:
|
218
|
+
|
219
|
+
# We pick the first one
|
220
|
+
v = waiting_list[waiting_beginning]
|
221
|
+
|
222
|
+
p_tmp = p_vertices[v]
|
223
|
+
end = p_vertices[v + 1]
|
224
|
+
|
225
|
+
# Iterating over all the outneighbors u of v
|
226
|
+
while p_tmp < end:
|
227
|
+
u = p_tmp[0]
|
228
|
+
|
229
|
+
# If we notice one of these neighbors is not seen yet, we set
|
230
|
+
# its parameters and add it to the queue to be explored later.
|
231
|
+
if not bitset_in(seen, u):
|
232
|
+
c_distances[u] = c_distances[v] + 1
|
233
|
+
if predecessors:
|
234
|
+
c_predecessors[u] = v
|
235
|
+
bitset_add(seen, u)
|
236
|
+
waiting_end += 1
|
237
|
+
waiting_list[waiting_end] = u
|
238
|
+
|
239
|
+
p_tmp += 1
|
240
|
+
|
241
|
+
waiting_beginning += 1
|
242
|
+
|
243
|
+
# If not all the vertices have been met
|
244
|
+
if bitset_len(seen) < n:
|
245
|
+
bitset_complement(seen, seen)
|
246
|
+
v = bitset_next(seen, 0)
|
247
|
+
while v >= 0:
|
248
|
+
c_distances[v] = INT32_MAX
|
249
|
+
if predecessors:
|
250
|
+
c_predecessors[v] = -1
|
251
|
+
v = bitset_next(seen, v + 1)
|
252
|
+
|
253
|
+
if eccentricity:
|
254
|
+
eccentricity[source] = UINT32_MAX
|
255
|
+
|
256
|
+
elif eccentricity:
|
257
|
+
eccentricity[source] = c_distances[waiting_list[n - 1]]
|
258
|
+
|
259
|
+
if predecessors:
|
260
|
+
c_predecessors += n
|
261
|
+
|
262
|
+
if distances:
|
263
|
+
for i in range(n):
|
264
|
+
distances[i] = <unsigned short> c_distances[i]
|
265
|
+
distances += n
|
266
|
+
|
267
|
+
bitset_free(seen)
|
268
|
+
|
269
|
+
|
270
|
+
cdef inline all_pairs_shortest_path_BFS(gg,
|
271
|
+
unsigned short* predecessors,
|
272
|
+
unsigned short* distances,
|
273
|
+
uint32_t* eccentricity,
|
274
|
+
vertex_list=None):
|
275
|
+
r"""
|
276
|
+
See the module's documentation.
|
277
|
+
|
278
|
+
Optional parameter ``vertex_list`` is a list of `n` vertices
|
279
|
+
specifying a mapping from `(0, \ldots, n-1)` to vertex labels in
|
280
|
+
``gg``. When ``vertex_list`` is ``None`` (default), the mapping is
|
281
|
+
given by the ordering of ``gg.vertices(sort=True)``. When set,
|
282
|
+
``distances[i * n + j]`` is the shortest BFS distance between
|
283
|
+
vertices ``vertex_list[i]`` and ``vertex_list[j]``.
|
284
|
+
"""
|
285
|
+
from sage.rings.infinity import Infinity
|
286
|
+
|
287
|
+
cdef list int_to_vertex
|
288
|
+
if vertex_list is None:
|
289
|
+
int_to_vertex = gg.vertices(sort=True)
|
290
|
+
elif set(gg.vertex_iterator()) == set(vertex_list):
|
291
|
+
int_to_vertex = vertex_list
|
292
|
+
else:
|
293
|
+
raise ValueError("parameter vertex_list is incorrect for this graph")
|
294
|
+
|
295
|
+
cdef int n = gg.order()
|
296
|
+
|
297
|
+
# Computing the predecessors/distances can only be done if we have less than
|
298
|
+
# MAX_UNSIGNED_SHORT vertices. No problem with the eccentricities though as
|
299
|
+
# we store them on an integer vector.
|
300
|
+
if (predecessors or distances) and n > <unsigned short> -1:
|
301
|
+
raise ValueError("The graph backend contains more than "+
|
302
|
+
str(<unsigned short> -1)+" nodes and we cannot "+
|
303
|
+
"compute the matrix of distances/predecessors on "+
|
304
|
+
"something like that !")
|
305
|
+
|
306
|
+
# Copying the whole graph to obtain the list of neighbors quicker than by
|
307
|
+
# calling out_neighbors
|
308
|
+
cdef short_digraph sd
|
309
|
+
init_short_digraph(sd, gg, edge_labelled=False, vertex_list=int_to_vertex)
|
310
|
+
|
311
|
+
c_all_pairs_shortest_path_BFS(sd, predecessors, distances, eccentricity)
|
312
|
+
|
313
|
+
free_short_digraph(sd)
|
314
|
+
|
315
|
+
|
316
|
+
################
|
317
|
+
# Predecessors #
|
318
|
+
################
|
319
|
+
|
320
|
+
cdef unsigned short* c_shortest_path_all_pairs(G, vertex_list=None) except NULL:
|
321
|
+
r"""
|
322
|
+
Return the matrix of predecessors in G.
|
323
|
+
|
324
|
+
The matrix `P` returned has size `n^2`, and is such that vertex `P[u,v]` is
|
325
|
+
a predecessor of `v` on a shortest `uv`-path. Hence, this matrix efficiently
|
326
|
+
encodes the information of a shortest `uv`-path for any `u,v\in G` : indeed,
|
327
|
+
to go from `u` to `v` you should first find a shortest `uP[u,v]`-path, then
|
328
|
+
jump from `P[u,v]` to `v` as it is one of its outneighbors.
|
329
|
+
|
330
|
+
Optional parameter ``vertex_list`` is a list of `n` vertices specifying a
|
331
|
+
mapping from `(0, \ldots, n-1)` to vertex labels in `G`. When
|
332
|
+
``vertex_list`` is ``None`` (default), the mapping is given by the ordering
|
333
|
+
of ``G.vertices(sort=True)``. When set, ``predecessors[i * n + j]`` is the
|
334
|
+
predecessor of ``vertex_list[j]`` on the shortest path from
|
335
|
+
``vertex_list[i]`` to ``vertex_list[j]``.
|
336
|
+
"""
|
337
|
+
|
338
|
+
cdef unsigned int n = G.order()
|
339
|
+
cdef unsigned short* distances = <unsigned short*> sig_malloc(n * n * sizeof(unsigned short))
|
340
|
+
if not distances:
|
341
|
+
raise MemoryError()
|
342
|
+
cdef unsigned short* predecessors = <unsigned short*> sig_malloc(n * n * sizeof(unsigned short))
|
343
|
+
if not predecessors:
|
344
|
+
sig_free(distances)
|
345
|
+
raise MemoryError()
|
346
|
+
all_pairs_shortest_path_BFS(G, predecessors, distances, NULL, vertex_list=vertex_list)
|
347
|
+
|
348
|
+
sig_free(distances)
|
349
|
+
|
350
|
+
return predecessors
|
351
|
+
|
352
|
+
|
353
|
+
def shortest_path_all_pairs(G):
|
354
|
+
r"""
|
355
|
+
Return the matrix of predecessors in G.
|
356
|
+
|
357
|
+
The matrix `P` returned has size `n^2`, and is such that vertex `P[u,v]` is
|
358
|
+
a predecessor of `v` on a shortest `uv`-path. Hence, this matrix efficiently
|
359
|
+
encodes the information of a shortest `uv`-path for any `u,v\in G` : indeed,
|
360
|
+
to go from `u` to `v` you should first find a shortest `uP[u,v]`-path, then
|
361
|
+
jump from `P[u,v]` to `v` as it is one of its outneighbors.
|
362
|
+
|
363
|
+
EXAMPLES::
|
364
|
+
|
365
|
+
sage: from sage.graphs.distances_all_pairs import shortest_path_all_pairs
|
366
|
+
sage: g = graphs.PetersenGraph()
|
367
|
+
sage: shortest_path_all_pairs(g)
|
368
|
+
{0: {0: None, 1: 0, 2: 1, 3: 4, 4: 0, 5: 0, 6: 1, 7: 5, 8: 5, 9: 4},
|
369
|
+
1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0, 5: 0, 6: 1, 7: 2, 8: 6, 9: 6},
|
370
|
+
2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3, 5: 7, 6: 1, 7: 2, 8: 3, 9: 7},
|
371
|
+
3: {0: 4, 1: 2, 2: 3, 3: None, 4: 3, 5: 8, 6: 8, 7: 2, 8: 3, 9: 4},
|
372
|
+
4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None, 5: 0, 6: 9, 7: 9, 8: 3, 9: 4},
|
373
|
+
5: {0: 5, 1: 0, 2: 7, 3: 8, 4: 0, 5: None, 6: 8, 7: 5, 8: 5, 9: 7},
|
374
|
+
6: {0: 1, 1: 6, 2: 1, 3: 8, 4: 9, 5: 8, 6: None, 7: 9, 8: 6, 9: 6},
|
375
|
+
7: {0: 5, 1: 2, 2: 7, 3: 2, 4: 9, 5: 7, 6: 9, 7: None, 8: 5, 9: 7},
|
376
|
+
8: {0: 5, 1: 6, 2: 3, 3: 8, 4: 3, 5: 8, 6: 8, 7: 5, 8: None, 9: 6},
|
377
|
+
9: {0: 4, 1: 6, 2: 7, 3: 4, 4: 9, 5: 7, 6: 9, 7: 9, 8: 6, 9: None}}
|
378
|
+
"""
|
379
|
+
cdef int n = G.order()
|
380
|
+
|
381
|
+
if not n:
|
382
|
+
return {}
|
383
|
+
|
384
|
+
# The order of vertices must be the same as in init_short_digraph
|
385
|
+
cdef list int_to_vertex = list(G)
|
386
|
+
cdef unsigned short* predecessors = c_shortest_path_all_pairs(G, vertex_list=int_to_vertex)
|
387
|
+
cdef unsigned short* c_predecessors = predecessors
|
388
|
+
|
389
|
+
cdef dict d = {}
|
390
|
+
cdef dict d_tmp
|
391
|
+
|
392
|
+
cdef int i, j
|
393
|
+
|
394
|
+
for j in range(n):
|
395
|
+
d_tmp = {}
|
396
|
+
for i in range(n):
|
397
|
+
if c_predecessors[i] == <unsigned short> -1:
|
398
|
+
d_tmp[int_to_vertex[i]] = None
|
399
|
+
else:
|
400
|
+
d_tmp[int_to_vertex[i]] = int_to_vertex[c_predecessors[i]]
|
401
|
+
|
402
|
+
d_tmp[int_to_vertex[j]] = None
|
403
|
+
d[int_to_vertex[j]] = d_tmp
|
404
|
+
|
405
|
+
c_predecessors += n
|
406
|
+
|
407
|
+
sig_free(predecessors)
|
408
|
+
return d
|
409
|
+
|
410
|
+
|
411
|
+
#############
|
412
|
+
# Distances #
|
413
|
+
#############
|
414
|
+
|
415
|
+
cdef unsigned short * c_distances_all_pairs(G, vertex_list=None) noexcept:
|
416
|
+
r"""
|
417
|
+
Return the matrix of distances in G.
|
418
|
+
|
419
|
+
The matrix `M` returned is of length `n^2`, and the distance between
|
420
|
+
vertices `u` and `v` is `M[u,v]`. The integer corresponding to a vertex is
|
421
|
+
its index in the list ``G.vertices(sort=True)`` unless parameter
|
422
|
+
``vertex_list`` is set.
|
423
|
+
|
424
|
+
Optional parameter ``vertex_list`` is a list of `n` vertices specifying a
|
425
|
+
mapping from `(0, \ldots, n-1)` to vertex labels in `G`. When set,
|
426
|
+
``distances[i * n + j]`` is the shortest BFS distance between vertices
|
427
|
+
``vertex_list[i]`` and ``vertex_list[j]``.
|
428
|
+
"""
|
429
|
+
cdef unsigned int n = G.order()
|
430
|
+
cdef unsigned short* distances = <unsigned short*> sig_malloc(n * n * sizeof(unsigned short))
|
431
|
+
if not distances:
|
432
|
+
raise MemoryError()
|
433
|
+
all_pairs_shortest_path_BFS(G, NULL, distances, NULL, vertex_list=vertex_list)
|
434
|
+
|
435
|
+
return distances
|
436
|
+
|
437
|
+
|
438
|
+
def distances_all_pairs(G):
|
439
|
+
r"""
|
440
|
+
Return the matrix of distances in G.
|
441
|
+
|
442
|
+
This function returns a double dictionary ``D`` of vertices, in which the
|
443
|
+
distance between vertices ``u`` and ``v`` is ``D[u][v]``.
|
444
|
+
|
445
|
+
EXAMPLES::
|
446
|
+
|
447
|
+
sage: from sage.graphs.distances_all_pairs import distances_all_pairs
|
448
|
+
sage: g = graphs.PetersenGraph()
|
449
|
+
sage: distances_all_pairs(g)
|
450
|
+
{0: {0: 0, 1: 1, 2: 2, 3: 2, 4: 1, 5: 1, 6: 2, 7: 2, 8: 2, 9: 2},
|
451
|
+
1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 2, 5: 2, 6: 1, 7: 2, 8: 2, 9: 2},
|
452
|
+
2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 2, 5: 2, 6: 2, 7: 1, 8: 2, 9: 2},
|
453
|
+
3: {0: 2, 1: 2, 2: 1, 3: 0, 4: 1, 5: 2, 6: 2, 7: 2, 8: 1, 9: 2},
|
454
|
+
4: {0: 1, 1: 2, 2: 2, 3: 1, 4: 0, 5: 2, 6: 2, 7: 2, 8: 2, 9: 1},
|
455
|
+
5: {0: 1, 1: 2, 2: 2, 3: 2, 4: 2, 5: 0, 6: 2, 7: 1, 8: 1, 9: 2},
|
456
|
+
6: {0: 2, 1: 1, 2: 2, 3: 2, 4: 2, 5: 2, 6: 0, 7: 2, 8: 1, 9: 1},
|
457
|
+
7: {0: 2, 1: 2, 2: 1, 3: 2, 4: 2, 5: 1, 6: 2, 7: 0, 8: 2, 9: 1},
|
458
|
+
8: {0: 2, 1: 2, 2: 2, 3: 1, 4: 2, 5: 1, 6: 1, 7: 2, 8: 0, 9: 2},
|
459
|
+
9: {0: 2, 1: 2, 2: 2, 3: 2, 4: 1, 5: 2, 6: 1, 7: 1, 8: 2, 9: 0}}
|
460
|
+
"""
|
461
|
+
from sage.rings.infinity import Infinity
|
462
|
+
|
463
|
+
cdef int n = G.order()
|
464
|
+
|
465
|
+
if not n:
|
466
|
+
return {}
|
467
|
+
|
468
|
+
# The order of vertices must be the same as in init_short_digraph
|
469
|
+
cdef list int_to_vertex = list(G)
|
470
|
+
|
471
|
+
cdef unsigned short* distances = c_distances_all_pairs(G, vertex_list=int_to_vertex)
|
472
|
+
cdef unsigned short* c_distances = distances
|
473
|
+
|
474
|
+
cdef dict d = {}
|
475
|
+
cdef dict d_tmp
|
476
|
+
|
477
|
+
cdef int i, j
|
478
|
+
|
479
|
+
for j in range(n):
|
480
|
+
d_tmp = {}
|
481
|
+
for i in range(n):
|
482
|
+
if c_distances[i] == <unsigned short> -1:
|
483
|
+
d_tmp[int_to_vertex[i]] = Infinity
|
484
|
+
else:
|
485
|
+
d_tmp[int_to_vertex[i]] = c_distances[i]
|
486
|
+
|
487
|
+
d[int_to_vertex[j]] = d_tmp
|
488
|
+
c_distances += n
|
489
|
+
|
490
|
+
sig_free(distances)
|
491
|
+
return d
|
492
|
+
|
493
|
+
|
494
|
+
def is_distance_regular(G, parameters=False):
|
495
|
+
r"""
|
496
|
+
Test if the graph is distance-regular.
|
497
|
+
|
498
|
+
A graph `G` is distance-regular if for any integers `j,k` the value of
|
499
|
+
`|\{x:d_G(x,u)=j,x\in V(G)\} \cap \{y:d_G(y,v)=j,y\in V(G)\}|` is constant
|
500
|
+
for any two vertices `u,v\in V(G)` at distance `i` from each other. In
|
501
|
+
particular `G` is regular, of degree `b_0` (see below), as one can take
|
502
|
+
`u=v`.
|
503
|
+
|
504
|
+
Equivalently a graph is distance-regular if there exist integers `b_i,c_i`
|
505
|
+
such that for any two vertices `u,v` at distance `i` we have
|
506
|
+
|
507
|
+
* `b_i = |\{x:d_G(x,u)=i+1,x\in V(G)\}\cap N_G(v)\}|, \ 0\leq i\leq d-1`
|
508
|
+
* `c_i = |\{x:d_G(x,u)=i-1,x\in V(G)\}\cap N_G(v)\}|, \ 1\leq i\leq d,`
|
509
|
+
|
510
|
+
where `d` is the diameter of the graph. For more information on
|
511
|
+
distance-regular graphs, see the :wikipedia:`Distance-regular_graph`.
|
512
|
+
|
513
|
+
INPUT:
|
514
|
+
|
515
|
+
- ``parameters`` -- boolean (default: ``False``); if set to ``True``, the
|
516
|
+
function returns the pair ``(b, c)`` of lists of integers instead of
|
517
|
+
a boolean answer (see the definition above)
|
518
|
+
|
519
|
+
.. SEEALSO::
|
520
|
+
|
521
|
+
* :meth:`~sage.graphs.generic_graph.GenericGraph.is_regular`
|
522
|
+
* :meth:`~Graph.is_strongly_regular`
|
523
|
+
|
524
|
+
EXAMPLES::
|
525
|
+
|
526
|
+
sage: g = graphs.PetersenGraph()
|
527
|
+
sage: g.is_distance_regular()
|
528
|
+
True
|
529
|
+
sage: g.is_distance_regular(parameters = True)
|
530
|
+
([3, 2, None], [None, 1, 1])
|
531
|
+
|
532
|
+
Cube graphs, which are not strongly regular, are a bit more interesting::
|
533
|
+
|
534
|
+
sage: graphs.CubeGraph(4).is_distance_regular()
|
535
|
+
True
|
536
|
+
sage: graphs.OddGraph(5).is_distance_regular()
|
537
|
+
True
|
538
|
+
|
539
|
+
Disconnected graph::
|
540
|
+
|
541
|
+
sage: (2*graphs.CubeGraph(4)).is_distance_regular()
|
542
|
+
True
|
543
|
+
|
544
|
+
TESTS::
|
545
|
+
|
546
|
+
sage: graphs.PathGraph(2).is_distance_regular(parameters=True)
|
547
|
+
([1, None], [None, 1])
|
548
|
+
sage: graphs.Tutte12Cage().is_distance_regular(parameters=True) # needs networkx
|
549
|
+
([3, 2, 2, 2, 2, 2, None], [None, 1, 1, 1, 1, 1, 3])
|
550
|
+
"""
|
551
|
+
cdef int i, u, v, d, b, c, k
|
552
|
+
cdef int n = G.order()
|
553
|
+
cdef int infinity = <unsigned short> -1
|
554
|
+
|
555
|
+
if n <= 1:
|
556
|
+
return ([], []) if parameters else True
|
557
|
+
|
558
|
+
if not G.is_regular():
|
559
|
+
return False
|
560
|
+
k = G.degree(next(G.vertex_iterator()))
|
561
|
+
|
562
|
+
# Matrix of distances
|
563
|
+
cdef unsigned short* distance_matrix = c_distances_all_pairs(G, vertex_list=list(G))
|
564
|
+
|
565
|
+
# The diameter, i.e. the longest *finite* distance between two vertices
|
566
|
+
cdef int diameter = 0
|
567
|
+
for i in range(n * n):
|
568
|
+
if distance_matrix[i] > diameter and distance_matrix[i] != infinity:
|
569
|
+
diameter = distance_matrix[i]
|
570
|
+
|
571
|
+
cdef bitset_t b_tmp
|
572
|
+
bitset_init(b_tmp, n)
|
573
|
+
|
574
|
+
# b_distance_matrix[d*n+v] is the set of vertices at distance d from v.
|
575
|
+
cdef binary_matrix_t b_distance_matrix
|
576
|
+
try:
|
577
|
+
binary_matrix_init(b_distance_matrix, n * (diameter + 2), n)
|
578
|
+
except MemoryError:
|
579
|
+
sig_free(distance_matrix)
|
580
|
+
bitset_free(b_tmp)
|
581
|
+
raise
|
582
|
+
|
583
|
+
# Fills b_distance_matrix
|
584
|
+
for u in range(n):
|
585
|
+
for v in range(u, n):
|
586
|
+
d = distance_matrix[u * n + v]
|
587
|
+
if d != infinity:
|
588
|
+
binary_matrix_set1(b_distance_matrix, d * n + u, v)
|
589
|
+
binary_matrix_set1(b_distance_matrix, d * n + v, u)
|
590
|
+
|
591
|
+
cdef list bi = [-1 for i in range(diameter + 1)]
|
592
|
+
cdef list ci = [-1 for i in range(diameter + 1)]
|
593
|
+
|
594
|
+
# Applying the definition with b_i,c_i
|
595
|
+
for u in range(n):
|
596
|
+
for v in range(n):
|
597
|
+
if u == v:
|
598
|
+
continue
|
599
|
+
|
600
|
+
d = distance_matrix[u * n + v]
|
601
|
+
if d == infinity:
|
602
|
+
continue
|
603
|
+
|
604
|
+
# Computations of b_d and c_d for u,v. We intersect sets stored in
|
605
|
+
# b_distance_matrix.
|
606
|
+
bitset_and(b_tmp, b_distance_matrix.rows[(d + 1) * n + u], b_distance_matrix.rows[n + v])
|
607
|
+
b = bitset_len(b_tmp)
|
608
|
+
bitset_and(b_tmp, b_distance_matrix.rows[(d - 1) * n + u], b_distance_matrix.rows[n + v])
|
609
|
+
c = bitset_len(b_tmp)
|
610
|
+
|
611
|
+
# Consistency of b_d and c_d
|
612
|
+
if bi[d] == -1:
|
613
|
+
bi[d] = b
|
614
|
+
ci[d] = c
|
615
|
+
|
616
|
+
elif bi[d] != b or ci[d] != c:
|
617
|
+
sig_free(distance_matrix)
|
618
|
+
binary_matrix_free(b_distance_matrix)
|
619
|
+
bitset_free(b_tmp)
|
620
|
+
return False
|
621
|
+
|
622
|
+
sig_free(distance_matrix)
|
623
|
+
binary_matrix_free(b_distance_matrix)
|
624
|
+
bitset_free(b_tmp)
|
625
|
+
|
626
|
+
if parameters:
|
627
|
+
bi[0] = k
|
628
|
+
bi[diameter] = None
|
629
|
+
ci[0] = None
|
630
|
+
return bi, ci
|
631
|
+
return True
|
632
|
+
|
633
|
+
|
634
|
+
###################################
|
635
|
+
# Both distances and predecessors #
|
636
|
+
###################################
|
637
|
+
|
638
|
+
def distances_and_predecessors_all_pairs(G):
|
639
|
+
r"""
|
640
|
+
Return the matrix of distances in G and the matrix of predecessors.
|
641
|
+
|
642
|
+
Distances : the matrix `M` returned is of length `n^2`, and the distance
|
643
|
+
between vertices `u` and `v` is `M[u,v]`. The integer corresponding to a
|
644
|
+
vertex is its index in the list ``G.vertices(sort=True)``.
|
645
|
+
|
646
|
+
Predecessors : the matrix `P` returned has size `n^2`, and is such that
|
647
|
+
vertex `P[u,v]` is a predecessor of `v` on a shortest `uv`-path. Hence, this
|
648
|
+
matrix efficiently encodes the information of a shortest `uv`-path for any
|
649
|
+
`u,v\in G` : indeed, to go from `u` to `v` you should first find a shortest
|
650
|
+
`uP[u,v]`-path, then jump from `P[u,v]` to `v` as it is one of its
|
651
|
+
outneighbors.
|
652
|
+
|
653
|
+
EXAMPLES::
|
654
|
+
|
655
|
+
sage: from sage.graphs.distances_all_pairs import distances_and_predecessors_all_pairs
|
656
|
+
sage: g = graphs.PetersenGraph()
|
657
|
+
sage: distances_and_predecessors_all_pairs(g)
|
658
|
+
({0: {0: 0, 1: 1, 2: 2, 3: 2, 4: 1, 5: 1, 6: 2, 7: 2, 8: 2, 9: 2},
|
659
|
+
1: {0: 1, 1: 0, 2: 1, 3: 2, 4: 2, 5: 2, 6: 1, 7: 2, 8: 2, 9: 2},
|
660
|
+
2: {0: 2, 1: 1, 2: 0, 3: 1, 4: 2, 5: 2, 6: 2, 7: 1, 8: 2, 9: 2},
|
661
|
+
3: {0: 2, 1: 2, 2: 1, 3: 0, 4: 1, 5: 2, 6: 2, 7: 2, 8: 1, 9: 2},
|
662
|
+
4: {0: 1, 1: 2, 2: 2, 3: 1, 4: 0, 5: 2, 6: 2, 7: 2, 8: 2, 9: 1},
|
663
|
+
5: {0: 1, 1: 2, 2: 2, 3: 2, 4: 2, 5: 0, 6: 2, 7: 1, 8: 1, 9: 2},
|
664
|
+
6: {0: 2, 1: 1, 2: 2, 3: 2, 4: 2, 5: 2, 6: 0, 7: 2, 8: 1, 9: 1},
|
665
|
+
7: {0: 2, 1: 2, 2: 1, 3: 2, 4: 2, 5: 1, 6: 2, 7: 0, 8: 2, 9: 1},
|
666
|
+
8: {0: 2, 1: 2, 2: 2, 3: 1, 4: 2, 5: 1, 6: 1, 7: 2, 8: 0, 9: 2},
|
667
|
+
9: {0: 2, 1: 2, 2: 2, 3: 2, 4: 1, 5: 2, 6: 1, 7: 1, 8: 2, 9: 0}},
|
668
|
+
{0: {0: None, 1: 0, 2: 1, 3: 4, 4: 0, 5: 0, 6: 1, 7: 5, 8: 5, 9: 4},
|
669
|
+
1: {0: 1, 1: None, 2: 1, 3: 2, 4: 0, 5: 0, 6: 1, 7: 2, 8: 6, 9: 6},
|
670
|
+
2: {0: 1, 1: 2, 2: None, 3: 2, 4: 3, 5: 7, 6: 1, 7: 2, 8: 3, 9: 7},
|
671
|
+
3: {0: 4, 1: 2, 2: 3, 3: None, 4: 3, 5: 8, 6: 8, 7: 2, 8: 3, 9: 4},
|
672
|
+
4: {0: 4, 1: 0, 2: 3, 3: 4, 4: None, 5: 0, 6: 9, 7: 9, 8: 3, 9: 4},
|
673
|
+
5: {0: 5, 1: 0, 2: 7, 3: 8, 4: 0, 5: None, 6: 8, 7: 5, 8: 5, 9: 7},
|
674
|
+
6: {0: 1, 1: 6, 2: 1, 3: 8, 4: 9, 5: 8, 6: None, 7: 9, 8: 6, 9: 6},
|
675
|
+
7: {0: 5, 1: 2, 2: 7, 3: 2, 4: 9, 5: 7, 6: 9, 7: None, 8: 5, 9: 7},
|
676
|
+
8: {0: 5, 1: 6, 2: 3, 3: 8, 4: 3, 5: 8, 6: 8, 7: 5, 8: None, 9: 6},
|
677
|
+
9: {0: 4, 1: 6, 2: 7, 3: 4, 4: 9, 5: 7, 6: 9, 7: 9, 8: 6, 9: None}})
|
678
|
+
"""
|
679
|
+
from sage.rings.infinity import Infinity
|
680
|
+
cdef unsigned int n = G.order()
|
681
|
+
|
682
|
+
if not n:
|
683
|
+
return {}, {}
|
684
|
+
|
685
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
686
|
+
cdef unsigned short* c_distances = <unsigned short*> mem.malloc(n * n * sizeof(unsigned short))
|
687
|
+
cdef unsigned short* c_predecessor = <unsigned short*> mem.malloc(n * n * sizeof(unsigned short))
|
688
|
+
|
689
|
+
# The order of vertices must be the same as in init_short_digraph
|
690
|
+
cdef list int_to_vertex = list(G)
|
691
|
+
|
692
|
+
all_pairs_shortest_path_BFS(G, c_predecessor, c_distances, NULL, vertex_list=int_to_vertex)
|
693
|
+
|
694
|
+
cdef dict d_distance = {}
|
695
|
+
cdef dict d_predecessor = {}
|
696
|
+
cdef dict t_distance = {}
|
697
|
+
cdef dict t_predecessor = {}
|
698
|
+
|
699
|
+
cdef unsigned int i, j
|
700
|
+
|
701
|
+
for j in range(n):
|
702
|
+
t_distance = {}
|
703
|
+
t_predecessor = {}
|
704
|
+
|
705
|
+
for i in range(n):
|
706
|
+
|
707
|
+
if c_distances[i] != <unsigned short> -1:
|
708
|
+
t_distance[int_to_vertex[i]] = c_distances[i]
|
709
|
+
t_predecessor[int_to_vertex[i]] = int_to_vertex[c_predecessor[i]]
|
710
|
+
|
711
|
+
t_predecessor[int_to_vertex[j]] = None
|
712
|
+
|
713
|
+
d_distance[int_to_vertex[j]] = t_distance
|
714
|
+
d_predecessor[int_to_vertex[j]] = t_predecessor
|
715
|
+
|
716
|
+
c_distances += n
|
717
|
+
c_predecessor += n
|
718
|
+
|
719
|
+
return d_distance, d_predecessor
|
720
|
+
|
721
|
+
|
722
|
+
################
|
723
|
+
# Eccentricity #
|
724
|
+
################
|
725
|
+
|
726
|
+
cdef uint32_t * c_eccentricity(G, vertex_list=None) except NULL:
|
727
|
+
r"""
|
728
|
+
Return the vector of eccentricities in G.
|
729
|
+
|
730
|
+
The array returned is of length `n`, and by default its `i`-th component is
|
731
|
+
the eccentricity of the `i`-th vertex in ``G.vertices(sort=True)``.
|
732
|
+
|
733
|
+
Optional parameter ``vertex_list`` is a list of `n` vertices specifying a
|
734
|
+
mapping from `(0, \ldots, n-1)` to vertex labels in `G`. When set,
|
735
|
+
``ecc[i]`` is the eccentricity of vertex ``vertex_list[i]``.
|
736
|
+
"""
|
737
|
+
cdef unsigned int n = G.order()
|
738
|
+
|
739
|
+
cdef uint32_t * ecc = <uint32_t *> sig_calloc(n, sizeof(uint32_t))
|
740
|
+
if not ecc:
|
741
|
+
raise MemoryError()
|
742
|
+
all_pairs_shortest_path_BFS(G, NULL, NULL, ecc, vertex_list=vertex_list)
|
743
|
+
|
744
|
+
return ecc
|
745
|
+
|
746
|
+
|
747
|
+
cdef uint32_t * c_eccentricity_bounding(short_digraph sd) except NULL:
|
748
|
+
r"""
|
749
|
+
Return the vector of eccentricities using the algorithm of [TK2013]_.
|
750
|
+
|
751
|
+
The array returned is of length `n`, and its `i`-th component is the
|
752
|
+
eccentricity of vertex `i` in ``sd``.
|
753
|
+
|
754
|
+
This method assumes that ``sd`` is an undirected graph.
|
755
|
+
|
756
|
+
The algorithm proposed in [TK2013]_ is based on the observation that for all
|
757
|
+
nodes `v,w\in V`, we have `\max(ecc[v]-d(v,w), d(v,w))\leq ecc[w] \leq
|
758
|
+
ecc[v] + d(v,w)`. Also the algorithms iteratively improves upper and lower
|
759
|
+
bounds on the eccentricity of each node until no further improvements can be
|
760
|
+
done. This algorithm offers good running time reduction on scale-free graphs.
|
761
|
+
"""
|
762
|
+
cdef unsigned int n = sd.n
|
763
|
+
|
764
|
+
# allocated some data structures
|
765
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
766
|
+
cdef uint32_t * distances = <uint32_t *> mem.malloc(3 * n * sizeof(uint32_t))
|
767
|
+
cdef uint32_t * LB = <uint32_t *> sig_calloc(n, sizeof(uint32_t))
|
768
|
+
if not distances or not LB:
|
769
|
+
sig_free(LB)
|
770
|
+
free_short_digraph(sd)
|
771
|
+
raise MemoryError()
|
772
|
+
cdef uint32_t * waiting_list = distances + n
|
773
|
+
cdef uint32_t * UB = distances + 2 * n
|
774
|
+
memset(UB, -1, n * sizeof(uint32_t))
|
775
|
+
cdef bitset_t seen
|
776
|
+
bitset_init(seen, n)
|
777
|
+
|
778
|
+
cdef uint32_t v, w, next_v, cpt = 0
|
779
|
+
|
780
|
+
# The first vertex is the one with largest degree
|
781
|
+
next_v = max((out_degree(sd, v), v) for v in range(n))[1]
|
782
|
+
|
783
|
+
sig_on()
|
784
|
+
while next_v != UINT32_MAX:
|
785
|
+
|
786
|
+
v = next_v
|
787
|
+
cpt += 1
|
788
|
+
|
789
|
+
# Compute the exact eccentricity of v
|
790
|
+
LB[v] = simple_BFS(sd, v, distances, NULL, waiting_list, seen)
|
791
|
+
|
792
|
+
if LB[v] == UINT32_MAX:
|
793
|
+
# The graph is not connected. We set maximum value and exit.
|
794
|
+
for w in range(n):
|
795
|
+
LB[w] = UINT32_MAX
|
796
|
+
break
|
797
|
+
|
798
|
+
# Improve the bounds on the eccentricity of other vertices and select
|
799
|
+
# source of the next BFS
|
800
|
+
next_v = UINT32_MAX
|
801
|
+
for w in range(n):
|
802
|
+
LB[w] = max(LB[w], max(LB[v] - distances[w], distances[w]))
|
803
|
+
UB[w] = min(UB[w], LB[v] + distances[w])
|
804
|
+
if LB[w] == UB[w]:
|
805
|
+
continue
|
806
|
+
elif next_v == UINT32_MAX or (not cpt % 2 and LB[w] < LB[next_v]) or (cpt % 2 and UB[w] > UB[next_v]):
|
807
|
+
# The next vertex is either the vertex with largest upper bound
|
808
|
+
# or smallest lower bound
|
809
|
+
next_v = w
|
810
|
+
|
811
|
+
sig_off()
|
812
|
+
|
813
|
+
bitset_free(seen)
|
814
|
+
|
815
|
+
return LB
|
816
|
+
|
817
|
+
|
818
|
+
cdef uint32_t * c_eccentricity_DHV(short_digraph sd) except NULL:
|
819
|
+
r"""
|
820
|
+
Return the vector of eccentricities using the algorithm of [Dragan2018]_.
|
821
|
+
|
822
|
+
The array returned is of length `n`, and its `i`-th component is the
|
823
|
+
eccentricity of vertex `i` in ``sd``.
|
824
|
+
|
825
|
+
This method assumes that ``sd`` is an undirected graph.
|
826
|
+
|
827
|
+
The algorithm proposed in [Dragan2018]_ is an improvement of the algorithm
|
828
|
+
proposed in [TK2013]_. It is also based on the observation that for all
|
829
|
+
nodes `v,w\in V`, we have `\max(ecc[v]-d(v,w), d(v,w))\leq ecc[w] \leq
|
830
|
+
ecc[v] + d(v,w)`. Also the algorithms iteratively improves upper and lower
|
831
|
+
bounds on the eccentricity of each vertex until no further improvements can
|
832
|
+
be done. The difference with [TK2013]_ is in the order in which improvements
|
833
|
+
are done.
|
834
|
+
|
835
|
+
EXAMPLES::
|
836
|
+
|
837
|
+
sage: from sage.graphs.distances_all_pairs import eccentricity
|
838
|
+
sage: G = graphs.PathGraph(5)
|
839
|
+
sage: eccentricity(G, algorithm='DHV')
|
840
|
+
[4, 3, 2, 3, 4]
|
841
|
+
|
842
|
+
TESTS:
|
843
|
+
|
844
|
+
sage: G = graphs.RandomBarabasiAlbert(50, 2) # needs networkx
|
845
|
+
sage: eccentricity(G, algorithm='bounds') == eccentricity(G, algorithm='DHV') # needs networkx
|
846
|
+
True
|
847
|
+
"""
|
848
|
+
cdef uint32_t n = sd.n
|
849
|
+
if not n:
|
850
|
+
return NULL
|
851
|
+
|
852
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
853
|
+
cdef uint32_t * distances = <uint32_t *> mem.malloc(3 * n * sizeof(uint32_t))
|
854
|
+
# For storing upper bounds on eccentricity of nodes
|
855
|
+
cdef uint32_t * ecc_upper_bound = <uint32_t *>sig_calloc(n, sizeof(uint32_t))
|
856
|
+
if not distances or not ecc_upper_bound:
|
857
|
+
sig_free(ecc_upper_bound)
|
858
|
+
free_short_digraph(sd)
|
859
|
+
raise MemoryError()
|
860
|
+
|
861
|
+
cdef uint32_t * waiting_list = distances + n
|
862
|
+
# For storing lower bounds on eccentricity of nodes
|
863
|
+
cdef uint32_t * ecc_lower_bound = distances + 2 * n
|
864
|
+
memset(ecc_upper_bound, <char>-1, n * sizeof(uint32_t))
|
865
|
+
memset(ecc_lower_bound, 0, n * sizeof(uint32_t))
|
866
|
+
|
867
|
+
cdef uint32_t u, ecc_u
|
868
|
+
cdef uint32_t antipode, ecc_antipode
|
869
|
+
cdef uint32_t v, tmp
|
870
|
+
cdef size_t i, idx
|
871
|
+
cdef bitset_t seen
|
872
|
+
bitset_init(seen, n)
|
873
|
+
|
874
|
+
cdef list active = list(range(n))
|
875
|
+
|
876
|
+
# Algorithm
|
877
|
+
while active:
|
878
|
+
# Select vertex with minimum eccentricity in active and update
|
879
|
+
# eccentricity upper bounds.
|
880
|
+
# For this, we select u with minimum eccentricity lower bound in active
|
881
|
+
# if ecc_u == ecc_lb[u], we are done. Otherwise, we update eccentricity
|
882
|
+
# lower bounds and repeat
|
883
|
+
|
884
|
+
tmp = UINT32_MAX
|
885
|
+
for i, v in enumerate(active):
|
886
|
+
if ecc_lower_bound[v] < tmp:
|
887
|
+
tmp = ecc_lower_bound[v]
|
888
|
+
idx = i
|
889
|
+
active[idx], active[-1] = active[-1], active[idx]
|
890
|
+
u = active.pop()
|
891
|
+
ecc_u = simple_BFS(sd, u, distances, NULL, waiting_list, seen)
|
892
|
+
ecc_upper_bound[u] = ecc_u
|
893
|
+
|
894
|
+
if ecc_u == UINT32_MAX: # Disconnected graph
|
895
|
+
break
|
896
|
+
|
897
|
+
if ecc_u == ecc_lower_bound[u]:
|
898
|
+
# We found the good vertex.
|
899
|
+
# Update eccentricity upper bounds and remove from active those
|
900
|
+
# vertices for which gap is closed
|
901
|
+
i = 0
|
902
|
+
while i < len(active):
|
903
|
+
v = active[i]
|
904
|
+
ecc_upper_bound[v] = min(ecc_upper_bound[v], distances[v] + ecc_u)
|
905
|
+
if ecc_upper_bound[v] == ecc_lower_bound[v]:
|
906
|
+
active[i] = active[-1]
|
907
|
+
active.pop()
|
908
|
+
else:
|
909
|
+
i += 1
|
910
|
+
|
911
|
+
else:
|
912
|
+
# u was not a good choice.
|
913
|
+
# We use its antipode to update eccentricity lower bounds.
|
914
|
+
# Observe that this antipode might have already been seen.
|
915
|
+
antipode = waiting_list[n-1]
|
916
|
+
for i, v in enumerate(active):
|
917
|
+
if v == antipode:
|
918
|
+
active[i] = active[-1]
|
919
|
+
active.pop()
|
920
|
+
break
|
921
|
+
|
922
|
+
ecc_antipode = simple_BFS(sd, antipode, distances, NULL, waiting_list, seen)
|
923
|
+
ecc_upper_bound[antipode] = ecc_antipode
|
924
|
+
|
925
|
+
# Update eccentricity lower bounds and remove from active those
|
926
|
+
# vertices for which the gap is closed
|
927
|
+
i = 0
|
928
|
+
while i < len(active):
|
929
|
+
v = active[i]
|
930
|
+
ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v])
|
931
|
+
if ecc_upper_bound[v] == ecc_lower_bound[v]:
|
932
|
+
active[i] = active[-1]
|
933
|
+
active.pop()
|
934
|
+
else:
|
935
|
+
i += 1
|
936
|
+
|
937
|
+
bitset_free(seen)
|
938
|
+
|
939
|
+
return ecc_upper_bound
|
940
|
+
|
941
|
+
|
942
|
+
def eccentricity(G, algorithm='standard', vertex_list=None):
|
943
|
+
r"""
|
944
|
+
Return the vector of eccentricities in G.
|
945
|
+
|
946
|
+
The array returned is of length `n`, and its `i`-th component is the
|
947
|
+
eccentricity of the `i`-th vertex in ``G.vertices(sort=True)``.
|
948
|
+
|
949
|
+
INPUT:
|
950
|
+
|
951
|
+
- ``G`` -- a Graph or a DiGraph
|
952
|
+
|
953
|
+
- ``algorithm`` -- string (default: ``'standard'``); name of the method used
|
954
|
+
to compute the eccentricity of the vertices
|
955
|
+
|
956
|
+
- ``'standard'`` -- computes eccentricity by performing a BFS from each
|
957
|
+
vertex
|
958
|
+
|
959
|
+
- ``'bounds'`` -- computes eccentricity using the fast algorithm proposed
|
960
|
+
in [TK2013]_ for undirected graphs
|
961
|
+
|
962
|
+
- ``'DHV'`` -- computes all eccentricities of undirected graph using the
|
963
|
+
algorithm proposed in [Dragan2018]_
|
964
|
+
|
965
|
+
- ``vertex_list`` -- list (default: ``None``); a list of `n` vertices
|
966
|
+
specifying a mapping from `(0, \ldots, n-1)` to vertex labels in `G`. When
|
967
|
+
set, ``ecc[i]`` is the eccentricity of vertex ``vertex_list[i]``. When
|
968
|
+
``vertex_list`` is ``None``, ``ecc[i]`` is the eccentricity of vertex
|
969
|
+
``G.vertices(sort=True)[i]``.
|
970
|
+
|
971
|
+
EXAMPLES::
|
972
|
+
|
973
|
+
sage: from sage.graphs.distances_all_pairs import eccentricity
|
974
|
+
sage: g = graphs.PetersenGraph()
|
975
|
+
sage: eccentricity(g)
|
976
|
+
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
|
977
|
+
sage: g.add_edge(0, g.add_vertex())
|
978
|
+
sage: V = list(g)
|
979
|
+
sage: eccentricity(g, vertex_list=V)
|
980
|
+
[2, 2, 3, 3, 2, 2, 3, 3, 3, 3, 3]
|
981
|
+
sage: eccentricity(g, vertex_list=V[::-1])
|
982
|
+
[3, 3, 3, 3, 3, 2, 2, 3, 3, 2, 2]
|
983
|
+
|
984
|
+
TESTS:
|
985
|
+
|
986
|
+
All algorithms are valid::
|
987
|
+
|
988
|
+
sage: from sage.graphs.distances_all_pairs import eccentricity
|
989
|
+
sage: g = graphs.RandomGNP(50, .1)
|
990
|
+
sage: ecc = eccentricity(g, algorithm='standard')
|
991
|
+
sage: ecc == eccentricity(g, algorithm='bounds')
|
992
|
+
True
|
993
|
+
sage: ecc == eccentricity(g, algorithm='DHV')
|
994
|
+
True
|
995
|
+
|
996
|
+
Case of not (strongly) connected (directed) graph::
|
997
|
+
|
998
|
+
sage: from sage.graphs.distances_all_pairs import eccentricity
|
999
|
+
sage: g = 2*graphs.PathGraph(2)
|
1000
|
+
sage: eccentricity(g, algorithm='bounds')
|
1001
|
+
[+Infinity, +Infinity, +Infinity, +Infinity]
|
1002
|
+
sage: g = digraphs.Path(3)
|
1003
|
+
sage: eccentricity(g, algorithm='standard')
|
1004
|
+
[2, +Infinity, +Infinity]
|
1005
|
+
|
1006
|
+
The bounds algorithm is for Graph only::
|
1007
|
+
|
1008
|
+
sage: from sage.graphs.distances_all_pairs import eccentricity
|
1009
|
+
sage: g = digraphs.Circuit(2)
|
1010
|
+
sage: eccentricity(g, algorithm='bounds')
|
1011
|
+
Traceback (most recent call last):
|
1012
|
+
...
|
1013
|
+
ValueError: the 'bounds' algorithm only works on undirected graphs
|
1014
|
+
|
1015
|
+
Asking for unknown algorithm::
|
1016
|
+
|
1017
|
+
sage: from sage.graphs.distances_all_pairs import eccentricity
|
1018
|
+
sage: g = graphs.PathGraph(2)
|
1019
|
+
sage: eccentricity(g, algorithm='Nice Jazz Festival')
|
1020
|
+
Traceback (most recent call last):
|
1021
|
+
...
|
1022
|
+
ValueError: unknown algorithm 'Nice Jazz Festival', please contribute
|
1023
|
+
|
1024
|
+
Invalid value for parameter vertex_list::
|
1025
|
+
|
1026
|
+
sage: from sage.graphs.distances_all_pairs import eccentricity
|
1027
|
+
sage: g = graphs.PathGraph(2)
|
1028
|
+
sage: eccentricity(g, vertex_list=[0, 1, 2])
|
1029
|
+
Traceback (most recent call last):
|
1030
|
+
...
|
1031
|
+
ValueError: parameter vertex_list is incorrect for this graph
|
1032
|
+
"""
|
1033
|
+
from sage.rings.infinity import Infinity
|
1034
|
+
cdef int n = G.order()
|
1035
|
+
cdef short_digraph sd
|
1036
|
+
|
1037
|
+
# Trivial cases
|
1038
|
+
if algorithm not in ['standard', 'bounds', 'DHV']:
|
1039
|
+
raise ValueError("unknown algorithm '{}', please contribute".format(algorithm))
|
1040
|
+
if not n:
|
1041
|
+
return []
|
1042
|
+
elif G.is_directed() and algorithm in ['bounds', 'DHV']:
|
1043
|
+
raise ValueError("the 'bounds' algorithm only works on undirected graphs")
|
1044
|
+
elif not G.is_connected():
|
1045
|
+
return [Infinity] * n
|
1046
|
+
|
1047
|
+
cdef list int_to_vertex
|
1048
|
+
if vertex_list is None:
|
1049
|
+
int_to_vertex = G.vertices(sort=True)
|
1050
|
+
elif len(vertex_list) == n and set(vertex_list) == set(G):
|
1051
|
+
int_to_vertex = vertex_list
|
1052
|
+
else:
|
1053
|
+
raise ValueError("parameter vertex_list is incorrect for this graph")
|
1054
|
+
|
1055
|
+
cdef uint32_t* ecc
|
1056
|
+
|
1057
|
+
if algorithm == "standard":
|
1058
|
+
ecc = c_eccentricity(G, vertex_list=int_to_vertex)
|
1059
|
+
|
1060
|
+
else:
|
1061
|
+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=vertex_list)
|
1062
|
+
|
1063
|
+
if algorithm == "DHV":
|
1064
|
+
ecc = c_eccentricity_DHV(sd)
|
1065
|
+
else: # "bounds"
|
1066
|
+
ecc = c_eccentricity_bounding(sd)
|
1067
|
+
|
1068
|
+
free_short_digraph(sd)
|
1069
|
+
|
1070
|
+
from sage.rings.integer import Integer
|
1071
|
+
cdef list l_ecc = [Integer(ecc[i]) if ecc[i] != UINT32_MAX else +Infinity for i in range(n)]
|
1072
|
+
|
1073
|
+
sig_free(ecc)
|
1074
|
+
|
1075
|
+
return l_ecc
|
1076
|
+
|
1077
|
+
|
1078
|
+
############
|
1079
|
+
# Diameter #
|
1080
|
+
############
|
1081
|
+
|
1082
|
+
cdef uint32_t diameter_lower_bound_2sweep(short_digraph g,
|
1083
|
+
uint32_t source,
|
1084
|
+
uint32_t* distances,
|
1085
|
+
uint32_t* predecessors,
|
1086
|
+
uint32_t* waiting_list,
|
1087
|
+
bitset_t seen) noexcept:
|
1088
|
+
"""
|
1089
|
+
Compute a lower bound on the diameter using the 2-sweep algorithm.
|
1090
|
+
|
1091
|
+
This method computes a lower bound on the diameter of an unweighted
|
1092
|
+
undirected graph using 2 BFS, as proposed in [MLH2008]_. It first selects a
|
1093
|
+
vertex `v` that is at largest distance from an initial vertex `source` using
|
1094
|
+
BFS. Then it performs a second BFS from `v`. The largest distance from `v`
|
1095
|
+
is returned as a lower bound on the diameter of `G`. The time complexity of
|
1096
|
+
this method is linear in the size of the graph.
|
1097
|
+
|
1098
|
+
|
1099
|
+
INPUT:
|
1100
|
+
|
1101
|
+
- ``g`` -- a short_digraph
|
1102
|
+
|
1103
|
+
- ``source`` -- starting node of the BFS
|
1104
|
+
|
1105
|
+
- ``distances`` -- array of size ``n`` to store BFS distances from `v`, the
|
1106
|
+
vertex at largest distance from ``source`` from which we start the second
|
1107
|
+
BFS. This method assumes that this array has already been allocated.
|
1108
|
+
However, there is no need to initialize it.
|
1109
|
+
|
1110
|
+
- ``predecessors`` -- array of size ``n`` to store the first predecessor of
|
1111
|
+
each vertex during the BFS search from `v`. The predecessor of `v` is
|
1112
|
+
itself. This method assumes that this array has already been allocated.
|
1113
|
+
However, it is possible to pass a ``NULL`` pointer in which case the
|
1114
|
+
predecessors are not recorded.
|
1115
|
+
|
1116
|
+
- ``waiting_list`` -- array of size ``n`` to store the order in which the
|
1117
|
+
vertices are visited during the BFS search from `v`. This method assumes
|
1118
|
+
that this array has already been allocated. However, there is no need to
|
1119
|
+
initialize it.
|
1120
|
+
|
1121
|
+
- ``seen`` -- bitset of size ``n`` that must be initialized before calling
|
1122
|
+
this method (i.e., bitset_init(seen, n)). However, there is no need to
|
1123
|
+
clear it.
|
1124
|
+
"""
|
1125
|
+
cdef uint32_t LB
|
1126
|
+
|
1127
|
+
# We do a first BFS from source and get the eccentricity of source
|
1128
|
+
LB = simple_BFS(g, source, distances, NULL, waiting_list, seen)
|
1129
|
+
|
1130
|
+
# If the eccentricity of the source is infinite (very large number), the
|
1131
|
+
# graph is not connected and so its diameter is infinite.
|
1132
|
+
if LB == UINT32_MAX:
|
1133
|
+
return UINT32_MAX
|
1134
|
+
|
1135
|
+
# Then we perform a second BFS from the last visited vertex
|
1136
|
+
source = waiting_list[g.n - 1]
|
1137
|
+
LB = simple_BFS(g, source, distances, predecessors, waiting_list, seen)
|
1138
|
+
|
1139
|
+
# We return the computed lower bound
|
1140
|
+
return LB
|
1141
|
+
|
1142
|
+
|
1143
|
+
cdef tuple diameter_lower_bound_2Dsweep(short_digraph g,
|
1144
|
+
short_digraph rev_g,
|
1145
|
+
uint32_t source):
|
1146
|
+
r"""
|
1147
|
+
Lower bound on the diameter of digraph using directed version of 2-sweep.
|
1148
|
+
|
1149
|
+
This method computes a lower bound on the diameter of an unweighted directed
|
1150
|
+
graph using directed version of the 2-sweep algorithm [Broder2000]_.
|
1151
|
+
In first part, it performs a forward BFS from `source` and selects a vertex
|
1152
|
+
`vf` at a maximum distance from `source` and then it calculates backward
|
1153
|
+
eccentricity of `vf` using a backward BFS from `vf`. In second part, it
|
1154
|
+
performs backward BFS from `source` and selects a vertex `vb` from which
|
1155
|
+
`source` is at maximum distance and then it calculates forward eccentricity
|
1156
|
+
of `vb` using a forward BFS from `vb`. It then calculates lower bound LB of
|
1157
|
+
diameter as the maximum of backward eccentricity of `vf` and forward
|
1158
|
+
eccentricity of `vb` and `s` as respective vertex.
|
1159
|
+
This method returns (`LB`, `s`, `m`, `d`), where `LB` is best found lower
|
1160
|
+
bound on diameter, `s` is vertex whose forward/backward eccentricity is
|
1161
|
+
`LB`, `d` is vertex at a distance `LB` from/to `s`, `m` is vertex at
|
1162
|
+
distance `LB/2` from/to both `s` and `d`.
|
1163
|
+
|
1164
|
+
INPUT:
|
1165
|
+
|
1166
|
+
- ``g`` -- a short_digraph
|
1167
|
+
|
1168
|
+
- ``rev_g`` -- a copy of `g` with edges reversed
|
1169
|
+
|
1170
|
+
- ``source`` -- starting node of the forward and backward BFS
|
1171
|
+
|
1172
|
+
TESTS:
|
1173
|
+
|
1174
|
+
Diameter of weakly connected digraph is infinity ::
|
1175
|
+
|
1176
|
+
sage: from sage.graphs.distances_all_pairs import diameter
|
1177
|
+
sage: G = DiGraph([(0,1)])
|
1178
|
+
sage: diameter(G, algorithm='2Dsweep')
|
1179
|
+
+Infinity
|
1180
|
+
"""
|
1181
|
+
cdef uint32_t LB_1, LB_2, LB, LB_m, m, s, d
|
1182
|
+
cdef uint32_t n = g.n
|
1183
|
+
cdef uint32_t source_1 = source
|
1184
|
+
cdef uint32_t source_2 = source
|
1185
|
+
cdef bitset_t seen_1, seen_2
|
1186
|
+
|
1187
|
+
# Memory allocation
|
1188
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
1189
|
+
bitset_init(seen_1, n)
|
1190
|
+
bitset_init(seen_2, n)
|
1191
|
+
cdef uint32_t * distances_1 = <uint32_t *>mem.malloc(3 * n * sizeof(uint32_t))
|
1192
|
+
cdef uint32_t * distances_2 = <uint32_t *>mem.malloc(3 * n * sizeof(uint32_t))
|
1193
|
+
if not distances_1 or not distances_2:
|
1194
|
+
bitset_free(seen_1)
|
1195
|
+
bitset_free(seen_2)
|
1196
|
+
raise MemoryError()
|
1197
|
+
|
1198
|
+
cdef uint32_t * predecessors_1 = distances_1 + n
|
1199
|
+
cdef uint32_t * predecessors_2 = distances_2 + n
|
1200
|
+
cdef uint32_t * waiting_list_1 = distances_1 + 2 * n
|
1201
|
+
cdef uint32_t * waiting_list_2 = distances_2 + 2 * n
|
1202
|
+
|
1203
|
+
# we perform forward BFS from source and get its forward eccentricity
|
1204
|
+
LB_1 = simple_BFS(g, source_1, distances_1, NULL, waiting_list_1, seen_1)
|
1205
|
+
|
1206
|
+
# if forward eccentricity of source is infinite, then graph is
|
1207
|
+
# not strongly connected and its diameter is infinite
|
1208
|
+
if LB_1 == UINT32_MAX:
|
1209
|
+
bitset_free(seen_1)
|
1210
|
+
bitset_free(seen_2)
|
1211
|
+
return (UINT32_MAX, 0, 0, 0)
|
1212
|
+
|
1213
|
+
# we perform backward BFS from source and get its backward eccentricity
|
1214
|
+
LB_2 = simple_BFS(rev_g, source_2, distances_2, NULL, waiting_list_2, seen_2)
|
1215
|
+
|
1216
|
+
# if backward eccentricity of source is infinite, then graph is
|
1217
|
+
# not strongly connected and its diameter is infinite
|
1218
|
+
if LB_2 == UINT32_MAX:
|
1219
|
+
bitset_free(seen_1)
|
1220
|
+
bitset_free(seen_2)
|
1221
|
+
return (UINT32_MAX, 0, 0, 0)
|
1222
|
+
|
1223
|
+
# Then we perform backward BFS from the last visited vertex of forward BFS
|
1224
|
+
# from source and obtain its backward eccentricity.
|
1225
|
+
source_1 = waiting_list_1[n - 1]
|
1226
|
+
LB_1 = simple_BFS(rev_g, source_1, distances_1, predecessors_1, waiting_list_1, seen_1)
|
1227
|
+
|
1228
|
+
# Then we perform forward BFS from the last visited vertex of backward BFS
|
1229
|
+
# from source and obtain its forward eccentricity.
|
1230
|
+
source_2 = waiting_list_2[n - 1]
|
1231
|
+
LB_2 = simple_BFS(g, source_2, distances_2, predecessors_2, waiting_list_2, seen_2)
|
1232
|
+
|
1233
|
+
# we select best found lower bound as LB, s and d as source and destination
|
1234
|
+
# of that BFS call and m as vertex at a distance LB/2 from/to both s and d
|
1235
|
+
if LB_1 < LB_2:
|
1236
|
+
LB = LB_2
|
1237
|
+
s = waiting_list_2[0]
|
1238
|
+
d = waiting_list_2[n - 1]
|
1239
|
+
LB_m = LB_2 / 2
|
1240
|
+
m = d
|
1241
|
+
while distances_2[m] > LB_m:
|
1242
|
+
m = predecessors_2[m]
|
1243
|
+
else:
|
1244
|
+
LB = LB_1
|
1245
|
+
s = waiting_list_1[0]
|
1246
|
+
d = waiting_list_1[n - 1]
|
1247
|
+
LB_m = LB_1 / 2
|
1248
|
+
m = d
|
1249
|
+
while distances_1[m] > LB_m:
|
1250
|
+
m = predecessors_1[m]
|
1251
|
+
|
1252
|
+
bitset_free(seen_1)
|
1253
|
+
bitset_free(seen_2)
|
1254
|
+
|
1255
|
+
return (LB, s, m, d)
|
1256
|
+
|
1257
|
+
|
1258
|
+
cdef tuple diameter_lower_bound_multi_sweep(short_digraph g,
|
1259
|
+
uint32_t source):
|
1260
|
+
"""
|
1261
|
+
Lower bound on the diameter using multi-sweep.
|
1262
|
+
|
1263
|
+
This method computes a lower bound on the diameter of an unweighted
|
1264
|
+
undirected graph using several iterations of the 2-sweep algorithms
|
1265
|
+
[CGHLM2013]_. Roughly, it first uses 2-sweep to identify two vertices `s`
|
1266
|
+
and `d` that are far apart. Then it selects a vertex `m` that is at same
|
1267
|
+
distance from `s` and `d`. This vertex `m` will serve as the new source for
|
1268
|
+
another iteration of the 2-sweep algorithm that may improve the current
|
1269
|
+
lower bound on the diameter. This process is repeated as long as the lower
|
1270
|
+
bound on the diameter is improved.
|
1271
|
+
|
1272
|
+
The method returns a 4-tuple (LB, s, m, d), where LB is the best found lower
|
1273
|
+
bound on the diameter, s is a vertex of eccentricity LB, d is a vertex at
|
1274
|
+
distance LB from s, and m is a vertex at distance LB/2 from both s and d.
|
1275
|
+
|
1276
|
+
INPUT:
|
1277
|
+
|
1278
|
+
- ``g`` -- a short_digraph
|
1279
|
+
|
1280
|
+
- ``source`` -- starting node of the BFS
|
1281
|
+
"""
|
1282
|
+
# The while loop below might not be entered so we have to make sure that
|
1283
|
+
# s and d which are returned are initialized.
|
1284
|
+
cdef uint32_t LB, tmp, m
|
1285
|
+
cdef uint32_t s = 0
|
1286
|
+
cdef uint32_t d = 0
|
1287
|
+
cdef uint32_t n = g.n
|
1288
|
+
|
1289
|
+
# Allocate some arrays and a bitset
|
1290
|
+
cdef bitset_t seen
|
1291
|
+
bitset_init(seen, n)
|
1292
|
+
cdef uint32_t * distances = <uint32_t *>sig_malloc(3 * n * sizeof(uint32_t))
|
1293
|
+
if not distances:
|
1294
|
+
bitset_free(seen)
|
1295
|
+
raise MemoryError()
|
1296
|
+
cdef uint32_t * predecessors = distances + n
|
1297
|
+
cdef uint32_t * waiting_list = distances + 2 * n
|
1298
|
+
|
1299
|
+
# We perform a first 2sweep call from source. If the returned value is a
|
1300
|
+
# very large number, the graph is not connected and so the diameter is
|
1301
|
+
# infinite.
|
1302
|
+
tmp = diameter_lower_bound_2sweep(g, source, distances, predecessors, waiting_list, seen)
|
1303
|
+
if tmp == UINT32_MAX:
|
1304
|
+
sig_free(distances)
|
1305
|
+
bitset_free(seen)
|
1306
|
+
return (UINT32_MAX, 0, 0, 0)
|
1307
|
+
|
1308
|
+
# We perform new 2sweep calls for as long as we are able to improve the
|
1309
|
+
# lower bound.
|
1310
|
+
LB = 0
|
1311
|
+
m = source
|
1312
|
+
while tmp > LB:
|
1313
|
+
|
1314
|
+
LB = tmp
|
1315
|
+
|
1316
|
+
# We store the vertices s, m, d of the last BFS call. For vertex m, we
|
1317
|
+
# search for a vertex of eccentricity LB/2. This vertex will serve as
|
1318
|
+
# the source for the next 2sweep call.
|
1319
|
+
s = waiting_list[0]
|
1320
|
+
d = waiting_list[n - 1]
|
1321
|
+
LB_2 = LB / 2
|
1322
|
+
m = d
|
1323
|
+
while distances[m] > LB_2:
|
1324
|
+
m = predecessors[m]
|
1325
|
+
|
1326
|
+
# We perform a new 2sweep call from m
|
1327
|
+
tmp = diameter_lower_bound_2sweep(g, m, distances, predecessors, waiting_list, seen)
|
1328
|
+
|
1329
|
+
sig_free(distances)
|
1330
|
+
bitset_free(seen)
|
1331
|
+
|
1332
|
+
return (LB, s, m, d)
|
1333
|
+
|
1334
|
+
|
1335
|
+
cdef uint32_t diameter_iFUB(short_digraph g,
|
1336
|
+
uint32_t source) noexcept:
|
1337
|
+
"""
|
1338
|
+
Compute the diameter of the input Graph using the ``iFUB`` algorithm.
|
1339
|
+
|
1340
|
+
The ``iFUB`` (iterative Fringe Upper Bound) algorithm calculates the exact
|
1341
|
+
value of the diameter of a unweighted undirected graph [CGILM2010]_. This
|
1342
|
+
algorithms starts with a vertex found through a multi-sweep call (a
|
1343
|
+
refinement of the 4sweep method). The worst case time complexity of the iFUB
|
1344
|
+
algorithm is `O(nm)`, but it can be very fast in practice. See the code's
|
1345
|
+
documentation and [CGHLM2013]_ for more details.
|
1346
|
+
|
1347
|
+
INPUT:
|
1348
|
+
|
1349
|
+
- ``g`` -- a short_digraph
|
1350
|
+
|
1351
|
+
- ``source`` -- starting node of the first BFS
|
1352
|
+
"""
|
1353
|
+
cdef uint32_t i, LB, m
|
1354
|
+
cdef uint32_t n = g.n
|
1355
|
+
|
1356
|
+
# We select a vertex m with low eccentricity using multi-sweep
|
1357
|
+
LB, _, m, _ = diameter_lower_bound_multi_sweep(g, source)
|
1358
|
+
|
1359
|
+
# If the lower bound is a very large number, it means that the graph is not
|
1360
|
+
# connected and so the diameter is infinite.
|
1361
|
+
if LB == UINT32_MAX:
|
1362
|
+
return LB
|
1363
|
+
|
1364
|
+
# We allocate some arrays and a bitset
|
1365
|
+
cdef bitset_t seen
|
1366
|
+
bitset_init(seen, n)
|
1367
|
+
cdef uint32_t * distances = <uint32_t *>sig_malloc(4 * n * sizeof(uint32_t))
|
1368
|
+
if not distances:
|
1369
|
+
bitset_free(seen)
|
1370
|
+
raise MemoryError()
|
1371
|
+
cdef uint32_t* waiting_list = distances + n
|
1372
|
+
cdef uint32_t* layer = distances + 2 * n
|
1373
|
+
cdef uint32_t* order = distances + 3 * n
|
1374
|
+
|
1375
|
+
# We order the vertices by decreasing layers. This is the inverse order of a
|
1376
|
+
# BFS from m, and so the inverse order of array waiting_list. Distances are
|
1377
|
+
# stored in array layer.
|
1378
|
+
LB = simple_BFS(g, m, layer, NULL, waiting_list, seen)
|
1379
|
+
for i in range(n):
|
1380
|
+
order[i] = waiting_list[n - i - 1]
|
1381
|
+
|
1382
|
+
# The algorithm:
|
1383
|
+
#
|
1384
|
+
# The diameter of the graph is equal to the maximum eccentricity of a
|
1385
|
+
# vertex. Let m be any vertex, and let V be partitioned into A u B where:
|
1386
|
+
#
|
1387
|
+
# d(m,a)<=i for all a \in A
|
1388
|
+
# d(m,b)>=i for all b \in B
|
1389
|
+
#
|
1390
|
+
# As all vertices from A are at distance <=2i from each other, a vertex a
|
1391
|
+
# from A with eccentricity ecc(a)>2i is at distance ecc(a) from some vertex
|
1392
|
+
# b\in B.
|
1393
|
+
#
|
1394
|
+
# Consequently, if we have already computed the eccentricity of all
|
1395
|
+
# vertices in B and know that the diameter is >2i, then we do not have to
|
1396
|
+
# compute the eccentricity of vertices in A.
|
1397
|
+
#
|
1398
|
+
# Now, we compute the maximum eccentricity of all vertices, ordered
|
1399
|
+
# decreasingly according to their distance to m. We stop when we know that
|
1400
|
+
# the eccentricity of the unexplored vertices is smaller than the max
|
1401
|
+
# eccentricity already found.
|
1402
|
+
i = 0
|
1403
|
+
while 2 * layer[order[i]] > LB and i < n:
|
1404
|
+
tmp = simple_BFS(g, order[i], distances, NULL, waiting_list, seen)
|
1405
|
+
i += 1
|
1406
|
+
|
1407
|
+
# We update the lower bound
|
1408
|
+
if tmp > LB:
|
1409
|
+
LB = tmp
|
1410
|
+
|
1411
|
+
sig_free(distances)
|
1412
|
+
bitset_free(seen)
|
1413
|
+
|
1414
|
+
# We finally return the computed diameter
|
1415
|
+
return LB
|
1416
|
+
|
1417
|
+
|
1418
|
+
cdef uint32_t diameter_DiFUB(short_digraph sd,
|
1419
|
+
uint32_t source) noexcept:
|
1420
|
+
r"""
|
1421
|
+
Return the diameter of unweighted directed graph.
|
1422
|
+
|
1423
|
+
The ``DiFUB`` (Directed iterative Fringe Upper Bound) algorithm calculates
|
1424
|
+
the exact value of the diameter of an unweighted directed graph [CGLM2012]_.
|
1425
|
+
|
1426
|
+
This algorithm starts from a vertex found through a 2Dsweep call (a directed
|
1427
|
+
version of the 2sweep method). The worst case time complexity of the DiFUB
|
1428
|
+
algorithm is `O(nm)`, but it can be very fast in practice. See the code's
|
1429
|
+
documentation and [CGLM2012]_ for more details.
|
1430
|
+
|
1431
|
+
If the digraph is not strongly connected, the returned value is infinity.
|
1432
|
+
|
1433
|
+
INPUT:
|
1434
|
+
|
1435
|
+
- ``sd`` -- a short_digraph
|
1436
|
+
|
1437
|
+
- ``source`` -- starting node of the first BFS
|
1438
|
+
|
1439
|
+
TESTS:
|
1440
|
+
|
1441
|
+
The diameter of a weakly connected digraph is infinity ::
|
1442
|
+
|
1443
|
+
sage: from sage.graphs.distances_all_pairs import diameter
|
1444
|
+
sage: G = digraphs.Path(5)
|
1445
|
+
sage: diameter(G, algorithm='DiFUB')
|
1446
|
+
+Infinity
|
1447
|
+
"""
|
1448
|
+
cdef uint32_t n = sd.n
|
1449
|
+
|
1450
|
+
if n <= 1: # Trivial case
|
1451
|
+
return 0
|
1452
|
+
|
1453
|
+
cdef short_digraph rev_sd # Copy of sd with edges reversed
|
1454
|
+
init_reverse(rev_sd, sd)
|
1455
|
+
|
1456
|
+
cdef uint32_t LB, m, LB_1, LB_2, UB
|
1457
|
+
cdef size_t i
|
1458
|
+
cdef bitset_t seen
|
1459
|
+
|
1460
|
+
# We select a vertex with low eccentricity using 2Dsweep
|
1461
|
+
LB, _, m, _ = diameter_lower_bound_2Dsweep(sd, rev_sd, source)
|
1462
|
+
|
1463
|
+
# If the lower bound is a very large number, it means that the digraph is
|
1464
|
+
# not strongly connected and so the diameter is infinite.
|
1465
|
+
if LB == UINT32_MAX:
|
1466
|
+
return LB
|
1467
|
+
|
1468
|
+
# We allocate some arrays and a bitset
|
1469
|
+
bitset_init(seen, n)
|
1470
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
1471
|
+
cdef uint32_t * distances = <uint32_t *>mem.malloc(6 * n * sizeof(uint32_t))
|
1472
|
+
|
1473
|
+
if not distances:
|
1474
|
+
bitset_free(seen)
|
1475
|
+
raise MemoryError()
|
1476
|
+
|
1477
|
+
cdef uint32_t * waiting_list = distances + n
|
1478
|
+
cdef uint32_t * order_1 = distances + 2 * n
|
1479
|
+
cdef uint32_t * order_2 = distances + 3 * n
|
1480
|
+
cdef uint32_t * layer_1 = distances + 4 * n
|
1481
|
+
cdef uint32_t * layer_2 = distances + 5 * n
|
1482
|
+
|
1483
|
+
# We order the vertices by decreasing forward / backward layers. This is the
|
1484
|
+
# inverse order of a forward / backward BFS from m, and so the inverse order
|
1485
|
+
# of array waiting_list. Forward / Backward distances are stored in arrays
|
1486
|
+
# layer_1 / layer_2 respectively.
|
1487
|
+
LB_1 = simple_BFS(sd, m, layer_1, NULL, waiting_list, seen)
|
1488
|
+
for i in range(n):
|
1489
|
+
order_1[i] = waiting_list[n - i - 1]
|
1490
|
+
|
1491
|
+
LB_2 = simple_BFS(rev_sd, m, layer_2, NULL, waiting_list, seen)
|
1492
|
+
for i in range(n):
|
1493
|
+
order_2[i] = waiting_list[n - i - 1]
|
1494
|
+
|
1495
|
+
# update the lower bound
|
1496
|
+
LB = max(LB, LB_1, LB_2)
|
1497
|
+
|
1498
|
+
if LB == UINT32_MAX: # Not strongly connected case
|
1499
|
+
return LB
|
1500
|
+
|
1501
|
+
# The algorithm:
|
1502
|
+
#
|
1503
|
+
# The diameter of the digraph is equal to the maximum forward or backward
|
1504
|
+
# eccentricity of a vertex. The algorithm is based on the following two
|
1505
|
+
# observations:
|
1506
|
+
# 1). All the nodes `x` above the level `i` in Backward BFS of `m` having
|
1507
|
+
# forward eccentricity greater than `2(i-1)` have a corresponding node `y`,
|
1508
|
+
# whose backward eccentricity is greater than or equal to the forward
|
1509
|
+
# eccentricity of `x`, below or on the level `i` in Forward BFS of `m`.
|
1510
|
+
#
|
1511
|
+
# 2). All the nodes `x` above the level `i` in Forward BFS of `m` having
|
1512
|
+
# backward eccentricity greater than `2(i-1)` have a corresponding node `y`,
|
1513
|
+
# whose forward eccentricity is greater than or equal to the backward
|
1514
|
+
# eccentricity of `x`, below or on the level `i` in Backward BFS of `m`
|
1515
|
+
#
|
1516
|
+
# Therefore, we calculate backward / forward eccentricity of all nodes at
|
1517
|
+
# level `i` in Forward / Backward BFS of `m` respectively. And their
|
1518
|
+
# maximum is `LB`. If `LB` is greater than `2(max distance at next level)`
|
1519
|
+
# then we are done, else we proceed further.
|
1520
|
+
|
1521
|
+
i = 0
|
1522
|
+
UB = max(2 * layer_1[order_1[i]], 2 * layer_2[order_2[i]])
|
1523
|
+
|
1524
|
+
while LB < UB:
|
1525
|
+
LB_1 = simple_BFS(rev_sd, order_1[i], distances, NULL, waiting_list, seen)
|
1526
|
+
LB_2 = simple_BFS(sd, order_2[i], distances, NULL, waiting_list, seen)
|
1527
|
+
|
1528
|
+
# update the lower bound
|
1529
|
+
LB = max(LB, LB_1, LB_2)
|
1530
|
+
i += 1
|
1531
|
+
|
1532
|
+
if LB == UINT32_MAX or i == n:
|
1533
|
+
break
|
1534
|
+
# maximum distance at next level
|
1535
|
+
UB = max(2 * layer_1[order_1[i]], 2 * layer_2[order_2[i]])
|
1536
|
+
|
1537
|
+
bitset_free(seen)
|
1538
|
+
free_short_digraph(rev_sd)
|
1539
|
+
|
1540
|
+
# Finally return the computed diameter
|
1541
|
+
return LB
|
1542
|
+
|
1543
|
+
|
1544
|
+
cdef uint32_t diameter_DHV(short_digraph g) noexcept:
|
1545
|
+
r"""
|
1546
|
+
Return the diameter of unweighted graph `g`.
|
1547
|
+
|
1548
|
+
This method computes the diameter of unweighted undirected graph using the
|
1549
|
+
algorithm proposed in [Dragan2018]_.
|
1550
|
+
|
1551
|
+
This method returns Infinity if graph is not connected.
|
1552
|
+
|
1553
|
+
INPUT:
|
1554
|
+
|
1555
|
+
- ``g`` -- a short_digraph
|
1556
|
+
|
1557
|
+
EXAMPLES::
|
1558
|
+
|
1559
|
+
sage: from sage.graphs.distances_all_pairs import diameter
|
1560
|
+
sage: G = graphs.PathGraph(5)
|
1561
|
+
sage: diameter(G, algorithm='DHV')
|
1562
|
+
4
|
1563
|
+
|
1564
|
+
TESTS:
|
1565
|
+
|
1566
|
+
sage: G = graphs.RandomGNP(20,0.3)
|
1567
|
+
sage: G.diameter() == diameter(G, algorithm='DHV')
|
1568
|
+
True
|
1569
|
+
|
1570
|
+
sage: G = Graph([(0, 1)])
|
1571
|
+
sage: diameter(G, algorithm='DHV')
|
1572
|
+
1
|
1573
|
+
"""
|
1574
|
+
cdef uint32_t n = g.n
|
1575
|
+
if n <= 1:
|
1576
|
+
return 0
|
1577
|
+
|
1578
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
1579
|
+
cdef uint32_t * distances = <uint32_t *>mem.malloc(4 * n * sizeof(uint32_t))
|
1580
|
+
if not distances:
|
1581
|
+
raise MemoryError()
|
1582
|
+
|
1583
|
+
cdef uint32_t * waiting_list = distances + n
|
1584
|
+
|
1585
|
+
# For storing upper and lower bounds on eccentricity of nodes
|
1586
|
+
cdef uint32_t * ecc_upper_bound = distances + 2 * n
|
1587
|
+
cdef uint32_t * ecc_lower_bound = distances + 3 * n
|
1588
|
+
memset(ecc_upper_bound, <char>-1, n * sizeof(uint32_t))
|
1589
|
+
memset(ecc_lower_bound, 0, n * sizeof(uint32_t))
|
1590
|
+
|
1591
|
+
cdef uint32_t u, ecc_u
|
1592
|
+
cdef uint32_t x, ecc_x
|
1593
|
+
cdef uint32_t antipode, ecc_antipode
|
1594
|
+
cdef uint32_t LB = 0
|
1595
|
+
cdef uint32_t UB = UINT32_MAX
|
1596
|
+
cdef uint32_t v, tmp
|
1597
|
+
cdef size_t i, idx
|
1598
|
+
cdef bitset_t seen
|
1599
|
+
bitset_init(seen, n)
|
1600
|
+
|
1601
|
+
cdef list active = list(range(n))
|
1602
|
+
|
1603
|
+
# Algorithm
|
1604
|
+
while LB < UB and active:
|
1605
|
+
# 1. Select vertex u with maximum eccentricity upper bound
|
1606
|
+
tmp = 0
|
1607
|
+
for i, v in enumerate(active):
|
1608
|
+
if ecc_upper_bound[v] > tmp:
|
1609
|
+
tmp = ecc_upper_bound[v]
|
1610
|
+
idx = i
|
1611
|
+
active[idx], active[-1] = active[-1], active[idx]
|
1612
|
+
u = active.pop()
|
1613
|
+
|
1614
|
+
# Compute the eccentricity of u and update eccentricity lower bounds
|
1615
|
+
ecc_u = simple_BFS(g, u, distances, NULL, waiting_list, seen)
|
1616
|
+
LB = max(LB, ecc_u)
|
1617
|
+
if LB == UINT32_MAX: # Disconnected graph
|
1618
|
+
break
|
1619
|
+
|
1620
|
+
for v in active:
|
1621
|
+
ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v])
|
1622
|
+
|
1623
|
+
# 2. Select x such that dist(u, x) + ecc[x] == ecc[u].
|
1624
|
+
# Since we don't know ecc[x], we select x with minimum eccentricity
|
1625
|
+
# lower bound. If ecc[x] == ecc_lb[x], we are done. Otherwise, we
|
1626
|
+
# update eccentricity lower bounds and repeat
|
1627
|
+
while active:
|
1628
|
+
# Select v with minimum eccentricity lower bound
|
1629
|
+
tmp = UINT32_MAX
|
1630
|
+
for i, v in enumerate(active):
|
1631
|
+
if ecc_lower_bound[v] < tmp:
|
1632
|
+
tmp = ecc_lower_bound[v]
|
1633
|
+
idx = i
|
1634
|
+
active[idx], active[-1] = active[-1], active[idx]
|
1635
|
+
x = active.pop()
|
1636
|
+
ecc_x = simple_BFS(g, x, distances, NULL, waiting_list, seen)
|
1637
|
+
LB = max(LB, ecc_x)
|
1638
|
+
|
1639
|
+
if ecc_x == ecc_lower_bound[x]:
|
1640
|
+
# We found the good vertex x
|
1641
|
+
# We update eccentricity upper bounds of the remaining vertices,
|
1642
|
+
# set UB to the largest of these values and break
|
1643
|
+
UB = 0
|
1644
|
+
for v in active:
|
1645
|
+
ecc_upper_bound[v] = min(ecc_upper_bound[v], distances[v] + ecc_x)
|
1646
|
+
UB = max(UB, ecc_upper_bound[v])
|
1647
|
+
break
|
1648
|
+
|
1649
|
+
else:
|
1650
|
+
# x was not a good choice
|
1651
|
+
# We use its antipode to update eccentricity lower bounds.
|
1652
|
+
# Observe that this antipode might have already been seen.
|
1653
|
+
antipode = waiting_list[n-1]
|
1654
|
+
for i, v in enumerate(active):
|
1655
|
+
if v == antipode:
|
1656
|
+
active[i] = active[-1]
|
1657
|
+
tmp = active.pop()
|
1658
|
+
break
|
1659
|
+
|
1660
|
+
ecc_antipode = simple_BFS(g, antipode, distances, NULL, waiting_list, seen)
|
1661
|
+
LB = max(LB, ecc_antipode)
|
1662
|
+
for v in active:
|
1663
|
+
ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v])
|
1664
|
+
|
1665
|
+
bitset_free(seen)
|
1666
|
+
return LB
|
1667
|
+
|
1668
|
+
|
1669
|
+
def diameter(G, algorithm=None, source=None):
|
1670
|
+
r"""
|
1671
|
+
Return the diameter of `G`.
|
1672
|
+
|
1673
|
+
This method returns Infinity if the (di)graph is not connected. It can
|
1674
|
+
also quickly return a lower bound on the diameter using the ``2sweep``,
|
1675
|
+
``2Dsweep`` and ``multi-sweep`` schemes.
|
1676
|
+
|
1677
|
+
INPUT:
|
1678
|
+
|
1679
|
+
- ``algorithm`` -- string (default: ``None``); specifies the algorithm to
|
1680
|
+
use among:
|
1681
|
+
|
1682
|
+
- ``'standard'`` -- computes the diameter of the input (di)graph as the
|
1683
|
+
largest eccentricity of its vertices. This is the classical algorithm
|
1684
|
+
with time complexity in `O(nm)`.
|
1685
|
+
|
1686
|
+
- ``'2sweep'`` -- computes a lower bound on the diameter of an
|
1687
|
+
unweighted undirected graph using 2 BFS, as proposed in [MLH2008]_. It
|
1688
|
+
first selects a vertex `v` that is at largest distance from an initial
|
1689
|
+
vertex source using BFS. Then it performs a second BFS from `v`. The
|
1690
|
+
largest distance from `v` is returned as a lower bound on the diameter
|
1691
|
+
of `G`. The time complexity of this algorithm is linear in the size of
|
1692
|
+
`G`.
|
1693
|
+
|
1694
|
+
- ``'2Dsweep'`` -- computes lower bound on the diameter of an unweighted
|
1695
|
+
directed graph using directed version of ``2sweep`` as proposed in
|
1696
|
+
[Broder2000]_. If the digraph is not strongly connected, the returned
|
1697
|
+
value is infinity.
|
1698
|
+
|
1699
|
+
- ``'DHV'`` -- computes diameter of unweighted undirected graph using the
|
1700
|
+
algorithm proposed in [Dragan2018]_
|
1701
|
+
|
1702
|
+
- ``'multi-sweep'`` -- computes a lower bound on the diameter of an
|
1703
|
+
unweighted undirected graph using several iterations of the ``2sweep``
|
1704
|
+
algorithms [CGHLM2013]_. Roughly, it first uses ``2sweep`` to identify
|
1705
|
+
two vertices `u` and `v` that are far apart. Then it selects a vertex
|
1706
|
+
`w` that is at same distance from `u` and `v`. This vertex `w` will
|
1707
|
+
serve as the new source for another iteration of the ``2sweep``
|
1708
|
+
algorithm that may improve the current lower bound on the diameter.
|
1709
|
+
This process is repeated as long as the lower bound on the diameter
|
1710
|
+
is improved.
|
1711
|
+
|
1712
|
+
- ``'iFUB'`` -- the iFUB (iterative Fringe Upper Bound) algorithm,
|
1713
|
+
proposed in [CGILM2010]_, computes the exact value of the diameter of an
|
1714
|
+
unweighted undirected graph. It is based on the following observation:
|
1715
|
+
|
1716
|
+
The diameter of the graph is equal to the maximum eccentricity of
|
1717
|
+
a vertex. Let `v` be any vertex, and let `V` be partitioned into
|
1718
|
+
`A\cup B` where:
|
1719
|
+
|
1720
|
+
.. MATH::
|
1721
|
+
|
1722
|
+
d(v,a) \leq i, \forall a \in A\\
|
1723
|
+
d(v,b) \geq i, \forall b \in B
|
1724
|
+
|
1725
|
+
As all vertices from `A` are at distance `\leq 2i` from each
|
1726
|
+
other, a vertex `a\in A` with eccentricity `ecc(a)>2i` is at
|
1727
|
+
distance `ecc(a)` from some vertex `b\in B`.
|
1728
|
+
|
1729
|
+
Consequently, if we have already computed the maximum eccentricity
|
1730
|
+
`m` of all vertices in `B` and if `m>2i`, then we do not need to
|
1731
|
+
compute the eccentricity of the vertices in `A`.
|
1732
|
+
|
1733
|
+
Starting from a vertex `v` obtained through a multi-sweep computation
|
1734
|
+
(which refines the 4sweep algorithm used in [CGHLM2013]_), we compute
|
1735
|
+
the diameter by computing the eccentricity of all vertices sorted
|
1736
|
+
decreasingly according to their distance to `v`, and stop as allowed
|
1737
|
+
by the remark above. The worst case time complexity of the iFUB
|
1738
|
+
algorithm is `O(nm)`, but it can be very fast in practice.
|
1739
|
+
|
1740
|
+
- ``'DiFUB'`` -- the directed version of iFUB (iterative Fringe Upper
|
1741
|
+
Bound) algorithm. See the code's documentation and [CGLM2012]_ for more
|
1742
|
+
details. If the digraph is not strongly connected, the returned value is
|
1743
|
+
infinity.
|
1744
|
+
|
1745
|
+
- ``source`` -- (default: ``None``) vertex from which to start the first BFS.
|
1746
|
+
If ``source==None``, an arbitrary vertex of the graph is chosen. Raise an
|
1747
|
+
error if the initial vertex is not in `G`. This parameter is not used
|
1748
|
+
when ``algorithm=='standard'``.
|
1749
|
+
|
1750
|
+
.. NOTE::
|
1751
|
+
As the graph is first converted to a short_digraph, all complexity
|
1752
|
+
have an extra `O(m+n)` for ``SparseGraph`` and `O(n^2)` for
|
1753
|
+
``DenseGraph``.
|
1754
|
+
|
1755
|
+
EXAMPLES::
|
1756
|
+
|
1757
|
+
sage: from sage.graphs.distances_all_pairs import diameter
|
1758
|
+
sage: G = graphs.PetersenGraph()
|
1759
|
+
sage: diameter(G, algorithm='iFUB')
|
1760
|
+
2
|
1761
|
+
sage: G = Graph({0: [], 1: [], 2: [1]})
|
1762
|
+
sage: diameter(G, algorithm='iFUB')
|
1763
|
+
+Infinity
|
1764
|
+
sage: G = digraphs.Circuit(6)
|
1765
|
+
sage: diameter(G, algorithm='2Dsweep')
|
1766
|
+
5
|
1767
|
+
sage: G = graphs.PathGraph(7).to_directed()
|
1768
|
+
sage: diameter(G, algorithm='DiFUB')
|
1769
|
+
6
|
1770
|
+
|
1771
|
+
Although max( ) is usually defined as -Infinity, since the diameter will
|
1772
|
+
never be negative, we define it to be zero::
|
1773
|
+
|
1774
|
+
sage: G = graphs.EmptyGraph()
|
1775
|
+
sage: diameter(G, algorithm='iFUB')
|
1776
|
+
0
|
1777
|
+
|
1778
|
+
Comparison of exact algorithms for graphs::
|
1779
|
+
|
1780
|
+
sage: # needs networkx
|
1781
|
+
sage: G = graphs.RandomBarabasiAlbert(100, 2)
|
1782
|
+
sage: d1 = diameter(G, algorithm='standard')
|
1783
|
+
sage: d2 = diameter(G, algorithm='iFUB')
|
1784
|
+
sage: d3 = diameter(G, algorithm='iFUB', source=G.random_vertex())
|
1785
|
+
sage: d4 = diameter(G, algorithm='DHV')
|
1786
|
+
sage: if d1 != d2 or d1 != d3 or d1 != d4: print("Something goes wrong!")
|
1787
|
+
|
1788
|
+
Comparison of lower bound algorithms::
|
1789
|
+
|
1790
|
+
sage: lb2 = diameter(G, algorithm='2sweep') # needs networkx
|
1791
|
+
sage: lbm = diameter(G, algorithm='multi-sweep') # needs networkx
|
1792
|
+
sage: if not (lb2 <= lbm and lbm <= d3): print("Something goes wrong!") # needs networkx
|
1793
|
+
|
1794
|
+
Comparison of exact algorithms for digraphs::
|
1795
|
+
|
1796
|
+
sage: # needs networkx
|
1797
|
+
sage: D = DiGraph(graphs.RandomBarabasiAlbert(50, 2))
|
1798
|
+
sage: d1 = diameter(D, algorithm='standard')
|
1799
|
+
sage: d2 = diameter(D, algorithm='DiFUB')
|
1800
|
+
sage: d3 = diameter(D, algorithm='DiFUB', source=D.random_vertex())
|
1801
|
+
sage: d1 == d2 and d1 == d3
|
1802
|
+
True
|
1803
|
+
|
1804
|
+
TESTS:
|
1805
|
+
|
1806
|
+
This was causing a segfault. Fixed in :issue:`17873` ::
|
1807
|
+
|
1808
|
+
sage: G = graphs.PathGraph(1)
|
1809
|
+
sage: diameter(G, algorithm='iFUB')
|
1810
|
+
0
|
1811
|
+
|
1812
|
+
Immutable graphs::
|
1813
|
+
|
1814
|
+
sage: G = graphs.RandomGNP(10, .5)
|
1815
|
+
sage: G._backend
|
1816
|
+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
|
1817
|
+
sage: H = Graph(G, immutable=True)
|
1818
|
+
sage: H._backend
|
1819
|
+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
|
1820
|
+
sage: diameter(G, algorithm='iFUB') == diameter(H, algorithm='iFUB')
|
1821
|
+
True
|
1822
|
+
"""
|
1823
|
+
cdef uint32_t n = G.order()
|
1824
|
+
if n <= 1:
|
1825
|
+
return 0
|
1826
|
+
|
1827
|
+
if G.is_directed():
|
1828
|
+
if algorithm is None:
|
1829
|
+
algorithm = 'DiFUB'
|
1830
|
+
elif algorithm not in ['2Dsweep', 'standard', 'DiFUB']:
|
1831
|
+
raise ValueError("unknown algorithm for computing the diameter of directed graph")
|
1832
|
+
else:
|
1833
|
+
if algorithm is None:
|
1834
|
+
algorithm = 'iFUB'
|
1835
|
+
elif algorithm not in ['2sweep', 'multi-sweep', 'iFUB', 'standard', 'DHV']:
|
1836
|
+
raise ValueError("unknown algorithm for computing the diameter of undirected graph")
|
1837
|
+
|
1838
|
+
if algorithm == 'standard':
|
1839
|
+
return max(G.eccentricity())
|
1840
|
+
if source is None:
|
1841
|
+
source = next(G.vertex_iterator())
|
1842
|
+
elif not G.has_vertex(source):
|
1843
|
+
raise ValueError("the specified source is not a vertex of the input Graph")
|
1844
|
+
|
1845
|
+
# Copying the whole graph to obtain the list of neighbors quicker than by
|
1846
|
+
# calling out_neighbors. This data structure is well documented in the
|
1847
|
+
# module sage.graphs.base.static_sparse_graph
|
1848
|
+
cdef list int_to_vertex
|
1849
|
+
cdef StaticSparseCGraph cg
|
1850
|
+
cdef short_digraph sd
|
1851
|
+
if isinstance(G, StaticSparseBackend):
|
1852
|
+
cg = <StaticSparseCGraph> G._cg
|
1853
|
+
sd = <short_digraph> cg.g
|
1854
|
+
int_to_vertex = cg._vertex_to_labels
|
1855
|
+
else:
|
1856
|
+
int_to_vertex = list(G)
|
1857
|
+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
|
1858
|
+
cdef short_digraph rev_sd # to store copy of sd with edges reversed
|
1859
|
+
|
1860
|
+
# and we map the source to an int in [0,n-1]
|
1861
|
+
cdef uint32_t isource = 0 if source is None else int_to_vertex.index(source)
|
1862
|
+
|
1863
|
+
cdef bitset_t seen
|
1864
|
+
cdef uint32_t* tab
|
1865
|
+
cdef uint32_t LB
|
1866
|
+
|
1867
|
+
if algorithm == '2sweep':
|
1868
|
+
# We need to allocate arrays and bitset
|
1869
|
+
bitset_init(seen, n)
|
1870
|
+
tab = <uint32_t*> sig_malloc(2* n * sizeof(uint32_t))
|
1871
|
+
if not tab:
|
1872
|
+
if not isinstance(G, StaticSparseBackend):
|
1873
|
+
free_short_digraph(sd)
|
1874
|
+
bitset_free(seen)
|
1875
|
+
raise MemoryError()
|
1876
|
+
|
1877
|
+
LB = diameter_lower_bound_2sweep(sd, isource, tab, NULL, tab + n, seen)
|
1878
|
+
|
1879
|
+
bitset_free(seen)
|
1880
|
+
sig_free(tab)
|
1881
|
+
|
1882
|
+
elif algorithm == '2Dsweep':
|
1883
|
+
init_reverse(rev_sd, sd)
|
1884
|
+
LB = diameter_lower_bound_2Dsweep(sd, rev_sd, isource)[0]
|
1885
|
+
free_short_digraph(rev_sd)
|
1886
|
+
|
1887
|
+
elif algorithm == 'multi-sweep':
|
1888
|
+
LB = diameter_lower_bound_multi_sweep(sd, isource)[0]
|
1889
|
+
|
1890
|
+
elif algorithm == 'DiFUB':
|
1891
|
+
LB = diameter_DiFUB(sd, isource)
|
1892
|
+
|
1893
|
+
elif algorithm == 'DHV':
|
1894
|
+
LB = diameter_DHV(sd)
|
1895
|
+
|
1896
|
+
else: # algorithm == 'iFUB'
|
1897
|
+
LB = diameter_iFUB(sd, isource)
|
1898
|
+
|
1899
|
+
if not isinstance(G, StaticSparseBackend):
|
1900
|
+
free_short_digraph(sd)
|
1901
|
+
|
1902
|
+
if LB < 0 or LB > n:
|
1903
|
+
from sage.rings.infinity import Infinity
|
1904
|
+
return +Infinity
|
1905
|
+
return int(LB)
|
1906
|
+
|
1907
|
+
|
1908
|
+
###########
|
1909
|
+
# Radius #
|
1910
|
+
###########
|
1911
|
+
|
1912
|
+
def radius_DHV(G):
|
1913
|
+
r"""
|
1914
|
+
Return the radius of unweighted graph `G`.
|
1915
|
+
|
1916
|
+
This method computes the radius of unweighted undirected graph using the
|
1917
|
+
algorithm given in [Dragan2018]_.
|
1918
|
+
|
1919
|
+
This method returns Infinity if graph is not connected.
|
1920
|
+
|
1921
|
+
EXAMPLES::
|
1922
|
+
|
1923
|
+
sage: from sage.graphs.distances_all_pairs import radius_DHV
|
1924
|
+
sage: G = graphs.PetersenGraph()
|
1925
|
+
sage: radius_DHV(G)
|
1926
|
+
2
|
1927
|
+
sage: G = graphs.RandomGNP(20,0.3)
|
1928
|
+
sage: from sage.graphs.distances_all_pairs import eccentricity
|
1929
|
+
sage: radius_DHV(G) == min(eccentricity(G, algorithm='bounds'))
|
1930
|
+
True
|
1931
|
+
|
1932
|
+
TESTS:
|
1933
|
+
|
1934
|
+
sage: G = Graph()
|
1935
|
+
sage: radius_DHV(G)
|
1936
|
+
0
|
1937
|
+
sage: G = Graph(1)
|
1938
|
+
sage: radius_DHV(G)
|
1939
|
+
0
|
1940
|
+
sage: G = Graph(2)
|
1941
|
+
sage: radius_DHV(G)
|
1942
|
+
+Infinity
|
1943
|
+
sage: G = graphs.PathGraph(2)
|
1944
|
+
sage: radius_DHV(G)
|
1945
|
+
1
|
1946
|
+
sage: G = DiGraph(1)
|
1947
|
+
sage: radius_DHV(G)
|
1948
|
+
Traceback (most recent call last):
|
1949
|
+
...
|
1950
|
+
TypeError: this method works for unweighted undirected graphs only
|
1951
|
+
|
1952
|
+
Immutable graphs::
|
1953
|
+
|
1954
|
+
sage: G = graphs.RandomGNP(10, .5)
|
1955
|
+
sage: G._backend
|
1956
|
+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
|
1957
|
+
sage: H = Graph(G, immutable=True)
|
1958
|
+
sage: H._backend
|
1959
|
+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
|
1960
|
+
sage: radius_DHV(G) == radius_DHV(H)
|
1961
|
+
True
|
1962
|
+
"""
|
1963
|
+
if G.is_directed():
|
1964
|
+
raise TypeError("this method works for unweighted undirected graphs only")
|
1965
|
+
|
1966
|
+
cdef uint32_t n = G.order()
|
1967
|
+
if n <= 1:
|
1968
|
+
return 0
|
1969
|
+
|
1970
|
+
cdef list int_to_vertex
|
1971
|
+
cdef StaticSparseCGraph cg
|
1972
|
+
cdef short_digraph sd
|
1973
|
+
if isinstance(G, StaticSparseBackend):
|
1974
|
+
cg = <StaticSparseCGraph> G._cg
|
1975
|
+
sd = <short_digraph> cg.g
|
1976
|
+
int_to_vertex = cg._vertex_to_labels
|
1977
|
+
else:
|
1978
|
+
int_to_vertex = list(G)
|
1979
|
+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
|
1980
|
+
|
1981
|
+
cdef uint32_t source, ecc_source
|
1982
|
+
cdef uint32_t antipode, ecc_antipode
|
1983
|
+
cdef uint32_t UB = UINT32_MAX
|
1984
|
+
cdef uint32_t LB = 0
|
1985
|
+
|
1986
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
1987
|
+
cdef uint32_t * distances = <uint32_t *>mem.malloc(3 * n * sizeof(uint32_t))
|
1988
|
+
if not distances:
|
1989
|
+
if not isinstance(G, StaticSparseBackend):
|
1990
|
+
free_short_digraph(sd)
|
1991
|
+
raise MemoryError()
|
1992
|
+
|
1993
|
+
cdef uint32_t * waiting_list = distances + n
|
1994
|
+
|
1995
|
+
# For storing lower bound on eccentricity of nodes
|
1996
|
+
cdef uint32_t * ecc_lower_bound = distances + 2 * n
|
1997
|
+
memset(ecc_lower_bound, 0, n * sizeof(uint32_t))
|
1998
|
+
|
1999
|
+
cdef bitset_t seen
|
2000
|
+
bitset_init(seen, n)
|
2001
|
+
|
2002
|
+
# Algorithm
|
2003
|
+
source = 0
|
2004
|
+
while LB < UB:
|
2005
|
+
# 1) pick vertex with minimum eccentricity lower bound
|
2006
|
+
# and compute its eccentricity
|
2007
|
+
ecc_source = simple_BFS(sd, source, distances, NULL, waiting_list, seen)
|
2008
|
+
|
2009
|
+
if ecc_source == UINT32_MAX: # Disconnected graph
|
2010
|
+
break
|
2011
|
+
|
2012
|
+
UB = min(UB, ecc_source) # minimum among exact computed eccentricities
|
2013
|
+
if ecc_source == ecc_lower_bound[source]:
|
2014
|
+
# we have found minimum eccentricity vertex and hence the radius
|
2015
|
+
break
|
2016
|
+
|
2017
|
+
# 2) Take vertex at largest distance from source, called antipode (last
|
2018
|
+
# vertex visited in simple_BFS), and compute its BFS distances.
|
2019
|
+
# By definition of antipode, we have ecc_antipode >= ecc_source.
|
2020
|
+
antipode = waiting_list[n-1]
|
2021
|
+
ecc_antipode = simple_BFS(sd, antipode, distances, NULL, waiting_list, seen)
|
2022
|
+
|
2023
|
+
# 3) Use distances from antipode to improve eccentricity lower bounds.
|
2024
|
+
# We also determine the next source
|
2025
|
+
LB = UINT32_MAX
|
2026
|
+
for v in range(n):
|
2027
|
+
ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v])
|
2028
|
+
if LB > ecc_lower_bound[v]:
|
2029
|
+
LB = ecc_lower_bound[v]
|
2030
|
+
source = v # vertex with minimum eccentricity lower bound
|
2031
|
+
|
2032
|
+
if not isinstance(G, StaticSparseBackend):
|
2033
|
+
free_short_digraph(sd)
|
2034
|
+
bitset_free(seen)
|
2035
|
+
if UB == UINT32_MAX:
|
2036
|
+
from sage.rings.infinity import Infinity
|
2037
|
+
return +Infinity
|
2038
|
+
|
2039
|
+
return UB
|
2040
|
+
|
2041
|
+
|
2042
|
+
################
|
2043
|
+
# Wiener index #
|
2044
|
+
################
|
2045
|
+
|
2046
|
+
def wiener_index(G):
|
2047
|
+
r"""
|
2048
|
+
Return the Wiener index of the graph.
|
2049
|
+
|
2050
|
+
The Wiener index of an undirected graph `G` is defined as
|
2051
|
+
`W(G) = \frac{1}{2} \sum_{u,v\in G} d(u,v)` where `d(u,v)` denotes the
|
2052
|
+
distance between vertices `u` and `v` (see [KRG1996]_).
|
2053
|
+
|
2054
|
+
The Wiener index of a directed graph `G` is defined as the sum of the
|
2055
|
+
distances between each pairs of vertices, `W(G) = \sum_{u,v\in G} d(u,v)`.
|
2056
|
+
|
2057
|
+
EXAMPLES:
|
2058
|
+
|
2059
|
+
From [GYLL1993]_, cited in [KRG1996]_::
|
2060
|
+
|
2061
|
+
sage: g=graphs.PathGraph(10)
|
2062
|
+
sage: w=lambda x: (x*(x*x -1)/6)
|
2063
|
+
sage: g.wiener_index()==w(10)
|
2064
|
+
True
|
2065
|
+
|
2066
|
+
Wiener index of complete (di)graphs::
|
2067
|
+
|
2068
|
+
sage: n = 5
|
2069
|
+
sage: g = graphs.CompleteGraph(n)
|
2070
|
+
sage: g.wiener_index() == (n * (n - 1)) / 2
|
2071
|
+
True
|
2072
|
+
sage: g = digraphs.Complete(n)
|
2073
|
+
sage: g.wiener_index() == n * (n - 1)
|
2074
|
+
True
|
2075
|
+
|
2076
|
+
Wiener index of a graph of order 1::
|
2077
|
+
|
2078
|
+
sage: Graph(1).wiener_index()
|
2079
|
+
0
|
2080
|
+
|
2081
|
+
The Wiener index is not defined on the empty graph::
|
2082
|
+
|
2083
|
+
sage: Graph().wiener_index()
|
2084
|
+
Traceback (most recent call last):
|
2085
|
+
...
|
2086
|
+
ValueError: Wiener index is not defined for the empty graph
|
2087
|
+
|
2088
|
+
TESTS:
|
2089
|
+
|
2090
|
+
Immutable graphs::
|
2091
|
+
|
2092
|
+
sage: G = graphs.RandomGNP(10, .5)
|
2093
|
+
sage: G._backend
|
2094
|
+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
|
2095
|
+
sage: H = Graph(G, immutable=True)
|
2096
|
+
sage: H._backend
|
2097
|
+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
|
2098
|
+
sage: G.wiener_index() == H.wiener_index()
|
2099
|
+
True
|
2100
|
+
"""
|
2101
|
+
if not G:
|
2102
|
+
raise ValueError("Wiener index is not defined for the empty graph")
|
2103
|
+
|
2104
|
+
cdef unsigned int n = G.order()
|
2105
|
+
if n == 1:
|
2106
|
+
return 0
|
2107
|
+
|
2108
|
+
# Copying the whole graph to obtain the list of neighbors quicker than by
|
2109
|
+
# calling out_neighbors. This data structure is well documented in the
|
2110
|
+
# module sage.graphs.base.static_sparse_graph
|
2111
|
+
cdef StaticSparseCGraph cg
|
2112
|
+
cdef short_digraph sd
|
2113
|
+
if isinstance(G, StaticSparseBackend):
|
2114
|
+
cg = <StaticSparseCGraph> G._cg
|
2115
|
+
sd = <short_digraph> cg.g
|
2116
|
+
else:
|
2117
|
+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=list(G))
|
2118
|
+
|
2119
|
+
# allocated some data structures
|
2120
|
+
cdef bitset_t seen
|
2121
|
+
bitset_init(seen, n)
|
2122
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
2123
|
+
cdef uint32_t * distances = <uint32_t *> mem.allocarray(2 * n, sizeof(uint32_t))
|
2124
|
+
cdef uint32_t * waiting_list = distances + n
|
2125
|
+
|
2126
|
+
cdef uint64_t s = 0
|
2127
|
+
cdef uint32_t u, v
|
2128
|
+
cdef uint32_t ecc
|
2129
|
+
for u in range(n):
|
2130
|
+
ecc = simple_BFS(sd, u, distances, NULL, waiting_list, seen)
|
2131
|
+
if ecc == UINT32_MAX:
|
2132
|
+
# the graph is not connected
|
2133
|
+
s = UINT64_MAX
|
2134
|
+
break
|
2135
|
+
for v in range(0 if G.is_directed() else (u + 1), n):
|
2136
|
+
s += distances[v]
|
2137
|
+
|
2138
|
+
if not isinstance(G, StaticSparseBackend):
|
2139
|
+
free_short_digraph(sd)
|
2140
|
+
bitset_free(seen)
|
2141
|
+
|
2142
|
+
if s == UINT64_MAX:
|
2143
|
+
from sage.rings.infinity import Infinity
|
2144
|
+
return +Infinity
|
2145
|
+
return s
|
2146
|
+
|
2147
|
+
|
2148
|
+
################
|
2149
|
+
# Szeged index #
|
2150
|
+
################
|
2151
|
+
|
2152
|
+
cdef uint64_t c_szeged_index_low_memory(short_digraph sd) noexcept:
|
2153
|
+
r"""
|
2154
|
+
Return the Szeged index of the graph.
|
2155
|
+
|
2156
|
+
Let `G = (V, E)` be a connected simple graph, and for any `uv\in E`, let
|
2157
|
+
`N_u(uv) = \{w\in V:d(u,w)<d(v,w)\}` and `n_u(uv)=|N_u(uv)|`. The Szeged
|
2158
|
+
index of `G` is then defined as [KRG1996]_ as `\sum_{uv \in
|
2159
|
+
E(G)}n_u(uv)\times n_v(uv)`.
|
2160
|
+
|
2161
|
+
To determine `N_u(uv)`, this method performs a breadth first search (BFS)
|
2162
|
+
from each vertex `s \in V`. Then, each time an edge `uv` visited by the BFS
|
2163
|
+
is such that `d(s, u) < d(s, v)`, it adds 1 to `N_u(uv)`. Since this method
|
2164
|
+
assumes that the graph is undirected, the graph `sd` has both arcs `uv` and
|
2165
|
+
`vu`. Using one counter per arc, the counter for arc `uv` records the number
|
2166
|
+
of vertices that are closer to the side `u` of edge `uv`, and the counter
|
2167
|
+
for arc `vu` records the number of vertices that are closer to the side `v`
|
2168
|
+
of edge `uv`.
|
2169
|
+
|
2170
|
+
This method assumes that the input graph has no loops or multiple edges.
|
2171
|
+
|
2172
|
+
EXAMPLES::
|
2173
|
+
|
2174
|
+
sage: graphs.CycleGraph(4).szeged_index(algorithm='low')
|
2175
|
+
16
|
2176
|
+
"""
|
2177
|
+
cdef size_t n = sd.n
|
2178
|
+
if n <= 1:
|
2179
|
+
return 0
|
2180
|
+
if n == 2:
|
2181
|
+
return 1
|
2182
|
+
|
2183
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
2184
|
+
cdef uint32_t * current_layer = <uint32_t *> mem.malloc(n * sizeof(uint32_t))
|
2185
|
+
cdef uint32_t n_current
|
2186
|
+
cdef uint32_t * next_layer = <uint32_t *> mem.malloc(n * sizeof(uint32_t))
|
2187
|
+
cdef uint32_t n_next
|
2188
|
+
cdef uint32_t * seen = <uint32_t *> mem.calloc(n, sizeof(uint32_t))
|
2189
|
+
cdef uint32_t seen_value = 0
|
2190
|
+
|
2191
|
+
# For each edge e = uv, we have 2 arcs. Let p_uv be a pointer to arc uv and
|
2192
|
+
# p_vu a pointer to arc vu. The index of arc uv is p_uv - sd.edges. During a
|
2193
|
+
# BFS from source, we add 1 to counter[p_uv - sd.edges] if vertex u is closer
|
2194
|
+
# from source than v and 1 to counter[p_vu - sd.edges] if vertex v is closer
|
2195
|
+
# from source than u. The Szeged index is then
|
2196
|
+
# sum_{e=uv} counter[p_uv - sd.edges] * counter[p_vu - sd.edges]
|
2197
|
+
cdef uint32_t * counter = <uint32_t *> mem.calloc(2 * sd.m, sizeof(uint32_t))
|
2198
|
+
|
2199
|
+
cdef uint32_t source, u, v, i
|
2200
|
+
cdef uint32_t* p_uv
|
2201
|
+
cdef uint32_t* p_end
|
2202
|
+
|
2203
|
+
sig_on()
|
2204
|
+
for source in range(n):
|
2205
|
+
|
2206
|
+
next_layer[0] = source
|
2207
|
+
n_next = 1
|
2208
|
+
seen_value += 1
|
2209
|
+
|
2210
|
+
while n_next:
|
2211
|
+
# Go to next layer
|
2212
|
+
current_layer, next_layer = next_layer, current_layer
|
2213
|
+
n_current, n_next = n_next, 0
|
2214
|
+
|
2215
|
+
# Mark all vertices in current layer as seen
|
2216
|
+
for i in range(n_current):
|
2217
|
+
seen[current_layer[i]] = seen_value
|
2218
|
+
|
2219
|
+
for i in range(n_current):
|
2220
|
+
u = current_layer[i]
|
2221
|
+
|
2222
|
+
# Visit all (out) neighbors of u
|
2223
|
+
p_uv = sd.neighbors[u]
|
2224
|
+
p_end = sd.neighbors[u + 1]
|
2225
|
+
while p_uv < p_end:
|
2226
|
+
v = p_uv[0]
|
2227
|
+
if seen[v] != seen_value:
|
2228
|
+
# u is closer to the source
|
2229
|
+
counter[p_uv - sd.edges] += 1
|
2230
|
+
|
2231
|
+
# Ensure that v is added only once for next_level
|
2232
|
+
if seen[v] != seen_value + 1:
|
2233
|
+
next_layer[n_next] = v
|
2234
|
+
n_next += 1
|
2235
|
+
seen[v] = seen_value + 1
|
2236
|
+
|
2237
|
+
p_uv += 1
|
2238
|
+
sig_off()
|
2239
|
+
|
2240
|
+
cdef uint64_t s = 0
|
2241
|
+
cdef uint32_t* p_vu
|
2242
|
+
|
2243
|
+
sig_on()
|
2244
|
+
for u in range(n - 1):
|
2245
|
+
p_uv = sd.neighbors[u]
|
2246
|
+
p_end = sd.neighbors[u + 1]
|
2247
|
+
while p_uv < p_end:
|
2248
|
+
v = p_uv[0]
|
2249
|
+
if u < v:
|
2250
|
+
# Get the pointer to arc vu
|
2251
|
+
p_vu = has_edge(sd, v, u)
|
2252
|
+
s += counter[p_uv - sd.edges] * counter[p_vu - sd.edges]
|
2253
|
+
|
2254
|
+
p_uv += 1
|
2255
|
+
sig_off()
|
2256
|
+
|
2257
|
+
return s
|
2258
|
+
|
2259
|
+
|
2260
|
+
cdef uint64_t c_szeged_index_high_memory(short_digraph sd) noexcept:
|
2261
|
+
r"""
|
2262
|
+
Return the Szeged index of the graph.
|
2263
|
+
|
2264
|
+
Let `G = (V, E)` be a connected graph, and for any `uv\in E`, let `N_u(uv) =
|
2265
|
+
\{w\in V:d(u,w)<d(v,w)\}` and `n_u(uv)=|N_u(uv)|`. The Szeged index of `G`
|
2266
|
+
is then defined as [KRG1996]_ as `\sum_{uv \in E(G)}n_u(uv)\times n_v(uv)`.
|
2267
|
+
|
2268
|
+
EXAMPLES::
|
2269
|
+
|
2270
|
+
sage: graphs.CycleGraph(4).szeged_index(algorithm='high')
|
2271
|
+
16
|
2272
|
+
"""
|
2273
|
+
cdef int n = sd.n
|
2274
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
2275
|
+
cdef unsigned short* distances = <unsigned short*> mem.malloc(n * n * sizeof(unsigned short))
|
2276
|
+
|
2277
|
+
# Compute all pairs shortest path
|
2278
|
+
c_all_pairs_shortest_path_BFS(sd, NULL, distances, NULL)
|
2279
|
+
|
2280
|
+
cdef uint32_t* p_uv
|
2281
|
+
cdef uint32_t* p_end
|
2282
|
+
cdef uint32_t u, v, w
|
2283
|
+
cdef unsigned short* du
|
2284
|
+
cdef unsigned short* dv
|
2285
|
+
cdef uint32_t n1, n2
|
2286
|
+
cdef uint64_t s = 0
|
2287
|
+
|
2288
|
+
for u in range(n):
|
2289
|
+
du = distances + u * n
|
2290
|
+
p_uv = sd.neighbors[u]
|
2291
|
+
p_end = sd.neighbors[u + 1]
|
2292
|
+
while p_uv < p_end:
|
2293
|
+
v = p_uv[0]
|
2294
|
+
if u < v:
|
2295
|
+
dv = distances + v * n
|
2296
|
+
n1 = n2 = 0
|
2297
|
+
for w in range(n):
|
2298
|
+
if du[w] < dv[w]:
|
2299
|
+
n1 += 1
|
2300
|
+
elif dv[w] < du[w]:
|
2301
|
+
n2 += 1
|
2302
|
+
|
2303
|
+
s += n1 * n2
|
2304
|
+
p_uv += 1
|
2305
|
+
|
2306
|
+
return s
|
2307
|
+
|
2308
|
+
|
2309
|
+
def szeged_index(G, algorithm=None):
|
2310
|
+
r"""
|
2311
|
+
Return the Szeged index of the graph `G`.
|
2312
|
+
|
2313
|
+
Let `G = (V, E)` be a connected graph, and for any `uv\in E`, let `N_u(uv) =
|
2314
|
+
\{w\in V:d(u,w)<d(v,w)\}` and `n_u(uv)=|N_u(uv)|`. The Szeged index of `G`
|
2315
|
+
is then defined as [KRG1996]_
|
2316
|
+
|
2317
|
+
.. MATH::
|
2318
|
+
|
2319
|
+
`\sum_{uv \in E(G)}n_u(uv)\times n_v(uv)`
|
2320
|
+
|
2321
|
+
See the :wikipedia:`Szeged_index` for more details.
|
2322
|
+
|
2323
|
+
INPUT:
|
2324
|
+
|
2325
|
+
- ``G`` -- a Sage graph
|
2326
|
+
|
2327
|
+
- ``algorithm`` -- string (default: ``None``); algorithm to use among:
|
2328
|
+
|
2329
|
+
- ``'low'`` -- algorithm with time complexity in `O(nm)` and space
|
2330
|
+
complexity in `O(m)`. This implementation is currently valid only for
|
2331
|
+
simple (without loops or multiple edges) connected graphs.
|
2332
|
+
|
2333
|
+
- ``'high'`` -- algorithm with time complexity in `O(nm)` and space
|
2334
|
+
complexity in `O(n^2)`. It cannot be used on graphs with more than
|
2335
|
+
`65536 = 2^{16}` vertices.
|
2336
|
+
|
2337
|
+
By default (``None``), the ``'low'`` algorithm is used for graphs and the
|
2338
|
+
``'high'`` algorithm for digraphs.
|
2339
|
+
|
2340
|
+
EXAMPLES:
|
2341
|
+
|
2342
|
+
True for any connected graph [KRG1996]_::
|
2343
|
+
|
2344
|
+
sage: from sage.graphs.distances_all_pairs import szeged_index
|
2345
|
+
sage: g = graphs.PetersenGraph()
|
2346
|
+
sage: g.wiener_index() <= szeged_index(g)
|
2347
|
+
True
|
2348
|
+
|
2349
|
+
True for all trees [KRG1996]_::
|
2350
|
+
|
2351
|
+
sage: g = Graph()
|
2352
|
+
sage: g.add_edges(graphs.CubeGraph(5).min_spanning_tree())
|
2353
|
+
sage: g.wiener_index() == szeged_index(g)
|
2354
|
+
True
|
2355
|
+
|
2356
|
+
Check that both algorithms return same value::
|
2357
|
+
|
2358
|
+
sage: # long time, needs networkx
|
2359
|
+
sage: G = graphs.RandomBarabasiAlbert(100, 2)
|
2360
|
+
sage: a = szeged_index(G, algorithm='low')
|
2361
|
+
sage: b = szeged_index(G, algorithm='high')
|
2362
|
+
sage: a == b
|
2363
|
+
True
|
2364
|
+
|
2365
|
+
The Szeged index of a directed circuit of order `n` is `(n-1)^2`::
|
2366
|
+
|
2367
|
+
sage: [digraphs.Circuit(n).szeged_index() for n in range(1, 8)]
|
2368
|
+
[0, 1, 4, 9, 16, 25, 36]
|
2369
|
+
|
2370
|
+
TESTS:
|
2371
|
+
|
2372
|
+
Not defined when the graph is not connected (:issue:`26803`)::
|
2373
|
+
|
2374
|
+
sage: szeged_index(Graph({0: [1], 2: []}))
|
2375
|
+
Traceback (most recent call last):
|
2376
|
+
...
|
2377
|
+
ValueError: the Szeged index is defined for connected graphs only
|
2378
|
+
|
2379
|
+
Directed graphs must be strongly connected::
|
2380
|
+
|
2381
|
+
sage: szeged_index(digraphs.Path(2))
|
2382
|
+
Traceback (most recent call last):
|
2383
|
+
...
|
2384
|
+
ValueError: the Szeged index is defined for strongly connected digraphs only
|
2385
|
+
|
2386
|
+
Wrong name of algorithm::
|
2387
|
+
|
2388
|
+
sage: szeged_index(Graph(1), algorithm='wheel')
|
2389
|
+
Traceback (most recent call last):
|
2390
|
+
...
|
2391
|
+
ValueError: unknown algorithm 'wheel'
|
2392
|
+
|
2393
|
+
Algorithm `"low"` is for graphs without loops or multiple edges::
|
2394
|
+
|
2395
|
+
sage: szeged_index(Graph([(0, 0)], loops=True), algorithm='low')
|
2396
|
+
Traceback (most recent call last):
|
2397
|
+
...
|
2398
|
+
ValueError: the 'low' algorithm is for simple connected undirected graphs only
|
2399
|
+
sage: szeged_index(Graph([(0, 1), (0, 1)], multiedges=True), algorithm='low')
|
2400
|
+
Traceback (most recent call last):
|
2401
|
+
...
|
2402
|
+
ValueError: the 'low' algorithm is for simple connected undirected graphs only
|
2403
|
+
sage: szeged_index(digraphs.Circuit(3), algorithm='low')
|
2404
|
+
Traceback (most recent call last):
|
2405
|
+
...
|
2406
|
+
ValueError: the 'low' algorithm cannot be used on digraphs
|
2407
|
+
|
2408
|
+
Immutable graphs::
|
2409
|
+
|
2410
|
+
sage: G = graphs.RandomGNP(10, .3)
|
2411
|
+
sage: G.add_edges(graphs.RandomTree(10).edges())
|
2412
|
+
sage: G._backend
|
2413
|
+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
|
2414
|
+
sage: H = Graph(G, immutable=True)
|
2415
|
+
sage: H._backend
|
2416
|
+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
|
2417
|
+
sage: szeged_index(G) == szeged_index(H)
|
2418
|
+
True
|
2419
|
+
"""
|
2420
|
+
if not G.is_connected():
|
2421
|
+
raise ValueError("the Szeged index is defined for connected graphs only")
|
2422
|
+
if G.is_directed() and not G.is_strongly_connected():
|
2423
|
+
raise ValueError("the Szeged index is defined for "
|
2424
|
+
"strongly connected digraphs only")
|
2425
|
+
if G.is_directed() and algorithm == "low":
|
2426
|
+
raise ValueError("the 'low' algorithm cannot be used on digraphs")
|
2427
|
+
|
2428
|
+
if algorithm is None:
|
2429
|
+
algorithm = "high" if G.is_directed() else "low"
|
2430
|
+
|
2431
|
+
elif algorithm not in ["low", "high"]:
|
2432
|
+
raise ValueError(f"unknown algorithm '{algorithm}'")
|
2433
|
+
|
2434
|
+
if algorithm == "low" and (G.has_loops() or G.has_multiple_edges()):
|
2435
|
+
raise ValueError("the 'low' algorithm is for simple connected "
|
2436
|
+
"undirected graphs only")
|
2437
|
+
|
2438
|
+
if G.order() <= 1:
|
2439
|
+
return 0
|
2440
|
+
|
2441
|
+
cdef StaticSparseCGraph cg
|
2442
|
+
cdef short_digraph sd
|
2443
|
+
if isinstance(G, StaticSparseBackend):
|
2444
|
+
cg = <StaticSparseCGraph> G._cg
|
2445
|
+
sd = <short_digraph> cg.g
|
2446
|
+
else:
|
2447
|
+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=list(G))
|
2448
|
+
|
2449
|
+
cdef uint64_t s
|
2450
|
+
|
2451
|
+
if algorithm == "low":
|
2452
|
+
s = c_szeged_index_low_memory(sd)
|
2453
|
+
else:
|
2454
|
+
s = c_szeged_index_high_memory(sd)
|
2455
|
+
|
2456
|
+
if not isinstance(G, StaticSparseBackend):
|
2457
|
+
free_short_digraph(sd)
|
2458
|
+
return s
|
2459
|
+
|
2460
|
+
|
2461
|
+
##########################
|
2462
|
+
# Distances distribution #
|
2463
|
+
##########################
|
2464
|
+
|
2465
|
+
def distances_distribution(G):
|
2466
|
+
r"""
|
2467
|
+
Return the distances distribution of the (di)graph in a dictionary.
|
2468
|
+
|
2469
|
+
This method *ignores all edge labels*, so that the distance considered is
|
2470
|
+
the topological distance.
|
2471
|
+
|
2472
|
+
OUTPUT:
|
2473
|
+
|
2474
|
+
A dictionary ``d`` such that the number of pairs of vertices at distance
|
2475
|
+
``k`` (if any) is equal to `d[k] \cdot |V(G)| \cdot (|V(G)|-1)`.
|
2476
|
+
|
2477
|
+
.. NOTE::
|
2478
|
+
|
2479
|
+
We consider that two vertices that do not belong to the same connected
|
2480
|
+
component are at infinite distance, and we do not take the trivial pairs
|
2481
|
+
of vertices `(v, v)` at distance `0` into account. Empty (di)graphs and
|
2482
|
+
(di)graphs of order 1 have no paths and so we return the empty
|
2483
|
+
dictionary ``{}``.
|
2484
|
+
|
2485
|
+
EXAMPLES:
|
2486
|
+
|
2487
|
+
An empty Graph::
|
2488
|
+
|
2489
|
+
sage: g = Graph()
|
2490
|
+
sage: g.distances_distribution()
|
2491
|
+
{}
|
2492
|
+
|
2493
|
+
A Graph of order 1::
|
2494
|
+
|
2495
|
+
sage: g = Graph()
|
2496
|
+
sage: g.add_vertex(1)
|
2497
|
+
sage: g.distances_distribution()
|
2498
|
+
{}
|
2499
|
+
|
2500
|
+
A Graph of order 2 without edge::
|
2501
|
+
|
2502
|
+
sage: g = Graph()
|
2503
|
+
sage: g.add_vertices([1,2])
|
2504
|
+
sage: g.distances_distribution()
|
2505
|
+
{+Infinity: 1}
|
2506
|
+
|
2507
|
+
The Petersen Graph::
|
2508
|
+
|
2509
|
+
sage: g = graphs.PetersenGraph()
|
2510
|
+
sage: g.distances_distribution()
|
2511
|
+
{1: 1/3, 2: 2/3}
|
2512
|
+
|
2513
|
+
A graph with multiple disconnected components::
|
2514
|
+
|
2515
|
+
sage: g = graphs.PetersenGraph()
|
2516
|
+
sage: g.add_edge('good','wine')
|
2517
|
+
sage: g.distances_distribution()
|
2518
|
+
{1: 8/33, 2: 5/11, +Infinity: 10/33}
|
2519
|
+
|
2520
|
+
The de Bruijn digraph dB(2,3)::
|
2521
|
+
|
2522
|
+
sage: D = digraphs.DeBruijn(2,3) # needs sage.combinat
|
2523
|
+
sage: D.distances_distribution() # needs sage.combinat
|
2524
|
+
{1: 1/4, 2: 11/28, 3: 5/14}
|
2525
|
+
|
2526
|
+
TESTS:
|
2527
|
+
|
2528
|
+
Immutable graphs::
|
2529
|
+
|
2530
|
+
sage: G = graphs.RandomGNP(10, .5)
|
2531
|
+
sage: G._backend
|
2532
|
+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
|
2533
|
+
sage: H = Graph(G, immutable=True)
|
2534
|
+
sage: H._backend
|
2535
|
+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
|
2536
|
+
sage: G.distances_distribution() == H.distances_distribution()
|
2537
|
+
True
|
2538
|
+
"""
|
2539
|
+
cdef size_t n = G.order()
|
2540
|
+
if n <= 1:
|
2541
|
+
return {}
|
2542
|
+
|
2543
|
+
cdef StaticSparseCGraph cg
|
2544
|
+
cdef short_digraph sd
|
2545
|
+
if isinstance(G, StaticSparseBackend):
|
2546
|
+
cg = <StaticSparseCGraph> G._cg
|
2547
|
+
sd = <short_digraph> cg.g
|
2548
|
+
else:
|
2549
|
+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=list(G))
|
2550
|
+
|
2551
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
2552
|
+
cdef uint32_t * distances = <uint32_t *> mem.allocarray(2 * n, sizeof(uint32_t))
|
2553
|
+
cdef uint32_t * waiting_list = distances + n
|
2554
|
+
cdef uint64_t * count = <uint64_t *> mem.calloc(n, sizeof(uint64_t))
|
2555
|
+
cdef bitset_t seen
|
2556
|
+
bitset_init(seen, n)
|
2557
|
+
|
2558
|
+
# We count the number of pairs at equal distances
|
2559
|
+
cdef uint32_t u, v
|
2560
|
+
cdef uint64_t count_inf = 0
|
2561
|
+
for u in range(n):
|
2562
|
+
ecc = simple_BFS(sd, u, distances, NULL, waiting_list, seen)
|
2563
|
+
if ecc == UINT32_MAX:
|
2564
|
+
for v in range(n):
|
2565
|
+
if bitset_in(seen, v):
|
2566
|
+
count[distances[v]] += 1
|
2567
|
+
count_inf += n - bitset_len(seen)
|
2568
|
+
else:
|
2569
|
+
for v in range(n):
|
2570
|
+
count[distances[v]] += 1
|
2571
|
+
|
2572
|
+
if not isinstance(G, StaticSparseBackend):
|
2573
|
+
free_short_digraph(sd)
|
2574
|
+
bitset_free(seen)
|
2575
|
+
|
2576
|
+
from sage.rings.infinity import Infinity
|
2577
|
+
from sage.rings.rational_field import QQ
|
2578
|
+
|
2579
|
+
# We normalize the distribution
|
2580
|
+
cdef uint64_t NN = n * (n - 1)
|
2581
|
+
cdef dict distr = {+Infinity: QQ((count_inf, NN))} if count_inf else {}
|
2582
|
+
cdef size_t d
|
2583
|
+
for d in range(1, n):
|
2584
|
+
if count[d]:
|
2585
|
+
distr[d] = QQ((count[d], NN))
|
2586
|
+
|
2587
|
+
return distr
|
2588
|
+
|
2589
|
+
|
2590
|
+
###################
|
2591
|
+
# Antipodal graph #
|
2592
|
+
###################
|
2593
|
+
|
2594
|
+
def antipodal_graph(G):
|
2595
|
+
r"""
|
2596
|
+
Return the antipodal graph of `G`.
|
2597
|
+
|
2598
|
+
The antipodal graph of a graph `G` has the same vertex set of `G` and
|
2599
|
+
two vertices are adjacent if their distance in `G` is equal to the
|
2600
|
+
diameter of `G`.
|
2601
|
+
|
2602
|
+
This method first computes the eccentricity of all vertices and determines
|
2603
|
+
the diameter of the graph. Then, it for each vertex `u` with eccentricity
|
2604
|
+
the diameter, it computes BFS distances from `u` and add an edge in the
|
2605
|
+
antipodal graph for each vertex `v` at diameter distance from `u`
|
2606
|
+
(i.e., for each antipodal vertex).
|
2607
|
+
|
2608
|
+
The drawback of this method is that some BFS distances may be computed
|
2609
|
+
twice, one time to determine the eccentricities and another time is the
|
2610
|
+
vertex has eccentricity equal to the diameter. However, in practive, this is
|
2611
|
+
much more efficient. See the documentation of method
|
2612
|
+
:meth:`c_eccentricity_DHV`.
|
2613
|
+
|
2614
|
+
EXAMPLES:
|
2615
|
+
|
2616
|
+
The antipodal graph of a grid graph has only 2 edges::
|
2617
|
+
|
2618
|
+
sage: from sage.graphs.distances_all_pairs import antipodal_graph
|
2619
|
+
sage: G = graphs.Grid2dGraph(5, 5)
|
2620
|
+
sage: A = antipodal_graph(G)
|
2621
|
+
sage: A.order(), A.size()
|
2622
|
+
(25, 2)
|
2623
|
+
|
2624
|
+
The antipodal graph of a disjoint union of cliques is its complement::
|
2625
|
+
|
2626
|
+
sage: from sage.graphs.distances_all_pairs import antipodal_graph
|
2627
|
+
sage: G = graphs.CompleteGraph(3) * 3
|
2628
|
+
sage: A = antipodal_graph(G)
|
2629
|
+
sage: A.is_isomorphic(G.complement())
|
2630
|
+
True
|
2631
|
+
|
2632
|
+
The antipodal graph can also be constructed as the
|
2633
|
+
:meth:`sage.graphs.generic_graph.distance_graph` for diameter distance::
|
2634
|
+
|
2635
|
+
sage: from sage.graphs.distances_all_pairs import antipodal_graph
|
2636
|
+
sage: G = graphs.RandomGNP(10, .2)
|
2637
|
+
sage: A = antipodal_graph(G)
|
2638
|
+
sage: B = G.distance_graph(G.diameter())
|
2639
|
+
sage: A.is_isomorphic(B)
|
2640
|
+
True
|
2641
|
+
|
2642
|
+
TESTS::
|
2643
|
+
|
2644
|
+
sage: from sage.graphs.distances_all_pairs import antipodal_graph
|
2645
|
+
sage: antipodal_graph(Graph())
|
2646
|
+
Traceback (most recent call last):
|
2647
|
+
...
|
2648
|
+
ValueError: the antipodal graph of the empty graph is not defined
|
2649
|
+
sage: antipodal_graph(DiGraph(1))
|
2650
|
+
Traceback (most recent call last):
|
2651
|
+
...
|
2652
|
+
ValueError: this method is defined for undirected graphs only
|
2653
|
+
sage: antipodal_graph(Graph(1))
|
2654
|
+
Antipodal graph of Graph on 1 vertex: Looped graph on 1 vertex
|
2655
|
+
sage: antipodal_graph(Graph(2)).edges(sort=True, labels=False)
|
2656
|
+
[(0, 1)]
|
2657
|
+
|
2658
|
+
Immutable graphs::
|
2659
|
+
|
2660
|
+
sage: G = graphs.RandomGNP(10, .5)
|
2661
|
+
sage: G._backend
|
2662
|
+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
|
2663
|
+
sage: H = Graph(G, immutable=True)
|
2664
|
+
sage: H._backend
|
2665
|
+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
|
2666
|
+
sage: antipodal_graph(G).is_isomorphic(antipodal_graph(H))
|
2667
|
+
True
|
2668
|
+
"""
|
2669
|
+
if not G:
|
2670
|
+
raise ValueError("the antipodal graph of the empty graph is not defined")
|
2671
|
+
if G.is_directed():
|
2672
|
+
raise ValueError("this method is defined for undirected graphs only")
|
2673
|
+
|
2674
|
+
from sage.graphs.graph import Graph
|
2675
|
+
|
2676
|
+
cdef uint32_t n = G.order()
|
2677
|
+
name = f"Antipodal graph of {G}"
|
2678
|
+
if n == 1:
|
2679
|
+
return Graph(list(zip(G, G)), loops=True, name=name)
|
2680
|
+
|
2681
|
+
import copy
|
2682
|
+
A = Graph(name=name, pos=copy.deepcopy(G.get_pos()))
|
2683
|
+
|
2684
|
+
if not G.is_connected():
|
2685
|
+
import itertools
|
2686
|
+
CC = G.connected_components(sort=False)
|
2687
|
+
for c1, c2 in itertools.combinations(CC, 2):
|
2688
|
+
A.add_edges(itertools.product(c1, c2))
|
2689
|
+
return A
|
2690
|
+
|
2691
|
+
cdef list int_to_vertex
|
2692
|
+
cdef StaticSparseCGraph cg
|
2693
|
+
cdef short_digraph sd
|
2694
|
+
if isinstance(G, StaticSparseBackend):
|
2695
|
+
cg = <StaticSparseCGraph> G._cg
|
2696
|
+
sd = <short_digraph> cg.g
|
2697
|
+
int_to_vertex = cg._vertex_to_labels
|
2698
|
+
else:
|
2699
|
+
int_to_vertex = list(G)
|
2700
|
+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
|
2701
|
+
|
2702
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
2703
|
+
cdef uint32_t * distances = <uint32_t *> mem.allocarray(2 * n, sizeof(uint32_t))
|
2704
|
+
cdef uint32_t * waiting_list = distances + n
|
2705
|
+
cdef bitset_t seen
|
2706
|
+
bitset_init(seen, n)
|
2707
|
+
|
2708
|
+
# Get the eccentricity of all vertices
|
2709
|
+
cdef uint32_t* ecc = c_eccentricity_DHV(sd)
|
2710
|
+
cdef uint32_t i
|
2711
|
+
cdef uint32_t diam = 0
|
2712
|
+
for i in range(n):
|
2713
|
+
if ecc[i] > diam:
|
2714
|
+
diam = ecc[i]
|
2715
|
+
|
2716
|
+
cdef uint32_t ui, vj, j
|
2717
|
+
for ui in range(n):
|
2718
|
+
if ecc[ui] == diam:
|
2719
|
+
_ = simple_BFS(sd, ui, distances, NULL, waiting_list, seen)
|
2720
|
+
u = int_to_vertex[ui]
|
2721
|
+
j = n - 1
|
2722
|
+
while distances[waiting_list[j]] == diam:
|
2723
|
+
vj = waiting_list[j]
|
2724
|
+
if ui < vj: # avoid adding twice the same edge
|
2725
|
+
A.add_edge(u, int_to_vertex[vj])
|
2726
|
+
j -= 1
|
2727
|
+
|
2728
|
+
if not isinstance(G, StaticSparseBackend):
|
2729
|
+
free_short_digraph(sd)
|
2730
|
+
bitset_free(seen)
|
2731
|
+
|
2732
|
+
A.add_vertices(G)
|
2733
|
+
return A
|
2734
|
+
|
2735
|
+
|
2736
|
+
##################
|
2737
|
+
# Floyd-Warshall #
|
2738
|
+
##################
|
2739
|
+
|
2740
|
+
def floyd_warshall(gg, paths=True, distances=False):
|
2741
|
+
r"""
|
2742
|
+
Compute the shortest path/distances between all pairs of vertices.
|
2743
|
+
|
2744
|
+
For more information on the Floyd-Warshall algorithm, see
|
2745
|
+
the :wikipedia:`Floyd-Warshall_algorithm`.
|
2746
|
+
|
2747
|
+
INPUT:
|
2748
|
+
|
2749
|
+
- ``gg`` -- the graph on which to work
|
2750
|
+
|
2751
|
+
- ``paths`` -- boolean (default: ``True``); whether to return the dictionary
|
2752
|
+
of shortest paths
|
2753
|
+
|
2754
|
+
- ``distances`` -- boolean (default: ``False``); whether to return the
|
2755
|
+
dictionary of distances
|
2756
|
+
|
2757
|
+
OUTPUT:
|
2758
|
+
|
2759
|
+
Depending on the input, this function return the dictionary of paths, the
|
2760
|
+
dictionary of distances, or a pair of dictionaries ``(distances, paths)``
|
2761
|
+
where ``distance[u][v]`` denotes the distance of a shortest path from `u` to
|
2762
|
+
`v` and ``paths[u][v]`` denotes an inneighbor `w` of `v` such that
|
2763
|
+
`dist(u,v) = 1 + dist(u,w)`.
|
2764
|
+
|
2765
|
+
.. WARNING::
|
2766
|
+
|
2767
|
+
Because this function works on matrices whose size is quadratic compared
|
2768
|
+
to the number of vertices, it uses short variables instead of long ones
|
2769
|
+
to divide by 2 the size in memory. This means that the current
|
2770
|
+
implementation does not run on a graph of more than 65536 nodes (this
|
2771
|
+
can be easily changed if necessary, but would require much more
|
2772
|
+
memory. It may be worth writing two versions). For information, the
|
2773
|
+
current version of the algorithm on a graph with `65536 = 2^{16}` nodes
|
2774
|
+
creates in memory `2` tables on `2^{32}` short elements (2bytes each),
|
2775
|
+
for a total of `2^{34}` bytes or `16` gigabytes. Let us also remember
|
2776
|
+
that if the memory size is quadratic, the algorithm runs in cubic time.
|
2777
|
+
|
2778
|
+
.. NOTE::
|
2779
|
+
|
2780
|
+
When ``paths = False`` the algorithm saves roughly half of the memory as
|
2781
|
+
it does not have to maintain the matrix of predecessors. However,
|
2782
|
+
setting ``distances=False`` produces no such effect as the algorithm can
|
2783
|
+
not run without computing them. They will not be returned, but they will
|
2784
|
+
be stored while the method is running.
|
2785
|
+
|
2786
|
+
EXAMPLES:
|
2787
|
+
|
2788
|
+
Shortest paths in a small grid ::
|
2789
|
+
|
2790
|
+
sage: g = graphs.Grid2dGraph(2,2)
|
2791
|
+
sage: from sage.graphs.distances_all_pairs import floyd_warshall
|
2792
|
+
sage: print(floyd_warshall(g))
|
2793
|
+
{(0, 0): {(0, 0): None, (0, 1): (0, 0), (1, 0): (0, 0), (1, 1): (0, 1)},
|
2794
|
+
(0, 1): {(0, 1): None, (0, 0): (0, 1), (1, 0): (0, 0), (1, 1): (0, 1)},
|
2795
|
+
(1, 0): {(1, 0): None, (0, 0): (1, 0), (0, 1): (0, 0), (1, 1): (1, 0)},
|
2796
|
+
(1, 1): {(1, 1): None, (0, 0): (0, 1), (0, 1): (1, 1), (1, 0): (1, 1)}}
|
2797
|
+
|
2798
|
+
Checking the distances are correct ::
|
2799
|
+
|
2800
|
+
sage: g = graphs.Grid2dGraph(5,5)
|
2801
|
+
sage: dist,path = floyd_warshall(g, distances=True)
|
2802
|
+
sage: all(dist[u][v] == g.distance(u, v) for u in g for v in g)
|
2803
|
+
True
|
2804
|
+
|
2805
|
+
Checking a random path is valid ::
|
2806
|
+
|
2807
|
+
sage: u,v = g.random_vertex(), g.random_vertex()
|
2808
|
+
sage: p = [v]
|
2809
|
+
sage: while p[0] is not None:
|
2810
|
+
....: p.insert(0,path[u][p[0]])
|
2811
|
+
sage: len(p) == dist[u][v] + 2
|
2812
|
+
True
|
2813
|
+
|
2814
|
+
Distances for all pairs of vertices in a diamond::
|
2815
|
+
|
2816
|
+
sage: g = graphs.DiamondGraph()
|
2817
|
+
sage: floyd_warshall(g, paths=False, distances=True)
|
2818
|
+
{0: {0: 0, 1: 1, 2: 1, 3: 2},
|
2819
|
+
1: {0: 1, 1: 0, 2: 1, 3: 1},
|
2820
|
+
2: {0: 1, 1: 1, 2: 0, 3: 1},
|
2821
|
+
3: {0: 2, 1: 1, 2: 1, 3: 0}}
|
2822
|
+
|
2823
|
+
TESTS:
|
2824
|
+
|
2825
|
+
Too large graphs::
|
2826
|
+
|
2827
|
+
sage: from sage.graphs.distances_all_pairs import floyd_warshall
|
2828
|
+
sage: floyd_warshall(Graph(65536))
|
2829
|
+
Traceback (most recent call last):
|
2830
|
+
...
|
2831
|
+
ValueError: the graph backend contains more than 65535 nodes
|
2832
|
+
"""
|
2833
|
+
from sage.rings.infinity import Infinity
|
2834
|
+
cdef CGraph g = <CGraph> gg._backend.c_graph()[0]
|
2835
|
+
|
2836
|
+
cdef list gverts = g.verts()
|
2837
|
+
|
2838
|
+
if not gverts:
|
2839
|
+
if distances and paths:
|
2840
|
+
return {}, {}
|
2841
|
+
return {}
|
2842
|
+
|
2843
|
+
cdef unsigned int n = max(gverts) + 1
|
2844
|
+
|
2845
|
+
if n >= <unsigned short> -1:
|
2846
|
+
raise ValueError("the graph backend contains more than " + str(<unsigned short> -1) + " nodes")
|
2847
|
+
|
2848
|
+
# All this just creates two tables prec[n][n] and dist[n][n]
|
2849
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
2850
|
+
cdef unsigned short* t_prec = NULL
|
2851
|
+
cdef unsigned short** prec = NULL
|
2852
|
+
# init dist
|
2853
|
+
cdef unsigned short* t_dist = <unsigned short*> mem.allocarray(n * n, sizeof(unsigned short))
|
2854
|
+
cdef unsigned short** dist = <unsigned short**> mem.allocarray(n, sizeof(unsigned short*))
|
2855
|
+
dist[0] = t_dist
|
2856
|
+
cdef unsigned int i
|
2857
|
+
for i in range(1, n):
|
2858
|
+
dist[i] = dist[i - 1] + n
|
2859
|
+
memset(t_dist, -1, n * n * sizeof(short))
|
2860
|
+
|
2861
|
+
cdef int v_int
|
2862
|
+
cdef int u_int
|
2863
|
+
cdef int w_int
|
2864
|
+
|
2865
|
+
# Copying the adjacency matrix (vertices at distance 1)
|
2866
|
+
for v_int in gverts:
|
2867
|
+
dist[v_int][v_int] = 0
|
2868
|
+
for u_int in g.out_neighbors(v_int):
|
2869
|
+
dist[v_int][u_int] = 1
|
2870
|
+
|
2871
|
+
if paths:
|
2872
|
+
# init prec
|
2873
|
+
t_prec = <unsigned short*> mem.allocarray(n * n, sizeof(unsigned short))
|
2874
|
+
prec = <unsigned short**> mem.allocarray(n, sizeof(unsigned short*))
|
2875
|
+
prec[0] = t_prec
|
2876
|
+
for i in range(1, n):
|
2877
|
+
prec[i] = prec[i - 1] + n
|
2878
|
+
memset(t_prec, 0, n * n * sizeof(short))
|
2879
|
+
# Copying the adjacency matrix (vertices at distance 1)
|
2880
|
+
for v_int in gverts:
|
2881
|
+
prec[v_int][v_int] = v_int
|
2882
|
+
for u_int in g.out_neighbors(v_int):
|
2883
|
+
prec[v_int][u_int] = v_int
|
2884
|
+
|
2885
|
+
# The algorithm itself.
|
2886
|
+
cdef unsigned short* dv
|
2887
|
+
cdef unsigned short* dw
|
2888
|
+
cdef int dvw
|
2889
|
+
cdef int val
|
2890
|
+
|
2891
|
+
for w_int in gverts:
|
2892
|
+
dw = dist[w_int]
|
2893
|
+
for v_int in gverts:
|
2894
|
+
dv = dist[v_int]
|
2895
|
+
dvw = dv[w_int]
|
2896
|
+
for u_int in gverts:
|
2897
|
+
val = dvw + dw[u_int]
|
2898
|
+
# If it is shorter to go from u to v through w, do it
|
2899
|
+
if dv[u_int] > val:
|
2900
|
+
dv[u_int] = val
|
2901
|
+
if paths:
|
2902
|
+
prec[v_int][u_int] = prec[w_int][u_int]
|
2903
|
+
|
2904
|
+
# Dictionaries of distance, precedent element, and integers
|
2905
|
+
cdef dict d_prec = {}
|
2906
|
+
cdef dict d_dist = {}
|
2907
|
+
cdef dict tmp_prec
|
2908
|
+
cdef dict tmp_dist
|
2909
|
+
|
2910
|
+
cdef CGraphBackend cgb = <CGraphBackend> gg._backend
|
2911
|
+
|
2912
|
+
for v_int in gverts:
|
2913
|
+
v = cgb.vertex_label(v_int)
|
2914
|
+
if paths:
|
2915
|
+
tmp_prec = {v: None}
|
2916
|
+
if distances:
|
2917
|
+
tmp_dist = {v: 0}
|
2918
|
+
dv = dist[v_int]
|
2919
|
+
for u_int in gverts:
|
2920
|
+
u = cgb.vertex_label(u_int)
|
2921
|
+
if v != u and dv[u_int] != <unsigned short> -1:
|
2922
|
+
if paths:
|
2923
|
+
tmp_prec[u] = cgb.vertex_label(prec[v_int][u_int])
|
2924
|
+
|
2925
|
+
if distances:
|
2926
|
+
tmp_dist[u] = dv[u_int]
|
2927
|
+
|
2928
|
+
if paths:
|
2929
|
+
d_prec[v] = tmp_prec
|
2930
|
+
if distances:
|
2931
|
+
d_dist[v] = tmp_dist
|
2932
|
+
|
2933
|
+
if distances and paths:
|
2934
|
+
return d_dist, d_prec
|
2935
|
+
if paths:
|
2936
|
+
return d_prec
|
2937
|
+
if distances:
|
2938
|
+
return d_dist
|