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,1038 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
r"""
|
3
|
+
Centrality
|
4
|
+
|
5
|
+
This module is meant for all functions related to centrality in networks.
|
6
|
+
|
7
|
+
.. csv-table::
|
8
|
+
:class: contentstable
|
9
|
+
:widths: 30, 70
|
10
|
+
:delim: |
|
11
|
+
|
12
|
+
:func:`centrality_betweenness` | Return the centrality betweenness of `G`
|
13
|
+
:func:`centrality_closeness_top_k` | Return the k most closeness central vertices of `G`
|
14
|
+
:func:`centrality_closeness_random_k` | Return an estimation of the closeness centrality of `G`.
|
15
|
+
|
16
|
+
Functions
|
17
|
+
---------
|
18
|
+
"""
|
19
|
+
|
20
|
+
from libc.string cimport memset
|
21
|
+
from libc.stdint cimport uint32_t
|
22
|
+
from cysignals.memory cimport check_allocarray, sig_free
|
23
|
+
from cysignals.signals cimport sig_check
|
24
|
+
from memory_allocator cimport MemoryAllocator
|
25
|
+
|
26
|
+
from sage.data_structures.bitset_base cimport *
|
27
|
+
from sage.graphs.base.static_sparse_backend cimport StaticSparseCGraph
|
28
|
+
from sage.graphs.base.static_sparse_backend cimport StaticSparseBackend
|
29
|
+
from sage.graphs.base.static_sparse_graph cimport *
|
30
|
+
from sage.libs.gmp.mpq cimport *
|
31
|
+
from sage.rings.rational cimport Rational
|
32
|
+
from sage.graphs.base.boost_graph import shortest_paths as boost_shortest_paths
|
33
|
+
import random
|
34
|
+
|
35
|
+
ctypedef fused numerical_type:
|
36
|
+
mpq_t
|
37
|
+
double
|
38
|
+
|
39
|
+
cimport cython
|
40
|
+
|
41
|
+
|
42
|
+
def centrality_betweenness(G, bint exact=False, bint normalize=True):
|
43
|
+
r"""
|
44
|
+
Return the centrality betweenness of `G`.
|
45
|
+
|
46
|
+
The centrality betweenness of a vertex `v\in G` is defined by:
|
47
|
+
|
48
|
+
.. MATH::
|
49
|
+
|
50
|
+
c(v) = \sum_{s\neq v \neq t} \frac{\#\{\text{shortest } st-\text{paths containing v}\}}
|
51
|
+
{\#\{\text{shortest } st-\text{paths}\}}
|
52
|
+
|
53
|
+
For more information, see the :wikipedia:`Betweenness_centrality`.
|
54
|
+
|
55
|
+
INPUT:
|
56
|
+
|
57
|
+
- ``G`` -- a (di)graph
|
58
|
+
|
59
|
+
- ``exact`` -- boolean (default: ``False``); whether to compute over
|
60
|
+
rationals or on ``double`` C variables
|
61
|
+
|
62
|
+
- ``normalize`` -- boolean (default: ``True``); whether to renormalize the
|
63
|
+
values by dividing them by `\binom {n-1} 2` (for graphs) or `2\binom {n-1}
|
64
|
+
2` (for digraphs).
|
65
|
+
|
66
|
+
ALGORITHM:
|
67
|
+
|
68
|
+
To compute `c(v)`, we fix `s` and define `c_s(v)` as the centrality of `v`
|
69
|
+
*due to* `s`, obtained from the formula above by running the sum over `t`
|
70
|
+
only. We obtain `c(v)=\sum_{s\neq v} c_s(v)`.
|
71
|
+
|
72
|
+
For every vertex `s`, we compute the value of `c_s(v)` for all `v`, using
|
73
|
+
the following remark (see [Brandes01]_):
|
74
|
+
|
75
|
+
Let `v_1,\ldots,v_k` be the out-neighbors of `v` such that
|
76
|
+
`dist(s,v_i) = dist(s,v) + 1`. Then
|
77
|
+
|
78
|
+
.. MATH::
|
79
|
+
|
80
|
+
c_s(v) = \sum_{1\leq i \leq k} c_s(v_i)
|
81
|
+
\frac{\#\{\text{shortest } sv_i-\text{paths}\}}
|
82
|
+
{\#\{\text{shortest } sv -\text{paths}\}}
|
83
|
+
|
84
|
+
The number of shortest paths between `s` and every other vertex can be
|
85
|
+
computed with a slightly modified BFS. While running this BFS we can also
|
86
|
+
store the list of the vertices `v_1,\ldots,v_k` associated with each `v`.
|
87
|
+
|
88
|
+
EXAMPLES::
|
89
|
+
|
90
|
+
sage: from sage.graphs.centrality import centrality_betweenness
|
91
|
+
sage: centrality_betweenness(digraphs.Circuit(6)) # abs tol 1e-10
|
92
|
+
{0: 0.5, 1: 0.5, 2: 0.5, 3: 0.5, 4: 0.5, 5: 0.5}
|
93
|
+
sage: centrality_betweenness(graphs.CycleGraph(6)) # abs tol 1e-10
|
94
|
+
{0: 0.2, 1: 0.2, 2: 0.2, 3: 0.2, 4: 0.2, 5: 0.2}
|
95
|
+
|
96
|
+
Exact computations::
|
97
|
+
|
98
|
+
sage: graphs.PetersenGraph().centrality_betweenness(exact=True)
|
99
|
+
{0: 1/12, 1: 1/12, 2: 1/12, 3: 1/12, 4: 1/12, 5: 1/12, 6: 1/12, 7: 1/12, 8: 1/12, 9: 1/12}
|
100
|
+
|
101
|
+
TESTS:
|
102
|
+
|
103
|
+
Compare with NetworkX::
|
104
|
+
|
105
|
+
sage: # needs networkx
|
106
|
+
sage: import networkx
|
107
|
+
sage: g = graphs.RandomGNP(100, .2)
|
108
|
+
sage: nw = networkx.betweenness_centrality(g.networkx_graph())
|
109
|
+
sage: sg = centrality_betweenness(g)
|
110
|
+
sage: max(abs(nw[x] - sg[x]) for x in g) # abs tol 1e-10
|
111
|
+
0
|
112
|
+
|
113
|
+
Stupid cases::
|
114
|
+
|
115
|
+
sage: centrality_betweenness(Graph())
|
116
|
+
{}
|
117
|
+
sage: centrality_betweenness(Graph(2))
|
118
|
+
{0: 0.0, 1: 0.0}
|
119
|
+
sage: centrality_betweenness(Graph(2), exact=1)
|
120
|
+
{0: 0, 1: 0}
|
121
|
+
|
122
|
+
The method is valid for immutable graphs::
|
123
|
+
|
124
|
+
sage: G = graphs.RandomGNP(10, .7)
|
125
|
+
sage: G._backend
|
126
|
+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
|
127
|
+
sage: H = Graph(G, immutable=True)
|
128
|
+
sage: H._backend
|
129
|
+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
|
130
|
+
sage: G.centrality_betweenness() == H.centrality_betweenness()
|
131
|
+
True
|
132
|
+
sage: G.centrality_betweenness(exact=True) == H.centrality_betweenness(exact=True)
|
133
|
+
True
|
134
|
+
"""
|
135
|
+
if exact:
|
136
|
+
return centrality_betweenness_C(G, <mpq_t> 0, normalize=normalize)
|
137
|
+
return centrality_betweenness_C(G, <double>0, normalize=normalize)
|
138
|
+
|
139
|
+
|
140
|
+
@cython.cdivision(True)
|
141
|
+
cdef dict centrality_betweenness_C(G, numerical_type _, bint normalize=True):
|
142
|
+
r"""
|
143
|
+
Return the centrality betweenness of G (C implementation).
|
144
|
+
|
145
|
+
INPUT:
|
146
|
+
|
147
|
+
- ``G`` -- a graph
|
148
|
+
|
149
|
+
- ``_`` -- this variable is ignored, only its type matters. If it is of type
|
150
|
+
``mpq_t`` then computations are made on `Q`, if it is ``double`` the
|
151
|
+
computations are made on ``double``.
|
152
|
+
|
153
|
+
- ``normalize`` -- boolean (default: ``True``); whether to renormalize the
|
154
|
+
values by dividing them by `\binom {n-1} 2` (for graphs) or `2\binom {n-1}
|
155
|
+
2` (for digraphs).
|
156
|
+
|
157
|
+
For more information, see the documentation of ``centrality_betweenness``.
|
158
|
+
"""
|
159
|
+
# Trivial case
|
160
|
+
if G.order() <= 2:
|
161
|
+
zero = 0. if numerical_type is double else Rational(0)
|
162
|
+
return {v: zero for v in G}
|
163
|
+
|
164
|
+
# A copy of G, for faster neighbor enumeration
|
165
|
+
cdef StaticSparseCGraph cg
|
166
|
+
cdef short_digraph g
|
167
|
+
|
168
|
+
# A second copy, to remember the edges used during the BFS (see doc)
|
169
|
+
cdef short_digraph bfs_dag
|
170
|
+
|
171
|
+
cdef list int_to_vertex
|
172
|
+
|
173
|
+
cdef int n = G.order()
|
174
|
+
|
175
|
+
cdef bitset_t seen # Vertices whose neighbors have been explored
|
176
|
+
cdef bitset_t next_layer # Unexplored neighbors of vertices in 'seen'
|
177
|
+
|
178
|
+
cdef uint32_t* queue = NULL # BFS queue
|
179
|
+
cdef uint32_t* degrees = NULL # degree[v] = nb of vertices which discovered v
|
180
|
+
|
181
|
+
cdef numerical_type* n_paths_from_source = NULL
|
182
|
+
cdef numerical_type* betweenness_source = NULL
|
183
|
+
cdef numerical_type* betweenness = NULL
|
184
|
+
cdef numerical_type mpq_tmp
|
185
|
+
|
186
|
+
cdef int layer_current_beginning
|
187
|
+
cdef int layer_current_end
|
188
|
+
cdef int layer_next_end
|
189
|
+
|
190
|
+
cdef int source, i, j, u, v
|
191
|
+
cdef uint32_t* p_tmp
|
192
|
+
cdef list betweenness_list
|
193
|
+
|
194
|
+
if numerical_type is mpq_t:
|
195
|
+
mpq_init(mpq_tmp)
|
196
|
+
|
197
|
+
try:
|
198
|
+
if isinstance(G, StaticSparseBackend):
|
199
|
+
cg = <StaticSparseCGraph> G._cg
|
200
|
+
g = <short_digraph> cg.g
|
201
|
+
int_to_vertex = cg._vertex_to_labels
|
202
|
+
else:
|
203
|
+
int_to_vertex = list(G)
|
204
|
+
init_short_digraph(g, G, edge_labelled=False, vertex_list=int_to_vertex)
|
205
|
+
|
206
|
+
init_reverse(bfs_dag, g)
|
207
|
+
|
208
|
+
queue = <uint32_t*> check_allocarray(n, sizeof(uint32_t))
|
209
|
+
degrees = <uint32_t*> check_allocarray(n, sizeof(uint32_t))
|
210
|
+
n_paths_from_source = <numerical_type*> check_allocarray(n, sizeof(numerical_type))
|
211
|
+
betweenness_source = <numerical_type*> check_allocarray(n, sizeof(numerical_type))
|
212
|
+
betweenness = <numerical_type*> check_allocarray(n, sizeof(numerical_type))
|
213
|
+
|
214
|
+
bitset_init(seen, n)
|
215
|
+
bitset_init(next_layer, n)
|
216
|
+
|
217
|
+
if numerical_type is double:
|
218
|
+
memset(betweenness, 0, n * sizeof(double))
|
219
|
+
else:
|
220
|
+
for i in range(n):
|
221
|
+
mpq_init(betweenness[i])
|
222
|
+
mpq_set_ui(betweenness[i], 0, 1)
|
223
|
+
mpq_init(betweenness_source[i])
|
224
|
+
mpq_init(n_paths_from_source[i])
|
225
|
+
|
226
|
+
for source in range(n):
|
227
|
+
|
228
|
+
if numerical_type is double:
|
229
|
+
memset(betweenness_source, 0, n * sizeof(double))
|
230
|
+
memset(n_paths_from_source, 0, n * sizeof(double))
|
231
|
+
n_paths_from_source[source] = 1
|
232
|
+
else:
|
233
|
+
for i in range(n):
|
234
|
+
mpq_set_ui(betweenness_source[i], 0, 1)
|
235
|
+
mpq_set_ui(n_paths_from_source[i], 0, 1)
|
236
|
+
mpq_set_ui(n_paths_from_source[source], 1, 1)
|
237
|
+
|
238
|
+
# initialize data
|
239
|
+
bitset_set_first_n(seen, 0)
|
240
|
+
bitset_add(seen, source)
|
241
|
+
bitset_set_first_n(next_layer, 0)
|
242
|
+
|
243
|
+
memset(degrees, 0, n * sizeof(uint32_t))
|
244
|
+
|
245
|
+
queue[0] = source
|
246
|
+
layer_current_beginning = 0
|
247
|
+
layer_current_end = 1
|
248
|
+
layer_next_end = 1
|
249
|
+
|
250
|
+
# The number of shortest paths from 'source' to every other vertex.
|
251
|
+
#
|
252
|
+
# It is a BFS. The graph is explored layer by layer.
|
253
|
+
while layer_current_beginning<layer_current_end:
|
254
|
+
|
255
|
+
# Looking for all non-discovered neighbors of some vertex of the
|
256
|
+
# current layer.
|
257
|
+
for j in range(layer_current_beginning, layer_current_end):
|
258
|
+
u = queue[j]
|
259
|
+
|
260
|
+
# List the neighbors of u
|
261
|
+
p_tmp = g.neighbors[u]
|
262
|
+
while p_tmp < g.neighbors[u + 1]:
|
263
|
+
v = p_tmp[0]
|
264
|
+
p_tmp += 1
|
265
|
+
|
266
|
+
# Is it a new vertex ?
|
267
|
+
if bitset_in(seen, v):
|
268
|
+
continue
|
269
|
+
|
270
|
+
# Is it the first time we see it ?
|
271
|
+
elif not bitset_in(next_layer, v):
|
272
|
+
bitset_add(next_layer, v)
|
273
|
+
queue[layer_next_end] = v
|
274
|
+
layer_next_end += 1
|
275
|
+
|
276
|
+
# update the count of paths and the BFS dag.
|
277
|
+
bfs_dag.neighbors[v][degrees[v]] = u
|
278
|
+
degrees[v] += 1
|
279
|
+
if numerical_type is double:
|
280
|
+
n_paths_from_source[v] += n_paths_from_source[u]
|
281
|
+
else:
|
282
|
+
mpq_add(n_paths_from_source[v], n_paths_from_source[v], n_paths_from_source[u])
|
283
|
+
|
284
|
+
# 'next_layer' becomes 'current_layer'
|
285
|
+
for j in range(layer_current_end, layer_next_end):
|
286
|
+
bitset_add(seen, queue[j])
|
287
|
+
|
288
|
+
layer_current_beginning = layer_current_end
|
289
|
+
layer_current_end = layer_next_end
|
290
|
+
|
291
|
+
# Compute the betweenness from the number of paths
|
292
|
+
#
|
293
|
+
# We enumerate vertices in reverse order of discovery.
|
294
|
+
for i in range(layer_current_end - 1, -1, -1):
|
295
|
+
u = queue[i]
|
296
|
+
for j in range(<int>degrees[u]):
|
297
|
+
v = bfs_dag.neighbors[u][j]
|
298
|
+
if v != source: # better to not 'if' but set it to 0 afterwards?
|
299
|
+
if numerical_type is double:
|
300
|
+
betweenness_source[v] += (betweenness_source[u] + 1) * (n_paths_from_source[v] / n_paths_from_source[u])
|
301
|
+
else:
|
302
|
+
mpq_set_ui(mpq_tmp, 1, 1)
|
303
|
+
mpq_add(mpq_tmp, betweenness_source[u], mpq_tmp)
|
304
|
+
mpq_mul(mpq_tmp, mpq_tmp, n_paths_from_source[v])
|
305
|
+
mpq_div(mpq_tmp, mpq_tmp, n_paths_from_source[u])
|
306
|
+
mpq_add(betweenness_source[v], betweenness_source[v], mpq_tmp)
|
307
|
+
|
308
|
+
# update betweenness from betweenness_source
|
309
|
+
for i in range(n):
|
310
|
+
if numerical_type is double:
|
311
|
+
betweenness[i] += betweenness_source[i]
|
312
|
+
else:
|
313
|
+
mpq_add(betweenness[i], betweenness[i], betweenness_source[i])
|
314
|
+
|
315
|
+
sig_check() # check for KeyboardInterrupt
|
316
|
+
|
317
|
+
if numerical_type is double:
|
318
|
+
betweenness_list = [betweenness[i] for i in range(n)]
|
319
|
+
else:
|
320
|
+
betweenness_list = [Rational(None) for x in range(n)]
|
321
|
+
|
322
|
+
for i in range(n):
|
323
|
+
(<Rational> (betweenness_list[i])).set_from_mpq(betweenness[i])
|
324
|
+
for i in range(n):
|
325
|
+
mpq_clear(betweenness_source[i])
|
326
|
+
mpq_clear(betweenness[i])
|
327
|
+
mpq_clear(n_paths_from_source[i])
|
328
|
+
mpq_clear(mpq_tmp)
|
329
|
+
|
330
|
+
finally:
|
331
|
+
if not isinstance(G, StaticSparseBackend):
|
332
|
+
free_short_digraph(g)
|
333
|
+
free_short_digraph(bfs_dag)
|
334
|
+
bitset_free(seen)
|
335
|
+
bitset_free(next_layer)
|
336
|
+
sig_free(queue)
|
337
|
+
sig_free(n_paths_from_source)
|
338
|
+
sig_free(degrees)
|
339
|
+
sig_free(betweenness_source)
|
340
|
+
sig_free(betweenness)
|
341
|
+
|
342
|
+
if not G.is_directed():
|
343
|
+
betweenness_list = [x / 2 for x in betweenness_list]
|
344
|
+
|
345
|
+
if normalize:
|
346
|
+
if G.is_directed():
|
347
|
+
betweenness_list = [x / ((n - 1) * (n - 2)) for x in betweenness_list]
|
348
|
+
else:
|
349
|
+
betweenness_list = [2 * x / ((n - 1) * (n - 2)) for x in betweenness_list]
|
350
|
+
|
351
|
+
return {vv: betweenness_list[i] for i, vv in enumerate(int_to_vertex)}
|
352
|
+
|
353
|
+
|
354
|
+
cdef void _estimate_reachable_vertices_dir(short_digraph g, int* reachL, int* reachU) noexcept:
|
355
|
+
r"""
|
356
|
+
For each vertex ``v``, bounds the number of vertices reachable from ``v``.
|
357
|
+
|
358
|
+
The lower bound is stored in ``reachL[v]``, while the upper bound is stored
|
359
|
+
in ``reachU[v]``. These two arrays must be pre-allocated and they must have
|
360
|
+
size at least ``n``, where ``n`` is the number of nodes of ``g``.
|
361
|
+
|
362
|
+
The estimate works as follows: first, we compute the graph of strongly
|
363
|
+
connected components `\mathcal{G=(V,E)}`, then, for each SCC `C`, we set:
|
364
|
+
|
365
|
+
.. MATH::
|
366
|
+
|
367
|
+
L(C)=|C|+\max_{(C,C') \in \mathcal{E}}L(C') \\
|
368
|
+
U(C)=|C|+\max_{(C,C') \in \mathcal{E}}L(C')
|
369
|
+
|
370
|
+
By analyzing strongly connected components in reverse topological order, we
|
371
|
+
are sure that, as soon as we process component `C`, all components `C'`
|
372
|
+
appearing on the right hand side have already been processed. A further
|
373
|
+
improvement on these bounds is obtained by exactly computing the number of
|
374
|
+
vertices reachable from the biggest strongly connected component, and handle
|
375
|
+
this component separately.
|
376
|
+
|
377
|
+
Then, for each vertex ``v``, we set ``reachL[v]=L(C)``, where `C` is the
|
378
|
+
strongly connected component containing ``v``.
|
379
|
+
|
380
|
+
INPUT:
|
381
|
+
|
382
|
+
- ``g`` -- short_digraph; the input graph;
|
383
|
+
|
384
|
+
OUTPUT:
|
385
|
+
|
386
|
+
``reachL``, ``reachU``: two arrays that should be allocated outside this
|
387
|
+
function and that should have size at least ``g.n``. At the end,
|
388
|
+
``reachL[v]`` (resp., ``reachU[v]``) will contain the lower (resp., upper)
|
389
|
+
bound on the number of reachable vertices from ``v``.
|
390
|
+
"""
|
391
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
392
|
+
cdef int n = g.n
|
393
|
+
cdef int* scc = <int*> mem.malloc(n * sizeof(int))
|
394
|
+
cdef int i, v, w, maxscc = 0
|
395
|
+
cdef int nscc = tarjan_strongly_connected_components_C(g, scc)
|
396
|
+
cdef short_digraph sccgraph
|
397
|
+
strongly_connected_components_digraph_C(g, nscc, scc, sccgraph)
|
398
|
+
|
399
|
+
cdef int* scc_sizes = <int*> mem.calloc(nscc, sizeof(int))
|
400
|
+
cdef int nreach_maxscc = 0
|
401
|
+
cdef short* reach_max_scc = <short*> mem.calloc(nscc, sizeof(short))
|
402
|
+
cdef int* reachL_scc = <int*> mem.calloc(nscc, sizeof(int))
|
403
|
+
cdef uint64_t* reachU_scc = <uint64_t*> mem.calloc(nscc, sizeof(uint64_t))
|
404
|
+
cdef uint64_t* reachU_without_maxscc = <uint64_t*> mem.calloc(nscc, sizeof(uint64_t))
|
405
|
+
# We need uint64_t because these values may become much bigger than g.n,
|
406
|
+
# up to g.n^2, during the computation. Only at the end, we set reachL and
|
407
|
+
# reachU as the maximum between g.n and the computed value (so that they
|
408
|
+
# can be converted to int without overflow).
|
409
|
+
|
410
|
+
# Variables used in BFS from the largest strongly connected component
|
411
|
+
cdef uint32_t startq, endq
|
412
|
+
cdef int* q = <int*> mem.malloc(nscc * sizeof(int))
|
413
|
+
cdef short* reached = <short*> mem.calloc(nscc, sizeof(short))
|
414
|
+
cdef uint32_t* neigh_start
|
415
|
+
cdef uint32_t* neigh_end
|
416
|
+
|
417
|
+
# Compute scc_sizes
|
418
|
+
for i in range(g.n):
|
419
|
+
scc_sizes[scc[i]] += 1
|
420
|
+
|
421
|
+
# Compute maxscc
|
422
|
+
for i in range(nscc):
|
423
|
+
if scc_sizes[maxscc] < scc_sizes[i]:
|
424
|
+
maxscc = i
|
425
|
+
reach_max_scc[maxscc] = 1
|
426
|
+
|
427
|
+
# BFS to compute number of reachable vertices for the biggest SCC.
|
428
|
+
q[0] = maxscc
|
429
|
+
nreach_maxscc = scc_sizes[maxscc]
|
430
|
+
reached[maxscc] = 1
|
431
|
+
startq = 0
|
432
|
+
endq = 1
|
433
|
+
while startq < endq:
|
434
|
+
v = q[startq]
|
435
|
+
startq += 1
|
436
|
+
neigh_start = sccgraph.neighbors[v]
|
437
|
+
neigh_end = sccgraph.neighbors[v + 1]
|
438
|
+
|
439
|
+
while neigh_start < neigh_end:
|
440
|
+
w = neigh_start[0]
|
441
|
+
if not reached[w]:
|
442
|
+
reached[w] = 1
|
443
|
+
nreach_maxscc += scc_sizes[w]
|
444
|
+
q[endq] = w
|
445
|
+
endq += 1
|
446
|
+
neigh_start += 1
|
447
|
+
|
448
|
+
reachL_scc[maxscc] = nreach_maxscc
|
449
|
+
reachU_scc[maxscc] = nreach_maxscc
|
450
|
+
reachU_without_maxscc[maxscc] = 0
|
451
|
+
# Dynamic programming to estimate number of reachable vertices for other
|
452
|
+
# SCCs
|
453
|
+
for i in range(nscc):
|
454
|
+
if i == maxscc:
|
455
|
+
continue
|
456
|
+
|
457
|
+
neigh_start = sccgraph.neighbors[i]
|
458
|
+
neigh_end = sccgraph.neighbors[i + 1]
|
459
|
+
|
460
|
+
while neigh_start < neigh_end:
|
461
|
+
w = neigh_start[0]
|
462
|
+
neigh_start += 1
|
463
|
+
|
464
|
+
reachL_scc[i] = max(reachL_scc[i], reachL_scc[w])
|
465
|
+
reachU_scc[i] += reachU_scc[w]
|
466
|
+
# Note that this might become much bigger than g.n, up to g.n*g.n.
|
467
|
+
# Hence we used uint64_t, and only at the end we take the minimum
|
468
|
+
# between this value and g.n (since g.n is an upper bound on
|
469
|
+
# the number of reachable vertices).
|
470
|
+
if not reached[w]:
|
471
|
+
reachU_without_maxscc[i] += reachU_without_maxscc[w]
|
472
|
+
reach_max_scc[i] = reach_max_scc[i] or reach_max_scc[w]
|
473
|
+
|
474
|
+
if reach_max_scc[i]:
|
475
|
+
reachU_scc[i] = reachU_without_maxscc[i] + nreach_maxscc
|
476
|
+
|
477
|
+
reachL_scc[i] += scc_sizes[i]
|
478
|
+
reachU_scc[i] += scc_sizes[i]
|
479
|
+
if not reached[i]:
|
480
|
+
reachU_without_maxscc[i] += scc_sizes[i]
|
481
|
+
|
482
|
+
for i in range(n):
|
483
|
+
reachL[i] = reachL_scc[scc[i]]
|
484
|
+
reachU[i] = min(<int>reachU_scc[scc[i]], g.n)
|
485
|
+
|
486
|
+
free_short_digraph(sccgraph)
|
487
|
+
|
488
|
+
|
489
|
+
cdef void _compute_reachable_vertices_undir(short_digraph g, int* reachable) noexcept:
|
490
|
+
r"""
|
491
|
+
For each vertex ``v``, compute the number of vertices reachable from ``v``.
|
492
|
+
|
493
|
+
The number of vertices reachable from ``v`` (which is the size of the
|
494
|
+
connected component containing ``v``) is stored in variable
|
495
|
+
``reachable[v]``. The array `reachable` is assumed to be allocated outside
|
496
|
+
this function, and it is assumed to have size at least ``g.n``.
|
497
|
+
"""
|
498
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
499
|
+
cdef int i
|
500
|
+
cdef int n = g.n
|
501
|
+
cdef int* q = <int*> mem.malloc(n * sizeof(int))
|
502
|
+
cdef short* reached = <short*> mem.calloc(n, sizeof(short))
|
503
|
+
|
504
|
+
cdef int v, w
|
505
|
+
cdef uint32_t* neigh_start
|
506
|
+
cdef uint32_t* neigh_end
|
507
|
+
cdef uint32_t startq, endq
|
508
|
+
cdef list currentcc
|
509
|
+
|
510
|
+
memset(reachable, 0, n * sizeof(int))
|
511
|
+
|
512
|
+
for i in range(n):
|
513
|
+
# BFS from i
|
514
|
+
if reachable[i]:
|
515
|
+
continue
|
516
|
+
|
517
|
+
reached[i] = 1
|
518
|
+
currentcc = [i]
|
519
|
+
|
520
|
+
q[0] = i
|
521
|
+
startq = 0
|
522
|
+
endq = 1
|
523
|
+
while startq < endq:
|
524
|
+
v = q[startq]
|
525
|
+
startq += 1
|
526
|
+
neigh_start = g.neighbors[v]
|
527
|
+
neigh_end = g.neighbors[v + 1]
|
528
|
+
|
529
|
+
while neigh_start < neigh_end:
|
530
|
+
w = neigh_start[0]
|
531
|
+
if not reached[w]:
|
532
|
+
reached[w] = 1
|
533
|
+
currentcc.append(w)
|
534
|
+
q[endq] = w
|
535
|
+
endq += 1
|
536
|
+
neigh_start += 1
|
537
|
+
|
538
|
+
for v in currentcc:
|
539
|
+
reachable[v] = len(currentcc)
|
540
|
+
|
541
|
+
|
542
|
+
cdef void _sort_vertices_degree(short_digraph g, int* sorted_verts) noexcept:
|
543
|
+
r"""
|
544
|
+
Sort vertices in decreasing order of degree.
|
545
|
+
|
546
|
+
Uses counting sort, since degrees are between `0` and `n-1`: the running
|
547
|
+
time is then `O(n)`.
|
548
|
+
"""
|
549
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
550
|
+
cdef uint32_t* verts_of_degree = <uint32_t*> mem.calloc(g.n, sizeof(uint32_t))
|
551
|
+
cdef uint32_t* next_vert_of_degree = <uint32_t*> mem.malloc(g.n * sizeof(uint32_t))
|
552
|
+
cdef int d, v
|
553
|
+
|
554
|
+
# Otherwise, segmentation fault
|
555
|
+
if not g.n:
|
556
|
+
return
|
557
|
+
|
558
|
+
for v in range(g.n):
|
559
|
+
verts_of_degree[out_degree(g, v)] += 1
|
560
|
+
|
561
|
+
next_vert_of_degree[g.n - 1] = 0
|
562
|
+
for i in range(g.n - 2, -1, -1):
|
563
|
+
next_vert_of_degree[i] = next_vert_of_degree[i + 1] + verts_of_degree[i + 1]
|
564
|
+
|
565
|
+
for v in range(g.n):
|
566
|
+
d = out_degree(g, v)
|
567
|
+
sorted_verts[next_vert_of_degree[d]] = v
|
568
|
+
next_vert_of_degree[d] += 1
|
569
|
+
|
570
|
+
|
571
|
+
def centrality_closeness_top_k(G, int k=1, int verbose=0):
|
572
|
+
r"""
|
573
|
+
Compute the ``k`` vertices with largest closeness centrality.
|
574
|
+
|
575
|
+
The algorithm is based on performing a breadth-first-search (BFS) from each
|
576
|
+
vertex, and to use bounds in order to cut these BFSes as soon as possible.
|
577
|
+
If ``k`` is small, it is much more efficient than computing all centralities
|
578
|
+
with :meth:`~sage.graphs.generic_graph.GenericGraph.centrality_closeness`.
|
579
|
+
Conversely, if ``k`` is close to the number of nodes, the running-time is
|
580
|
+
approximately the same (it might even be a bit longer, because more
|
581
|
+
computations are needed).
|
582
|
+
|
583
|
+
For more information, see [BCM15]_. The algorithm does not work on
|
584
|
+
weighted graphs.
|
585
|
+
|
586
|
+
INPUT:
|
587
|
+
|
588
|
+
- ``G`` -- a Sage Graph or DiGraph;
|
589
|
+
|
590
|
+
- ``k`` -- integer (default: 1); the algorithm will return the ``k``
|
591
|
+
vertices with largest closeness centrality. This value should be between 1
|
592
|
+
and the number of vertices with positive (out)degree, because the
|
593
|
+
closeness centrality is not defined for vertices with (out)degree 0. If
|
594
|
+
``k`` is bigger than this value, the output will contain all vertices of
|
595
|
+
positive (out)degree.
|
596
|
+
|
597
|
+
- ``verbose`` -- integer (default: 0); define how "verbose" the algorithm
|
598
|
+
should be. If 0, nothing is printed, if 1, we print only the performance
|
599
|
+
ratio at the end of the algorithm, if 2, we print partial results every
|
600
|
+
1000 visits, if 3, we print partial results after every visit.
|
601
|
+
|
602
|
+
OUTPUT:
|
603
|
+
|
604
|
+
An ordered list of ``k`` pairs `(closv, v)`, where `v` is one of the ``k``
|
605
|
+
most central vertices, and `closv` is its closeness centrality. If `k` is
|
606
|
+
bigger than the number of vertices with positive (out)degree, the list might
|
607
|
+
be smaller.
|
608
|
+
|
609
|
+
EXAMPLES::
|
610
|
+
|
611
|
+
sage: from sage.graphs.centrality import centrality_closeness_top_k
|
612
|
+
sage: g = graphs.PathGraph(10)
|
613
|
+
sage: centrality_closeness_top_k(g, 4, 1)
|
614
|
+
Final performance ratio: 0.711111111111...
|
615
|
+
[(0.36, 5),
|
616
|
+
(0.36, 4),
|
617
|
+
(0.3333333333333333, 6),
|
618
|
+
(0.3333333333333333, 3)]
|
619
|
+
sage: g = digraphs.Path(10)
|
620
|
+
sage: centrality_closeness_top_k(g, 5, 1)
|
621
|
+
Final performance ratio: 0.422222222222...
|
622
|
+
[(0.2, 0),
|
623
|
+
(0.19753086419753085, 1),
|
624
|
+
(0.19444444444444442, 2),
|
625
|
+
(0.19047619047619047, 3),
|
626
|
+
(0.18518518518518517, 4)]
|
627
|
+
|
628
|
+
TESTS:
|
629
|
+
|
630
|
+
If ``k`` or ``verbose`` is not an integer::
|
631
|
+
|
632
|
+
sage: from sage.graphs.centrality import centrality_closeness_top_k
|
633
|
+
sage: g = digraphs.Path(10)
|
634
|
+
sage: centrality_closeness_top_k(g, 'abc', 1)
|
635
|
+
Traceback (most recent call last):
|
636
|
+
...
|
637
|
+
TypeError: an integer is required
|
638
|
+
sage: centrality_closeness_top_k(g, 1, 'abc')
|
639
|
+
Traceback (most recent call last):
|
640
|
+
...
|
641
|
+
TypeError: an integer is required
|
642
|
+
|
643
|
+
If ``k`` is bigger than the number of nodes::
|
644
|
+
|
645
|
+
sage: from sage.graphs.centrality import centrality_closeness_top_k
|
646
|
+
sage: g = graphs.PathGraph(5)
|
647
|
+
sage: centrality_closeness_top_k(g, 10, 0)
|
648
|
+
[(0.6666666666666666, 2),
|
649
|
+
(0.5714285714285714, 3),
|
650
|
+
(0.5714285714285714, 1),
|
651
|
+
(0.4, 4),
|
652
|
+
(0.4, 0)]
|
653
|
+
|
654
|
+
Empty graph::
|
655
|
+
|
656
|
+
sage: from sage.graphs.centrality import centrality_closeness_top_k
|
657
|
+
sage: g = Graph()
|
658
|
+
sage: centrality_closeness_top_k(g, 10, 0)
|
659
|
+
[]
|
660
|
+
sage: g = Graph(10)
|
661
|
+
sage: centrality_closeness_top_k(g, 10, 0)
|
662
|
+
[]
|
663
|
+
|
664
|
+
The result is correct::
|
665
|
+
|
666
|
+
sage: # needs networkx
|
667
|
+
sage: from sage.graphs.centrality import centrality_closeness_top_k
|
668
|
+
sage: import random
|
669
|
+
sage: n = 20
|
670
|
+
sage: m = random.randint(1, n * (n - 1) / 2)
|
671
|
+
sage: k = random.randint(1, n)
|
672
|
+
sage: g = graphs.RandomGNM(n, m)
|
673
|
+
sage: topk = centrality_closeness_top_k(g, k)
|
674
|
+
sage: centr = g.centrality_closeness(algorithm='BFS')
|
675
|
+
sage: sorted_centr = sorted(centr.values(), reverse=True)
|
676
|
+
sage: len(topk) == min(k, len(sorted_centr))
|
677
|
+
True
|
678
|
+
sage: all(abs(topk[i][0] - sorted_centr[i]) < 1e-12 for i in range(len(topk)))
|
679
|
+
True
|
680
|
+
|
681
|
+
Directed case::
|
682
|
+
|
683
|
+
sage: # needs networkx
|
684
|
+
sage: from sage.graphs.centrality import centrality_closeness_top_k
|
685
|
+
sage: import random
|
686
|
+
sage: n = 20
|
687
|
+
sage: m = random.randint(1, n * (n - 1))
|
688
|
+
sage: k = random.randint(1, n)
|
689
|
+
sage: g = digraphs.RandomDirectedGNM(n, m)
|
690
|
+
sage: topk = centrality_closeness_top_k(g, k)
|
691
|
+
sage: centr = g.centrality_closeness(algorithm='BFS')
|
692
|
+
sage: sorted_centr = sorted(centr.values(), reverse=True)
|
693
|
+
sage: len(topk) == min(k, len(sorted_centr))
|
694
|
+
True
|
695
|
+
sage: all(abs(topk[i][0] - sorted_centr[i]) < 1e-12 for i in range(len(topk)))
|
696
|
+
True
|
697
|
+
|
698
|
+
Immutable graphs::
|
699
|
+
|
700
|
+
sage: from sage.graphs.centrality import centrality_closeness_top_k
|
701
|
+
sage: G = graphs.RandomGNP(10, .7)
|
702
|
+
sage: G._backend
|
703
|
+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
|
704
|
+
sage: H = Graph(G, immutable=True)
|
705
|
+
sage: H._backend
|
706
|
+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
|
707
|
+
sage: k = randint(1, 10)
|
708
|
+
sage: centrality_closeness_top_k(G, k) == centrality_closeness_top_k(H, k)
|
709
|
+
True
|
710
|
+
"""
|
711
|
+
cdef list res
|
712
|
+
if k >= G.order():
|
713
|
+
closeness_dict = G.centrality_closeness(by_weight=False, algorithm='BFS')
|
714
|
+
res = [(closz, z) for z, closz in closeness_dict.items()]
|
715
|
+
try:
|
716
|
+
res = sorted(res, reverse=True)
|
717
|
+
except TypeError:
|
718
|
+
# A TypeError may occur in Python 3 when vertex labels are of
|
719
|
+
# different types. We then sort on values only.
|
720
|
+
res = sorted(res, reverse=True, key=lambda zz: zz[0])
|
721
|
+
return res
|
722
|
+
|
723
|
+
if G.order() < 2:
|
724
|
+
return []
|
725
|
+
|
726
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
727
|
+
cdef StaticSparseCGraph cg
|
728
|
+
cdef short_digraph sd
|
729
|
+
# Copying the whole graph to obtain the list of neighbors quicker than by
|
730
|
+
# calling out_neighbors. This data structure is well documented in the
|
731
|
+
# module sage.graphs.base.static_sparse_graph
|
732
|
+
cdef list V
|
733
|
+
if isinstance(G, StaticSparseBackend):
|
734
|
+
cg = <StaticSparseCGraph> G._cg
|
735
|
+
sd = <short_digraph> cg.g
|
736
|
+
V = cg._vertex_to_labels
|
737
|
+
else:
|
738
|
+
V = list(G)
|
739
|
+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=V)
|
740
|
+
cdef int n = sd.n
|
741
|
+
cdef int* reachL = <int*> mem.malloc(n * sizeof(int))
|
742
|
+
cdef int* reachU
|
743
|
+
cdef int* pred = <int*> mem.calloc(n, sizeof(int))
|
744
|
+
cdef double *farness = <double*> mem.malloc(n * sizeof(double))
|
745
|
+
cdef int d, nd, x, v
|
746
|
+
cdef long f, gamma
|
747
|
+
cdef int* queue = <int*> mem.malloc(n * sizeof(int))
|
748
|
+
cdef double tildefL, tildefU
|
749
|
+
cdef bint stopped
|
750
|
+
cdef uint32_t* p_tmp
|
751
|
+
cdef int layer_current_beginning, layer_current_end, layer_next_end=0
|
752
|
+
cdef long visited = 0
|
753
|
+
cdef int nvis = 0
|
754
|
+
cdef short* seen = <short*> mem.calloc(n, sizeof(short))
|
755
|
+
cdef bint directed = G.is_directed()
|
756
|
+
|
757
|
+
cdef int* topk = <int*> mem.malloc(k * sizeof(int))
|
758
|
+
for i in range(k):
|
759
|
+
topk[i] = -1
|
760
|
+
for i in range(n):
|
761
|
+
pred[i] = -1
|
762
|
+
|
763
|
+
cdef double kth = n
|
764
|
+
cdef int* sorted_vert = <int*> mem.malloc(n * sizeof(int))
|
765
|
+
if directed:
|
766
|
+
reachU = <int*> mem.malloc(n * sizeof(int))
|
767
|
+
_estimate_reachable_vertices_dir(sd, reachL, reachU)
|
768
|
+
else:
|
769
|
+
_compute_reachable_vertices_undir(sd, reachL)
|
770
|
+
reachU = reachL
|
771
|
+
_sort_vertices_degree(sd, sorted_vert)
|
772
|
+
|
773
|
+
for x in sorted_vert[:n]:
|
774
|
+
sig_check()
|
775
|
+
|
776
|
+
if not out_degree(sd, x):
|
777
|
+
break
|
778
|
+
# We start a BFSCut from x:
|
779
|
+
|
780
|
+
# We reset variable seen:
|
781
|
+
for v in queue[:layer_next_end]:
|
782
|
+
seen[v] = 0
|
783
|
+
pred[v] = -1
|
784
|
+
|
785
|
+
layer_current_beginning = 0
|
786
|
+
layer_current_end = 1
|
787
|
+
layer_next_end = 1
|
788
|
+
d = 0
|
789
|
+
f = 0
|
790
|
+
# We are at level 0, and gamma is the number of arcs exiting level 0
|
791
|
+
# (hence, deg(x)).
|
792
|
+
gamma = out_degree(sd, x)
|
793
|
+
nd = 1
|
794
|
+
queue[0] = x
|
795
|
+
stopped = False
|
796
|
+
seen[x] = 1
|
797
|
+
nvis += 1
|
798
|
+
|
799
|
+
# The graph is explored layer by layer.
|
800
|
+
while layer_current_beginning < layer_current_end and not stopped:
|
801
|
+
sig_check()
|
802
|
+
|
803
|
+
# We update our estimate of the farness of v.
|
804
|
+
# The estimate sets distance d+1 to gamma vertices (which is an
|
805
|
+
# upper bound on the number of vertices at distance d+1 from v),
|
806
|
+
# and distance d+2 to all other vertices reachable from x.
|
807
|
+
tildefL = ((f - gamma + (d + 2) * (<double>(reachL[x] - nd))) * (n - 1)) / ((<double>(reachL[x] - 1)) * (reachL[x] - 1))
|
808
|
+
tildefU = ((f - gamma + (d + 2) * (<double>(reachU[x] - nd))) * (n - 1)) / ((<double>(reachU[x] - 1)) * (reachU[x] - 1))
|
809
|
+
d += 1
|
810
|
+
gamma = 0
|
811
|
+
|
812
|
+
if tildefL >= kth and tildefU >= kth:
|
813
|
+
farness[x] = n
|
814
|
+
stopped = True
|
815
|
+
break
|
816
|
+
# Looking for all non-discovered neighbors of some vertex of the
|
817
|
+
# current layer.
|
818
|
+
for j in range(layer_current_beginning, layer_current_end):
|
819
|
+
sig_check()
|
820
|
+
|
821
|
+
u = queue[j]
|
822
|
+
|
823
|
+
# List the neighbors of u
|
824
|
+
p_tmp = sd.neighbors[u]
|
825
|
+
while p_tmp < sd.neighbors[u + 1] and not stopped:
|
826
|
+
sig_check()
|
827
|
+
|
828
|
+
visited += 1
|
829
|
+
v = p_tmp[0]
|
830
|
+
p_tmp += 1
|
831
|
+
# Is it a new vertex ?
|
832
|
+
if not seen[v]:
|
833
|
+
seen[v] = 1
|
834
|
+
queue[layer_next_end] = v
|
835
|
+
layer_next_end += 1
|
836
|
+
f = f + d
|
837
|
+
gamma += out_degree(sd, v) if directed else (out_degree(sd, v) - 1)
|
838
|
+
nd = nd + 1
|
839
|
+
pred[v] = u
|
840
|
+
elif directed or pred[u] != v:
|
841
|
+
tildefL += (n - 1) / (<double>(reachL[x] - 1) * (reachL[x] - 1))
|
842
|
+
tildefU += (n - 1) / (<double>(reachU[x] - 1) * (reachU[x] - 1))
|
843
|
+
if tildefL >= kth and tildefU >= kth:
|
844
|
+
farness[x] = n
|
845
|
+
stopped = True
|
846
|
+
if stopped:
|
847
|
+
break
|
848
|
+
# 'next_layer' becomes 'current_layer'
|
849
|
+
layer_current_beginning = layer_current_end
|
850
|
+
layer_current_end = layer_next_end
|
851
|
+
|
852
|
+
if not stopped:
|
853
|
+
farness[x] = ((<double> f) * (n - 1)) / (<double>(nd - 1) * (nd - 1))
|
854
|
+
|
855
|
+
if farness[x] < kth:
|
856
|
+
for i in range(k):
|
857
|
+
if topk[i] == -1 or farness[topk[i]] == kth:
|
858
|
+
topk[i] = x
|
859
|
+
break
|
860
|
+
kth = 0
|
861
|
+
for i in range(k):
|
862
|
+
if topk[i] == -1:
|
863
|
+
kth = n
|
864
|
+
break
|
865
|
+
kth = max(kth, farness[topk[i]])
|
866
|
+
if verbose >= 3 or (verbose == 2 and nvis % 1000 == 0):
|
867
|
+
print("Visit {} from {}:".format(nvis, x))
|
868
|
+
print(" Lower bound: {}".format(1 / kth))
|
869
|
+
print(" Perf. ratio: {}".format(visited / (nvis * <double> (sd.neighbors[sd.n] - sd.edges))))
|
870
|
+
|
871
|
+
if verbose > 0:
|
872
|
+
print("Final performance ratio: {}".format(visited / (n * <double> (sd.neighbors[sd.n] - sd.edges))))
|
873
|
+
|
874
|
+
if not isinstance(G, StaticSparseBackend):
|
875
|
+
free_short_digraph(sd)
|
876
|
+
|
877
|
+
res = [(1.0 / farness[v], V[v]) for v in topk[:k] if v != -1]
|
878
|
+
try:
|
879
|
+
res = sorted(res, reverse=True)
|
880
|
+
except TypeError:
|
881
|
+
# A TypeError may occur in Python 3 when vertex labels are of
|
882
|
+
# different types. We then sort on values only.
|
883
|
+
res = sorted(res, reverse=True, key=lambda vv: vv[0])
|
884
|
+
return res
|
885
|
+
|
886
|
+
|
887
|
+
def centrality_closeness_random_k(G, int k=1):
|
888
|
+
r"""
|
889
|
+
Return an estimation of the closeness centrality of `G`.
|
890
|
+
|
891
|
+
The algorithm first randomly selects a set `S` of `k` vertices. Then it
|
892
|
+
computes shortest path distances from each vertex in `S` (using Dijkstra for
|
893
|
+
weighted graph and breadth-first-search (BFS) for unweighted graph) and uses
|
894
|
+
this knowledge to estimate the closeness centrality of all vertices.
|
895
|
+
|
896
|
+
For more information, see [EDI2014]_.
|
897
|
+
|
898
|
+
INPUT:
|
899
|
+
|
900
|
+
- ``G`` -- an undirected connected Graph
|
901
|
+
|
902
|
+
- ``k`` -- integer (default: 1); number of random nodes to choose
|
903
|
+
|
904
|
+
OUTPUT: a dictionary associating to each vertex its estimated closeness centrality
|
905
|
+
|
906
|
+
EXAMPLES:
|
907
|
+
|
908
|
+
Estimation of the closeness centrality of the Petersen Graph when `k == n`::
|
909
|
+
|
910
|
+
sage: from sage.graphs.centrality import centrality_closeness_random_k
|
911
|
+
sage: G = graphs.PetersenGraph()
|
912
|
+
sage: centrality_closeness_random_k(G, 10)
|
913
|
+
{0: 0.6,
|
914
|
+
1: 0.6,
|
915
|
+
2: 0.6,
|
916
|
+
3: 0.6,
|
917
|
+
4: 0.6,
|
918
|
+
5: 0.6,
|
919
|
+
6: 0.6,
|
920
|
+
7: 0.6,
|
921
|
+
8: 0.6,
|
922
|
+
9: 0.6}
|
923
|
+
|
924
|
+
TESTS::
|
925
|
+
|
926
|
+
sage: from sage.graphs.centrality import centrality_closeness_random_k
|
927
|
+
sage: G = Graph('Ihe\\n@GUA')
|
928
|
+
sage: centrality_closeness_random_k(G, -1)
|
929
|
+
Traceback (most recent call last):
|
930
|
+
...
|
931
|
+
ValueError: parameter k must be a positive integer
|
932
|
+
|
933
|
+
sage: centrality_closeness_random_k(DiGraph(2), 1)
|
934
|
+
Traceback (most recent call last):
|
935
|
+
...
|
936
|
+
ValueError: G must be an undirected Graph
|
937
|
+
|
938
|
+
The method is valid for immutable graphs::
|
939
|
+
|
940
|
+
sage: from sage.graphs.centrality import centrality_closeness_random_k
|
941
|
+
sage: G = graphs.RandomGNP(10, .7)
|
942
|
+
sage: G._backend
|
943
|
+
<sage.graphs.base.sparse_graph.SparseGraphBackend ...>
|
944
|
+
sage: H = Graph(G, immutable=True)
|
945
|
+
sage: H._backend
|
946
|
+
<sage.graphs.base.static_sparse_backend.StaticSparseBackend ...>
|
947
|
+
sage: centrality_closeness_random_k(G, 10) == centrality_closeness_random_k(H, 10)
|
948
|
+
True
|
949
|
+
"""
|
950
|
+
G._scream_if_not_simple()
|
951
|
+
if G.is_directed():
|
952
|
+
raise ValueError("G must be an undirected Graph")
|
953
|
+
|
954
|
+
cdef int n = G.order()
|
955
|
+
if not n:
|
956
|
+
return {}
|
957
|
+
if k < 1:
|
958
|
+
raise ValueError("parameter k must be a positive integer")
|
959
|
+
if k > n:
|
960
|
+
k = n
|
961
|
+
|
962
|
+
if not G.is_connected():
|
963
|
+
raise ValueError("G must be a connected Graph")
|
964
|
+
|
965
|
+
# Initialization of some data structures
|
966
|
+
cdef MemoryAllocator mem = MemoryAllocator()
|
967
|
+
cdef double* partial_farness = <double*> mem.malloc(n * sizeof(double))
|
968
|
+
cdef uint32_t* distance
|
969
|
+
cdef uint32_t* waiting_list
|
970
|
+
cdef StaticSparseCGraph cg
|
971
|
+
cdef short_digraph sd
|
972
|
+
cdef bitset_t seen
|
973
|
+
cdef double farness
|
974
|
+
cdef int i, j
|
975
|
+
cdef dict closeness_centrality_array = {}
|
976
|
+
cdef list int_to_vertex
|
977
|
+
cdef dict vertex_to_int
|
978
|
+
if isinstance(G, StaticSparseBackend):
|
979
|
+
cg = <StaticSparseCGraph> G._cg
|
980
|
+
int_to_vertex = cg._vertex_to_labels
|
981
|
+
vertex_to_int = cg._vertex_to_int
|
982
|
+
else:
|
983
|
+
int_to_vertex = list(G)
|
984
|
+
vertex_to_int = {v: i for i, v in enumerate(int_to_vertex)}
|
985
|
+
|
986
|
+
# Initialize
|
987
|
+
for i in range(n):
|
988
|
+
partial_farness[i] = 0
|
989
|
+
|
990
|
+
# Shuffle the vertices
|
991
|
+
cdef list V = list(range(n))
|
992
|
+
random.shuffle(V)
|
993
|
+
|
994
|
+
if G.weighted():
|
995
|
+
# For all random nodes take as a source then run Dijstra and
|
996
|
+
# calculate closeness centrality for k random vertices from l.
|
997
|
+
for i in range(k):
|
998
|
+
farness = 0
|
999
|
+
distances = boost_shortest_paths(G, int_to_vertex[V[i]], algorithm='Dijkstra')[0]
|
1000
|
+
for vertex in distances:
|
1001
|
+
farness += float(distances[vertex])
|
1002
|
+
partial_farness[vertex_to_int[vertex]] += float(distances[vertex])
|
1003
|
+
|
1004
|
+
closeness_centrality_array[int_to_vertex[V[i]]] = (n - 1) / farness
|
1005
|
+
|
1006
|
+
# G is unweighted graph
|
1007
|
+
else:
|
1008
|
+
|
1009
|
+
# Copying the whole graph as a static_sparse_graph for fast shortest
|
1010
|
+
# paths computation in unweighted graph. This data structure is well
|
1011
|
+
# documented in module sage.graphs.base.static_sparse_graph
|
1012
|
+
if isinstance(G, StaticSparseBackend):
|
1013
|
+
sd = <short_digraph> cg.g
|
1014
|
+
else:
|
1015
|
+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
|
1016
|
+
distance = <uint32_t*> mem.malloc(n * sizeof(uint32_t))
|
1017
|
+
waiting_list = <uint32_t*> mem.malloc(n * sizeof(uint32_t))
|
1018
|
+
bitset_init(seen, n)
|
1019
|
+
|
1020
|
+
# Run BFS for random k vertices
|
1021
|
+
for i in range(k):
|
1022
|
+
farness = 0
|
1023
|
+
simple_BFS(sd, V[i], distance, NULL, waiting_list, seen)
|
1024
|
+
for j in range(n):
|
1025
|
+
farness += distance[j]
|
1026
|
+
partial_farness[j] += distance[j]
|
1027
|
+
|
1028
|
+
closeness_centrality_array[int_to_vertex[V[i]]] = (n - 1) / farness
|
1029
|
+
|
1030
|
+
bitset_free(seen)
|
1031
|
+
if not isinstance(G, StaticSparseBackend):
|
1032
|
+
free_short_digraph(sd)
|
1033
|
+
|
1034
|
+
# Estimate the closeness centrality for remaining n-k vertices.
|
1035
|
+
for i in range(k, n):
|
1036
|
+
closeness_centrality_array[int_to_vertex[V[i]]] = k / partial_farness[V[i]]
|
1037
|
+
|
1038
|
+
return closeness_centrality_array
|