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,3045 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-graphs
|
2
|
+
r"""
|
3
|
+
Interface to run Boost algorithms
|
4
|
+
|
5
|
+
Wrapper for a Boost graph. The Boost graphs are Cython C++ variables, and they
|
6
|
+
cannot be converted to Python objects: as a consequence, only functions defined
|
7
|
+
with cdef are able to create, read, modify, and delete these graphs.
|
8
|
+
|
9
|
+
A very important feature of Boost graph library is that all object are generic:
|
10
|
+
for instance, adjacency lists can be stored using different data structures,
|
11
|
+
and (most of) the functions work with all implementations provided. This feature
|
12
|
+
is implemented in our interface using fused types: however, Cython's support for
|
13
|
+
fused types is still experimental, and some features are missing. For instance,
|
14
|
+
there cannot be nested generic function calls, and no variable can have a
|
15
|
+
generic type, apart from the arguments of a generic function.
|
16
|
+
|
17
|
+
All the input functions use pointers, because otherwise we might have problems
|
18
|
+
with ``delete()``.
|
19
|
+
|
20
|
+
**Basic Boost Graph operations:**
|
21
|
+
|
22
|
+
.. csv-table::
|
23
|
+
:class: contentstable
|
24
|
+
:widths: 30, 70
|
25
|
+
:delim: |
|
26
|
+
|
27
|
+
:func:`clustering_coeff` | Return the clustering coefficient of all vertices in the graph.
|
28
|
+
:func:`edge_connectivity` | Return the edge connectivity of the graph.
|
29
|
+
:func:`dominator_tree` | Return a dominator tree of the graph.
|
30
|
+
:func:`bandwidth_heuristics` | Use heuristics to approximate the bandwidth of the graph.
|
31
|
+
:func:`min_spanning_tree` | Compute a minimum spanning tree of a (weighted) graph.
|
32
|
+
:func:`shortest_paths` | Use Dijkstra or Bellman-Ford algorithm to compute the single-source shortest paths.
|
33
|
+
:func:`johnson_shortest_paths` | Use Johnson algorithm to compute the all-pairs shortest paths.
|
34
|
+
:func:`floyd_warshall_shortest_paths` | Use Floyd-Warshall algorithm to compute the all-pairs shortest paths.
|
35
|
+
:func:`johnson_closeness_centrality` | Use Johnson algorithm to compute the closeness centrality of all vertices.
|
36
|
+
:func:`blocks_and_cut_vertices` | Use Tarjan's algorithm to compute the blocks and cut vertices of the graph.
|
37
|
+
:func:`min_cycle_basis` | Return a minimum weight cycle basis of the input graph.
|
38
|
+
|
39
|
+
Functions
|
40
|
+
---------
|
41
|
+
"""
|
42
|
+
|
43
|
+
# ****************************************************************************
|
44
|
+
# Copyright (C) 2015 Michele Borassi michele.borassi@imtlucca.it
|
45
|
+
#
|
46
|
+
# This program is free software: you can redistribute it and/or modify
|
47
|
+
# it under the terms of the GNU General Public License as published by
|
48
|
+
# the Free Software Foundation, either version 2 of the License, or
|
49
|
+
# (at your option) any later version.
|
50
|
+
# http://www.gnu.org/licenses/
|
51
|
+
# ****************************************************************************
|
52
|
+
|
53
|
+
cimport cython
|
54
|
+
from cysignals.signals cimport sig_check, sig_on, sig_off
|
55
|
+
from libcpp.set cimport set as cset
|
56
|
+
from libcpp.pair cimport pair
|
57
|
+
|
58
|
+
|
59
|
+
cdef boost_graph_from_sage_graph(BoostGenGraph *g, g_sage, vertex_to_int, reverse=False):
|
60
|
+
r"""
|
61
|
+
Initialize the Boost graph ``g`` to be equal to ``g_sage``.
|
62
|
+
|
63
|
+
INPUT:
|
64
|
+
|
65
|
+
- ``g`` -- a Boost graph; it must represent an empty graph (an exception is
|
66
|
+
raised otherwise)
|
67
|
+
|
68
|
+
- ``g_sage`` -- a Sage graph
|
69
|
+
|
70
|
+
- ``vertex_to_int`` -- dictionary; it is a mapping from the vertex set of
|
71
|
+
``g_sage`` to `(0, \ldots, n-1)`
|
72
|
+
|
73
|
+
- ``reverse`` -- boolean (default: ``False``); when set to ``True``, the
|
74
|
+
Boost graph is initialized with reversed edges
|
75
|
+
"""
|
76
|
+
from sage.graphs.generic_graph import GenericGraph
|
77
|
+
|
78
|
+
if not isinstance(g_sage, GenericGraph):
|
79
|
+
raise TypeError("the input must be a Sage graph")
|
80
|
+
|
81
|
+
if g.num_verts():
|
82
|
+
raise AssertionError("the given Boost graph must be empty")
|
83
|
+
|
84
|
+
cdef int N = g_sage.num_verts()
|
85
|
+
cdef int i
|
86
|
+
|
87
|
+
for i in range(N):
|
88
|
+
g.add_vertex()
|
89
|
+
|
90
|
+
if reverse:
|
91
|
+
for u, v in g_sage.edge_iterator(labels=None):
|
92
|
+
g.add_edge(vertex_to_int[v], vertex_to_int[u])
|
93
|
+
else:
|
94
|
+
for u, v in g_sage.edge_iterator(labels=None):
|
95
|
+
g.add_edge(vertex_to_int[u], vertex_to_int[v])
|
96
|
+
|
97
|
+
|
98
|
+
cdef boost_weighted_graph_from_sage_graph(BoostWeightedGraph *g,
|
99
|
+
g_sage,
|
100
|
+
vertex_to_int,
|
101
|
+
weight_function=None,
|
102
|
+
reverse=False):
|
103
|
+
r"""
|
104
|
+
Initialize the Boost weighted graph ``g`` to be equal to ``g_sage``.
|
105
|
+
|
106
|
+
INPUT:
|
107
|
+
|
108
|
+
- ``g`` -- a Boost weighted graph; it must represent an empty weighted graph
|
109
|
+
(an exception is raised otherwise)
|
110
|
+
|
111
|
+
- ``g_sage`` -- a Sage graph
|
112
|
+
|
113
|
+
- ``vertex_to_int`` -- dictionary; it is a mapping from the vertex set of
|
114
|
+
``g_sage`` to `(0, \ldots, n-1)`
|
115
|
+
|
116
|
+
- ``weight_function`` -- function (default: ``None``); a function which
|
117
|
+
inputs an edge ``e`` and outputs a number. The edge weights are chosen as
|
118
|
+
follows, and they must be convertible to floats, otherwise an error is
|
119
|
+
raised.
|
120
|
+
|
121
|
+
- If ``weight_function`` is not ``None``, this function is used
|
122
|
+
|
123
|
+
- If ``weight_function`` is ``None`` and ``g`` is weighted, the edge
|
124
|
+
labels of ``g`` are used; in other words, the weight of an edge
|
125
|
+
``e = (u, v, l)`` is ``l``
|
126
|
+
|
127
|
+
- Otherwise, all weights are set to 1
|
128
|
+
|
129
|
+
- ``reverse`` -- boolean (default: ``False``); when set to ``True``, the
|
130
|
+
Boost graph is initialized with reversed edges
|
131
|
+
"""
|
132
|
+
from sage.graphs.generic_graph import GenericGraph
|
133
|
+
|
134
|
+
if not isinstance(g_sage, GenericGraph):
|
135
|
+
raise TypeError("the input must be a Sage graph")
|
136
|
+
|
137
|
+
if g.num_verts():
|
138
|
+
raise AssertionError("the given Boost graph must be empty")
|
139
|
+
|
140
|
+
cdef int N = g_sage.num_verts()
|
141
|
+
cdef int i
|
142
|
+
|
143
|
+
for i in range(N):
|
144
|
+
g.add_vertex()
|
145
|
+
|
146
|
+
if weight_function is not None:
|
147
|
+
if reverse:
|
148
|
+
for e in g_sage.edge_iterator():
|
149
|
+
g.add_edge(vertex_to_int[e[1]],
|
150
|
+
vertex_to_int[e[0]],
|
151
|
+
float(weight_function(e)))
|
152
|
+
else:
|
153
|
+
for e in g_sage.edge_iterator():
|
154
|
+
g.add_edge(vertex_to_int[e[0]],
|
155
|
+
vertex_to_int[e[1]],
|
156
|
+
float(weight_function(e)))
|
157
|
+
elif g_sage.weighted():
|
158
|
+
if reverse:
|
159
|
+
for u, v, w in g_sage.edge_iterator():
|
160
|
+
g.add_edge(vertex_to_int[v], vertex_to_int[u], float(w))
|
161
|
+
else:
|
162
|
+
for u, v, w in g_sage.edge_iterator():
|
163
|
+
g.add_edge(vertex_to_int[u], vertex_to_int[v], float(w))
|
164
|
+
else:
|
165
|
+
if reverse:
|
166
|
+
for u, v in g_sage.edge_iterator(labels=False):
|
167
|
+
g.add_edge(vertex_to_int[v], vertex_to_int[u], 1)
|
168
|
+
else:
|
169
|
+
for u, v in g_sage.edge_iterator(labels=False):
|
170
|
+
g.add_edge(vertex_to_int[u], vertex_to_int[v], 1)
|
171
|
+
|
172
|
+
|
173
|
+
cdef boost_edge_connectivity(BoostVecGenGraph *g):
|
174
|
+
r"""
|
175
|
+
Compute the edge connectivity of the input Boost graph.
|
176
|
+
|
177
|
+
The output is a pair ``[ec,edges]``, where ``ec`` is the edge connectivity,
|
178
|
+
``edges`` is the list of edges in a minimum cut.
|
179
|
+
"""
|
180
|
+
cdef result_ec result
|
181
|
+
|
182
|
+
sig_on()
|
183
|
+
result = g[0].edge_connectivity()
|
184
|
+
sig_off()
|
185
|
+
|
186
|
+
cdef size_t i
|
187
|
+
edges = [(result.edges[i], result.edges[i+1])
|
188
|
+
for i in range(0, result.edges.size(), 2)]
|
189
|
+
|
190
|
+
return (result.ec, edges)
|
191
|
+
|
192
|
+
|
193
|
+
cpdef edge_connectivity(g):
|
194
|
+
r"""
|
195
|
+
Compute the edge connectivity of the input graph, using Boost.
|
196
|
+
|
197
|
+
OUTPUT: a pair ``(ec, edges)``, where ``ec`` is the edge
|
198
|
+
connectivity, ``edges`` is the list of edges in a minimum cut.
|
199
|
+
|
200
|
+
.. SEEALSO::
|
201
|
+
|
202
|
+
:meth:`sage.graphs.generic_graph.GenericGraph.edge_connectivity`
|
203
|
+
|
204
|
+
EXAMPLES:
|
205
|
+
|
206
|
+
Computing the edge connectivity of a clique::
|
207
|
+
|
208
|
+
sage: from sage.graphs.base.boost_graph import edge_connectivity
|
209
|
+
sage: g = graphs.CompleteGraph(5)
|
210
|
+
sage: edge_connectivity(g)
|
211
|
+
(4, [(0, 1), (0, 2), (0, 3), (0, 4)])
|
212
|
+
|
213
|
+
Vertex-labeled graphs::
|
214
|
+
|
215
|
+
sage: from sage.graphs.base.boost_graph import edge_connectivity
|
216
|
+
sage: g = graphs.GridGraph([2,2])
|
217
|
+
sage: edge_connectivity(g)
|
218
|
+
(2, [((0, 0), (0, 1)), ((0, 0), (1, 0))])
|
219
|
+
"""
|
220
|
+
from sage.graphs.graph import Graph
|
221
|
+
from sage.graphs.digraph import DiGraph
|
222
|
+
|
223
|
+
# These variables are automatically deleted when the function terminates.
|
224
|
+
cdef BoostVecGraph g_boost_und
|
225
|
+
cdef BoostVecDiGraph g_boost_dir
|
226
|
+
cdef v_index i
|
227
|
+
cdef list int_to_vertex = list(g)
|
228
|
+
cdef dict vertex_to_int = {v: i for i, v in enumerate(int_to_vertex)}
|
229
|
+
|
230
|
+
if isinstance(g, Graph):
|
231
|
+
boost_graph_from_sage_graph(&g_boost_und, g, vertex_to_int)
|
232
|
+
ec, edges = boost_edge_connectivity(&g_boost_und)
|
233
|
+
|
234
|
+
elif isinstance(g, DiGraph):
|
235
|
+
from sage.misc.stopgap import stopgap
|
236
|
+
stopgap("The edge connectivity of directed graphs is not implemented "
|
237
|
+
"in Boost. The result may be mathematically unreliable.", 18753)
|
238
|
+
|
239
|
+
boost_graph_from_sage_graph(&g_boost_dir, g, vertex_to_int)
|
240
|
+
ec, edges = boost_edge_connectivity(&g_boost_dir)
|
241
|
+
|
242
|
+
else:
|
243
|
+
raise TypeError("the input must be a Sage graph")
|
244
|
+
|
245
|
+
return (ec, [(int_to_vertex[u], int_to_vertex[v]) for u, v in edges])
|
246
|
+
|
247
|
+
|
248
|
+
cdef boost_clustering_coeff(BoostGenGraph *g, vertices):
|
249
|
+
r"""
|
250
|
+
Compute the clustering coefficient of all vertices in the list provided.
|
251
|
+
|
252
|
+
The output is a pair ``[average_clustering_coefficient, clust_of_v]``, where
|
253
|
+
``average_clustering_coefficient`` is the average clustering of the vertices
|
254
|
+
in variable ``vertices``, ``clust_of_v`` is a dictionary that associates to
|
255
|
+
each vertex (stored as an integer) its clustering coefficient.
|
256
|
+
"""
|
257
|
+
cdef result_cc result
|
258
|
+
cdef double result_d
|
259
|
+
cdef v_index vi
|
260
|
+
cdef dict clust_of_v
|
261
|
+
|
262
|
+
if len(vertices) == g.num_verts():
|
263
|
+
sig_on()
|
264
|
+
result = g[0].clustering_coeff_all()
|
265
|
+
sig_off()
|
266
|
+
clust_of_v = {v: result.clust_of_v[v] for v in range(g.num_verts())}
|
267
|
+
return (result.average_clustering_coefficient, clust_of_v)
|
268
|
+
|
269
|
+
else:
|
270
|
+
clust_of_v = {}
|
271
|
+
for v in vertices:
|
272
|
+
vi = v
|
273
|
+
sig_on()
|
274
|
+
result_d = g[0].clustering_coeff(vi)
|
275
|
+
sig_off()
|
276
|
+
clust_of_v[v] = result_d
|
277
|
+
return ((sum(clust_of_v.itervalues()) / len(clust_of_v)), clust_of_v)
|
278
|
+
|
279
|
+
|
280
|
+
cpdef clustering_coeff(g, vertices=None):
|
281
|
+
r"""
|
282
|
+
Compute the clustering coefficient of the input graph, using Boost.
|
283
|
+
|
284
|
+
.. SEEALSO::
|
285
|
+
|
286
|
+
:meth:`sage.graphs.generic_graph.GenericGraph.clustering_coeff`
|
287
|
+
|
288
|
+
INPUT:
|
289
|
+
|
290
|
+
- ``g`` -- the input Sage Graph
|
291
|
+
|
292
|
+
- ``vertices`` -- list (default: ``None``); the list of vertices to analyze
|
293
|
+
(if ``None``, compute the clustering coefficient of all vertices)
|
294
|
+
|
295
|
+
OUTPUT: a pair ``(average_clustering_coefficient, clust_of_v)``, where
|
296
|
+
``average_clustering_coefficient`` is the average clustering of the vertices
|
297
|
+
in variable ``vertices``, ``clust_of_v`` is a dictionary that associates to
|
298
|
+
each vertex its clustering coefficient. If ``vertices`` is ``None``, all
|
299
|
+
vertices are considered.
|
300
|
+
|
301
|
+
EXAMPLES:
|
302
|
+
|
303
|
+
Computing the clustering coefficient of a clique::
|
304
|
+
|
305
|
+
sage: from sage.graphs.base.boost_graph import clustering_coeff
|
306
|
+
sage: g = graphs.CompleteGraph(5)
|
307
|
+
sage: clustering_coeff(g)
|
308
|
+
(1.0, {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0})
|
309
|
+
sage: clustering_coeff(g, vertices = [0,1,2])
|
310
|
+
(1.0, {0: 1.0, 1: 1.0, 2: 1.0})
|
311
|
+
|
312
|
+
Of a non-clique graph with triangles::
|
313
|
+
|
314
|
+
sage: g = graphs.IcosahedralGraph()
|
315
|
+
sage: clustering_coeff(g, vertices=[1,2,3])
|
316
|
+
(0.5, {1: 0.5, 2: 0.5, 3: 0.5})
|
317
|
+
|
318
|
+
With labels::
|
319
|
+
|
320
|
+
sage: g.relabel(list("abcdefghiklm"))
|
321
|
+
sage: clustering_coeff(g, vertices='abde')
|
322
|
+
(0.5, {'a': 0.5, 'b': 0.5, 'd': 0.5, 'e': 0.5})
|
323
|
+
"""
|
324
|
+
from sage.graphs.graph import Graph
|
325
|
+
|
326
|
+
# These variables are automatically deleted when the function terminates.
|
327
|
+
cdef BoostVecGraph g_boost
|
328
|
+
cdef v_index i
|
329
|
+
cdef list g_vertices = list(g)
|
330
|
+
cdef dict vertex_to_int = {v: i for i, v in enumerate(g_vertices)}
|
331
|
+
|
332
|
+
if not isinstance(g, Graph):
|
333
|
+
raise TypeError("the input must be a Sage Graph")
|
334
|
+
|
335
|
+
boost_graph_from_sage_graph(&g_boost, g, vertex_to_int)
|
336
|
+
|
337
|
+
if vertices is None:
|
338
|
+
vertices = g_vertices
|
339
|
+
|
340
|
+
cdef list vertices_boost = [vertex_to_int[v] for v in vertices]
|
341
|
+
average_clustering, clust_v_int = boost_clustering_coeff(&g_boost, vertices_boost)
|
342
|
+
cdef dict clust_v_sage = {g_vertices[v]: clust_v_int[v] for v in vertices_boost}
|
343
|
+
return (average_clustering, clust_v_sage)
|
344
|
+
|
345
|
+
|
346
|
+
@cython.binding(True)
|
347
|
+
cpdef dominator_tree(g, root, return_dict=False, reverse=False):
|
348
|
+
r"""
|
349
|
+
Use Boost to compute the dominator tree of ``g``, rooted at ``root``.
|
350
|
+
|
351
|
+
A node `d` dominates a node `n` if every path from the entry node
|
352
|
+
``root`` to `n` must go through `d`. The immediate dominator of a node
|
353
|
+
`n` is the unique node that strictly dominates `n` but does not dominate
|
354
|
+
any other node that dominates `n`. A dominator tree is a tree where each
|
355
|
+
node's children are those nodes it immediately dominates. For more
|
356
|
+
information, see the :wikipedia:`Dominator_(graph_theory)`.
|
357
|
+
|
358
|
+
If the graph is connected and undirected, the parent of a vertex `v` is:
|
359
|
+
|
360
|
+
- the root if `v` is in the same biconnected component as the root;
|
361
|
+
|
362
|
+
- the first cut vertex in a path from `v` to the root, otherwise.
|
363
|
+
|
364
|
+
If the graph is not connected, the dominator tree of the whole graph is
|
365
|
+
equal to the dominator tree of the connected component of the root.
|
366
|
+
|
367
|
+
If the graph is directed, computing a dominator tree is more complicated,
|
368
|
+
and it needs time `O(m\log m)`, where `m` is the number of edges. The
|
369
|
+
implementation provided by Boost is the most general one, so it needs time
|
370
|
+
`O(m\log m)` even for undirected graphs.
|
371
|
+
|
372
|
+
INPUT:
|
373
|
+
|
374
|
+
- ``g`` -- the input Sage (Di)Graph
|
375
|
+
|
376
|
+
- ``root`` -- the root of the dominator tree
|
377
|
+
|
378
|
+
- ``return_dict`` -- boolean (default: ``False``); if ``True``, the function
|
379
|
+
returns a dictionary associating to each vertex its parent in the
|
380
|
+
dominator tree. If ``False`` (default), it returns the whole tree, as a
|
381
|
+
``Graph`` or a ``DiGraph``.
|
382
|
+
|
383
|
+
- ``reverse`` -- boolean (default: ``False``); when set to ``True``,
|
384
|
+
computes the dominator tree in the reverse graph
|
385
|
+
|
386
|
+
OUTPUT:
|
387
|
+
|
388
|
+
The dominator tree, as a graph or as a dictionary, depending on the
|
389
|
+
value of ``return_dict``. If the output is a dictionary, it will contain
|
390
|
+
``None`` in correspondence of ``root`` and of vertices that are not
|
391
|
+
reachable from ``root``. If the output is a graph, it will not contain
|
392
|
+
vertices that are not reachable from ``root``.
|
393
|
+
|
394
|
+
EXAMPLES:
|
395
|
+
|
396
|
+
An undirected grid is biconnected, and its dominator tree is a star
|
397
|
+
(everyone's parent is the root)::
|
398
|
+
|
399
|
+
sage: g = graphs.GridGraph([2,2]).dominator_tree((0,0))
|
400
|
+
sage: g.to_dictionary()
|
401
|
+
{(0, 0): [(0, 1), (1, 0), (1, 1)], (0, 1): [(0, 0)], (1, 0): [(0, 0)], (1, 1): [(0, 0)]}
|
402
|
+
|
403
|
+
If the graph is made by two 3-cycles `C_1,C_2` connected by an edge `(v,w)`,
|
404
|
+
with `v \in C_1`, `w \in C_2`, the cut vertices are `v` and `w`, the
|
405
|
+
biconnected components are `C_1`, `C_2`, and the edge `(v,w)`. If the root
|
406
|
+
is in `C_1`, the parent of each vertex in `C_1` is the root, the parent of
|
407
|
+
`w` is `v`, and the parent of each vertex in `C_2` is `w`::
|
408
|
+
|
409
|
+
sage: G = 2 * graphs.CycleGraph(3)
|
410
|
+
sage: v = 0
|
411
|
+
sage: w = 3
|
412
|
+
sage: G.add_edge(v,w)
|
413
|
+
sage: G.dominator_tree(1, return_dict=True)
|
414
|
+
{0: 1, 1: None, 2: 1, 3: 0, 4: 3, 5: 3}
|
415
|
+
|
416
|
+
An example with a directed graph::
|
417
|
+
|
418
|
+
sage: g = digraphs.Circuit(10).dominator_tree(5)
|
419
|
+
sage: g.to_dictionary()
|
420
|
+
{0: [1], 1: [2], 2: [3], 3: [4], 4: [], 5: [6], 6: [7], 7: [8], 8: [9], 9: [0]}
|
421
|
+
sage: g = digraphs.Circuit(10).dominator_tree(5, reverse=True)
|
422
|
+
sage: g.to_dictionary()
|
423
|
+
{0: [9], 1: [0], 2: [1], 3: [2], 4: [3], 5: [4], 6: [], 7: [6], 8: [7], 9: [8]}
|
424
|
+
|
425
|
+
If the output is a dictionary::
|
426
|
+
|
427
|
+
sage: graphs.GridGraph([2,2]).dominator_tree((0,0), return_dict=True)
|
428
|
+
{(0, 0): None, (0, 1): (0, 0), (1, 0): (0, 0), (1, 1): (0, 0)}
|
429
|
+
|
430
|
+
TESTS:
|
431
|
+
|
432
|
+
If ``g`` is not a graph, an error is raised::
|
433
|
+
|
434
|
+
sage: from sage.graphs.base.boost_graph import dominator_tree
|
435
|
+
sage: dominator_tree('I am not a graph', 0)
|
436
|
+
Traceback (most recent call last):
|
437
|
+
...
|
438
|
+
TypeError: the input must be a Sage Graph or DiGraph
|
439
|
+
|
440
|
+
If ``root`` is not a vertex, an error is raised::
|
441
|
+
|
442
|
+
sage: digraphs.TransitiveTournament(10).dominator_tree('Not a vertex!')
|
443
|
+
Traceback (most recent call last):
|
444
|
+
...
|
445
|
+
ValueError: the input root must be a vertex of the given graph
|
446
|
+
sage: graphs.GridGraph([2,2]).dominator_tree(0)
|
447
|
+
Traceback (most recent call last):
|
448
|
+
...
|
449
|
+
ValueError: the input root must be a vertex of the given graph
|
450
|
+
"""
|
451
|
+
from sage.graphs.graph import Graph
|
452
|
+
from sage.graphs.digraph import DiGraph
|
453
|
+
|
454
|
+
if not isinstance(g, (Graph, DiGraph)):
|
455
|
+
raise TypeError("the input must be a Sage Graph or DiGraph")
|
456
|
+
if root not in g:
|
457
|
+
raise ValueError("the input root must be a vertex of the given graph")
|
458
|
+
|
459
|
+
# These variables are automatically deleted when the function terminates.
|
460
|
+
cdef BoostVecGraph g_boost_und
|
461
|
+
cdef BoostVecDiGraph g_boost_dir
|
462
|
+
cdef vector[v_index] result
|
463
|
+
cdef v_index i
|
464
|
+
cdef list int_to_vertex = list(g)
|
465
|
+
cdef dict vertex_to_int = {v: i for i, v in enumerate(int_to_vertex)}
|
466
|
+
|
467
|
+
if isinstance(g, Graph):
|
468
|
+
boost_graph_from_sage_graph(&g_boost_und, g, vertex_to_int, reverse)
|
469
|
+
i = vertex_to_int[root]
|
470
|
+
sig_on()
|
471
|
+
result = g_boost_und.dominator_tree(i)
|
472
|
+
sig_off()
|
473
|
+
|
474
|
+
elif isinstance(g, DiGraph):
|
475
|
+
boost_graph_from_sage_graph(&g_boost_dir, g, vertex_to_int, reverse)
|
476
|
+
i = vertex_to_int[root]
|
477
|
+
sig_on()
|
478
|
+
result = g_boost_dir.dominator_tree(i)
|
479
|
+
sig_off()
|
480
|
+
|
481
|
+
cdef v_index no_parent = -1
|
482
|
+
|
483
|
+
if return_dict:
|
484
|
+
return {v: (None if result[i] == no_parent else int_to_vertex[<int> result[i]]) for i, v in enumerate(int_to_vertex)}
|
485
|
+
|
486
|
+
cdef list edges = [[int_to_vertex[<int> result[i]], v] for i, v in enumerate(int_to_vertex) if result[i] != no_parent]
|
487
|
+
|
488
|
+
if g.is_directed():
|
489
|
+
if not edges:
|
490
|
+
g = DiGraph()
|
491
|
+
g.add_vertex(root)
|
492
|
+
return g
|
493
|
+
else:
|
494
|
+
return DiGraph(edges)
|
495
|
+
else:
|
496
|
+
if not edges:
|
497
|
+
g = Graph()
|
498
|
+
g.add_vertex(root)
|
499
|
+
return g
|
500
|
+
else:
|
501
|
+
return Graph(edges)
|
502
|
+
|
503
|
+
|
504
|
+
cpdef bandwidth_heuristics(g, algorithm='cuthill_mckee'):
|
505
|
+
r"""
|
506
|
+
Use Boost heuristics to approximate the bandwidth of the input graph.
|
507
|
+
|
508
|
+
The bandwidth `bw(M)` of a matrix `M` is the smallest integer `k` such that
|
509
|
+
all nonzero entries of `M` are at distance `k` from the diagonal. The
|
510
|
+
bandwidth `bw(g)` of an undirected graph `g` is the minimum bandwidth of
|
511
|
+
the adjacency matrix of `g`, over all possible relabellings of its vertices
|
512
|
+
(for more information, see the
|
513
|
+
:mod:`~sage.graphs.graph_decompositions.bandwidth`
|
514
|
+
module).
|
515
|
+
|
516
|
+
Unfortunately, exactly computing the bandwidth is NP-hard (and an
|
517
|
+
exponential algorithm is implemented in Sagemath in routine
|
518
|
+
:func:`~sage.graphs.graph_decompositions.bandwidth.bandwidth`). Here, we
|
519
|
+
implement two heuristics to find good orderings: Cuthill-McKee, reverse
|
520
|
+
Cuthill-McKee (also known as ``RCM``) and King.
|
521
|
+
|
522
|
+
This function works only in undirected graphs, and its running time is
|
523
|
+
`O(md_{max}\log d_{max})` for the Cuthill-McKee ordering, and
|
524
|
+
`O(md_{max}^2\log d_{max})` for the King ordering, where `m` is the number
|
525
|
+
of edges, and `d_{max}` is the maximum degree in the graph.
|
526
|
+
|
527
|
+
INPUT:
|
528
|
+
|
529
|
+
- ``g`` -- the input Sage graph
|
530
|
+
|
531
|
+
- ``algorithm`` -- string (default: ``'cuthill_mckee'``); the heuristic used
|
532
|
+
to compute the ordering among ``'cuthill_mckee'``,
|
533
|
+
``'reverse_cuthill_mckee'`` and ``'king'``
|
534
|
+
|
535
|
+
OUTPUT:
|
536
|
+
|
537
|
+
A pair ``[bandwidth, ordering]``, where ``ordering`` is the ordering of
|
538
|
+
vertices, ``bandwidth`` is the bandwidth of that specific ordering (which
|
539
|
+
is not necessarily the bandwidth of the graph, because this is a heuristic).
|
540
|
+
|
541
|
+
EXAMPLES::
|
542
|
+
|
543
|
+
sage: from sage.graphs.base.boost_graph import bandwidth_heuristics
|
544
|
+
sage: bandwidth_heuristics(graphs.PathGraph(10))
|
545
|
+
(1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
|
546
|
+
sage: bandwidth_heuristics(graphs.GridGraph([3,3]))
|
547
|
+
(3, [(0, 0), (1, 0), (0, 1), (2, 0), (1, 1), (0, 2), (2, 1), (1, 2), (2, 2)])
|
548
|
+
sage: bandwidth_heuristics(graphs.GridGraph([3,3]), algorithm='reverse_cuthill_mckee')
|
549
|
+
(3, [(2, 2), (1, 2), (2, 1), (0, 2), (1, 1), (2, 0), (0, 1), (1, 0), (0, 0)])
|
550
|
+
sage: bandwidth_heuristics(graphs.GridGraph([3,3]), algorithm='king')
|
551
|
+
(3, [(0, 0), (1, 0), (0, 1), (2, 0), (1, 1), (0, 2), (2, 1), (1, 2), (2, 2)])
|
552
|
+
|
553
|
+
TESTS:
|
554
|
+
|
555
|
+
Given an input which is not a graph::
|
556
|
+
|
557
|
+
sage: from sage.graphs.base.boost_graph import bandwidth_heuristics
|
558
|
+
sage: bandwidth_heuristics(digraphs.Path(10))
|
559
|
+
Traceback (most recent call last):
|
560
|
+
...
|
561
|
+
TypeError: the input must be a Sage Graph
|
562
|
+
sage: bandwidth_heuristics("I am not a graph!")
|
563
|
+
Traceback (most recent call last):
|
564
|
+
...
|
565
|
+
TypeError: the input must be a Sage Graph
|
566
|
+
|
567
|
+
Given a wrong algorithm::
|
568
|
+
|
569
|
+
sage: from sage.graphs.base.boost_graph import bandwidth_heuristics
|
570
|
+
sage: bandwidth_heuristics(graphs.PathGraph(3), algorithm='tip top')
|
571
|
+
Traceback (most recent call last):
|
572
|
+
...
|
573
|
+
ValueError: unknown algorithm 'tip top'
|
574
|
+
|
575
|
+
Given a graph with no edges::
|
576
|
+
|
577
|
+
sage: from sage.graphs.base.boost_graph import bandwidth_heuristics
|
578
|
+
sage: bandwidth_heuristics(Graph())
|
579
|
+
(0, [])
|
580
|
+
sage: bandwidth_heuristics(graphs.RandomGNM(10,0)) # needs networkx
|
581
|
+
(0, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
|
582
|
+
"""
|
583
|
+
from sage.graphs.graph import Graph
|
584
|
+
|
585
|
+
# Tests for errors and trivial cases
|
586
|
+
if not isinstance(g, Graph):
|
587
|
+
raise TypeError("the input must be a Sage Graph")
|
588
|
+
if algorithm not in ['cuthill_mckee', 'reverse_cuthill_mckee', 'king']:
|
589
|
+
raise ValueError(f"unknown algorithm {algorithm!r}")
|
590
|
+
if not g.num_edges():
|
591
|
+
return (0, list(g))
|
592
|
+
|
593
|
+
cdef bint reverse = False
|
594
|
+
if algorithm == 'reverse_cuthill_mckee':
|
595
|
+
reverse = True
|
596
|
+
algorithm = 'cuthill_mckee'
|
597
|
+
|
598
|
+
# These variables are automatically deleted when the function terminates.
|
599
|
+
cdef BoostVecGraph g_boost
|
600
|
+
cdef vector[v_index] result
|
601
|
+
cdef v_index i
|
602
|
+
cdef list int_to_vertex = list(g)
|
603
|
+
cdef dict vertex_to_int = {v: i for i, v in enumerate(int_to_vertex)}
|
604
|
+
|
605
|
+
boost_graph_from_sage_graph(&g_boost, g, vertex_to_int)
|
606
|
+
cdef bint use_cuthill_mckee = (algorithm == 'cuthill_mckee')
|
607
|
+
sig_on()
|
608
|
+
result = g_boost.bandwidth_ordering(use_cuthill_mckee)
|
609
|
+
sig_off()
|
610
|
+
|
611
|
+
cdef int n = g.num_verts()
|
612
|
+
cdef dict pos = {int_to_vertex[<int> result[i]]: i for i in range(n)}
|
613
|
+
cdef int bandwidth = max([abs(pos[u] - pos[v])
|
614
|
+
for u, v in g.edge_iterator(labels=False)])
|
615
|
+
|
616
|
+
if reverse:
|
617
|
+
return (bandwidth, [int_to_vertex[<int> result[i]]
|
618
|
+
for i in range(n - 1, -1, -1)])
|
619
|
+
return (bandwidth, [int_to_vertex[<int> result[i]] for i in range(n)])
|
620
|
+
|
621
|
+
|
622
|
+
cpdef min_spanning_tree(g,
|
623
|
+
weight_function=None,
|
624
|
+
algorithm='Kruskal'):
|
625
|
+
r"""
|
626
|
+
Use Boost to compute the minimum spanning tree of the input graph.
|
627
|
+
|
628
|
+
INPUT:
|
629
|
+
|
630
|
+
- ``g`` -- the input Sage graph
|
631
|
+
|
632
|
+
- ``weight_function`` -- function (default: ``None``); a function that
|
633
|
+
inputs an edge ``e`` and outputs its weight. An edge has the form
|
634
|
+
``(u,v,l)``, where ``u`` and ``v`` are vertices, ``l`` is a label (that
|
635
|
+
can be of any kind). The ``weight_function`` can be used to transform the
|
636
|
+
label into a weight (see the example below). In particular:
|
637
|
+
|
638
|
+
- if ``weight_function`` is not ``None``, the weight of an edge ``e`` is
|
639
|
+
``weight_function(e)``;
|
640
|
+
|
641
|
+
- if ``weight_function`` is ``None`` (default) and ``g`` is weighted (that
|
642
|
+
is, ``g.weighted()==True``), for each edge ``e=(u,v,l)``, we set weight
|
643
|
+
``l``;
|
644
|
+
|
645
|
+
- if ``weight_function`` is ``None`` and ``g`` is not weighted, we set all
|
646
|
+
weights to 1 (hence, the output can be any spanning tree).
|
647
|
+
|
648
|
+
Note that, if the weight is not convertible to a number with function
|
649
|
+
``float()``, an error is raised (see tests below).
|
650
|
+
|
651
|
+
- ``algorithm`` -- string (default: ``'Kruskal'``); the algorithm to use
|
652
|
+
among ``'Kruskal'`` and ``'Prim'``
|
653
|
+
|
654
|
+
OUTPUT:
|
655
|
+
|
656
|
+
The edges of a minimum spanning tree of ``g``, if one exists, otherwise
|
657
|
+
the empty list.
|
658
|
+
|
659
|
+
.. SEEALSO::
|
660
|
+
|
661
|
+
- :meth:`sage.graphs.generic_graph.GenericGraph.min_spanning_tree`
|
662
|
+
|
663
|
+
EXAMPLES::
|
664
|
+
|
665
|
+
sage: from sage.graphs.base.boost_graph import min_spanning_tree
|
666
|
+
sage: min_spanning_tree(graphs.PathGraph(4))
|
667
|
+
[(0, 1, None), (1, 2, None), (2, 3, None)]
|
668
|
+
|
669
|
+
sage: G = Graph([(0,1,{'name':'a','weight':1}), (0,2,{'name':'b','weight':3}), (1,2,{'name':'b','weight':1})])
|
670
|
+
sage: min_spanning_tree(G, weight_function=lambda e: e[2]['weight'])
|
671
|
+
[(0, 1, {'name': 'a', 'weight': 1}), (1, 2, {'name': 'b', 'weight': 1})]
|
672
|
+
|
673
|
+
TESTS:
|
674
|
+
|
675
|
+
Given an input which is not a graph::
|
676
|
+
|
677
|
+
sage: min_spanning_tree("I am not a graph!")
|
678
|
+
Traceback (most recent call last):
|
679
|
+
...
|
680
|
+
TypeError: the input must be a Sage Graph
|
681
|
+
|
682
|
+
Given a wrong algorithm::
|
683
|
+
|
684
|
+
sage: min_spanning_tree(graphs.PathGraph(3), algorithm='tip top')
|
685
|
+
Traceback (most recent call last):
|
686
|
+
...
|
687
|
+
ValueError: algorithm 'tip top' not yet implemented, please contribute
|
688
|
+
|
689
|
+
If the weight is not a number::
|
690
|
+
|
691
|
+
sage: g = Graph([(0,1,1), (1,2,'a')], weighted=True)
|
692
|
+
sage: min_spanning_tree(g)
|
693
|
+
Traceback (most recent call last):
|
694
|
+
...
|
695
|
+
ValueError: could not convert string to float:...
|
696
|
+
|
697
|
+
sage: g = Graph([(0,1,1), (1,2,[1,2,3])], weighted=True)
|
698
|
+
sage: min_spanning_tree(g)
|
699
|
+
Traceback (most recent call last):
|
700
|
+
...
|
701
|
+
TypeError: float() argument must be a string or a... number...
|
702
|
+
|
703
|
+
Check that the method is robust to incomparable vertices::
|
704
|
+
|
705
|
+
sage: G = Graph([(1, 2, 10), (1, 'a', 1), ('a', 'b', 1), ('b', 2, 1)], weighted=True)
|
706
|
+
sage: E = min_spanning_tree(G, algorithm='Kruskal')
|
707
|
+
sage: sum(w for _, _, w in E)
|
708
|
+
3
|
709
|
+
sage: F = min_spanning_tree(G, algorithm='Prim')
|
710
|
+
sage: sum(w for _, _, w in F)
|
711
|
+
3
|
712
|
+
"""
|
713
|
+
from sage.graphs.graph import Graph
|
714
|
+
|
715
|
+
if not isinstance(g, Graph):
|
716
|
+
raise TypeError("the input must be a Sage Graph")
|
717
|
+
if algorithm not in ['Kruskal', 'Prim']:
|
718
|
+
raise ValueError("algorithm '%s' not yet implemented, please contribute" % (algorithm))
|
719
|
+
|
720
|
+
if g.allows_loops() or g.allows_multiple_edges():
|
721
|
+
g = g.to_simple()
|
722
|
+
# Now g has no self loops and no multiple edges.
|
723
|
+
# These variables are automatically deleted when the function terminates.
|
724
|
+
cdef BoostVecWeightedGraph g_boost
|
725
|
+
cdef vector[v_index] result
|
726
|
+
cdef v_index i
|
727
|
+
cdef list int_to_vertex = list(g)
|
728
|
+
cdef dict vertex_to_int = {v: i for i, v in enumerate(int_to_vertex)}
|
729
|
+
|
730
|
+
boost_weighted_graph_from_sage_graph(&g_boost, g, vertex_to_int, weight_function)
|
731
|
+
|
732
|
+
if algorithm == 'Kruskal':
|
733
|
+
sig_on()
|
734
|
+
result = g_boost.kruskal_min_spanning_tree()
|
735
|
+
sig_off()
|
736
|
+
elif algorithm == 'Prim':
|
737
|
+
sig_on()
|
738
|
+
result = g_boost.prim_min_spanning_tree()
|
739
|
+
sig_off()
|
740
|
+
|
741
|
+
cdef v_index n = g.num_verts()
|
742
|
+
|
743
|
+
if <v_index> result.size() != 2 * (n - 1):
|
744
|
+
return []
|
745
|
+
edges = [(int_to_vertex[<int> result[2*i]], int_to_vertex[<int> result[2*i + 1]]) for i in range(n - 1)]
|
746
|
+
return [(u, v, g.edge_label(u, v)) for u, v in edges]
|
747
|
+
|
748
|
+
|
749
|
+
cpdef blocks_and_cut_vertices(g):
|
750
|
+
r"""
|
751
|
+
Compute the blocks and cut vertices of the graph.
|
752
|
+
|
753
|
+
This method uses the implementation of Tarjan's algorithm available in the
|
754
|
+
Boost library .
|
755
|
+
|
756
|
+
INPUT:
|
757
|
+
|
758
|
+
- ``g`` -- the input Sage graph
|
759
|
+
|
760
|
+
OUTPUT:
|
761
|
+
|
762
|
+
A 2-dimensional vector with m+1 rows (m is the number of biconnected
|
763
|
+
components), where each of the first m rows correspond to vertices in a
|
764
|
+
block, and the last row is the list of cut vertices.
|
765
|
+
|
766
|
+
.. SEEALSO::
|
767
|
+
|
768
|
+
- :meth:`sage.graphs.generic_graph.GenericGraph.blocks_and_cut_vertices`
|
769
|
+
|
770
|
+
EXAMPLES::
|
771
|
+
|
772
|
+
sage: from sage.graphs.base.boost_graph import blocks_and_cut_vertices
|
773
|
+
sage: g = graphs.KrackhardtKiteGraph()
|
774
|
+
sage: blocks_and_cut_vertices(g)
|
775
|
+
([[8, 9], [7, 8], [0, 1, 2, 3, 5, 4, 6, 7]], [8, 7])
|
776
|
+
|
777
|
+
sage: G = Graph([(0,1,{'name':'a','weight':1}), (0,2,{'name':'b','weight':3}), (1,2,{'name':'b','weight':1})])
|
778
|
+
sage: blocks_and_cut_vertices(G)
|
779
|
+
([[0, 1, 2]], [])
|
780
|
+
|
781
|
+
TESTS:
|
782
|
+
|
783
|
+
Given an input which is not a graph::
|
784
|
+
|
785
|
+
sage: blocks_and_cut_vertices("I am not a graph!")
|
786
|
+
Traceback (most recent call last):
|
787
|
+
...
|
788
|
+
TypeError: the input must be a Sage graph
|
789
|
+
"""
|
790
|
+
from sage.graphs.generic_graph import GenericGraph
|
791
|
+
|
792
|
+
if not isinstance(g, GenericGraph):
|
793
|
+
raise TypeError("the input must be a Sage graph")
|
794
|
+
|
795
|
+
if g.allows_loops() or g.allows_multiple_edges() or g.is_directed():
|
796
|
+
g = g.to_simple()
|
797
|
+
|
798
|
+
cdef BoostVecGraph g_boost
|
799
|
+
cdef vector[vector[v_index]] result
|
800
|
+
cdef v_index vi
|
801
|
+
cdef list int_to_vertex = list(g)
|
802
|
+
cdef dict vertex_to_int = {vv: vi for vi, vv in enumerate(int_to_vertex)}
|
803
|
+
cdef list vertex_status = [-1] * g.order()
|
804
|
+
|
805
|
+
boost_graph_from_sage_graph(&g_boost, g, vertex_to_int)
|
806
|
+
sig_on()
|
807
|
+
result = g_boost.blocks_and_cut_vertices()
|
808
|
+
sig_off()
|
809
|
+
|
810
|
+
cdef list result_blocks = []
|
811
|
+
cdef set result_cut = set()
|
812
|
+
cdef list result_temp = []
|
813
|
+
cdef v_index v
|
814
|
+
|
815
|
+
# We iterate over the vertices in the blocks and find articulation points
|
816
|
+
for i in range(len(result)):
|
817
|
+
for v in result[i]:
|
818
|
+
# The vertex is seen for the first time
|
819
|
+
if vertex_status[v] == -1:
|
820
|
+
result_temp.append(int_to_vertex[<int> v])
|
821
|
+
vertex_status[v] = i
|
822
|
+
# Vertex belongs to a previous block also, must be a cut vertex
|
823
|
+
elif vertex_status[v] < i:
|
824
|
+
result_cut.add(int_to_vertex[<int> v])
|
825
|
+
result_temp.append(int_to_vertex[<int> v])
|
826
|
+
# Change the block number to avoid adding the vertex twice
|
827
|
+
# as a cut vertex if it is repeated in block i
|
828
|
+
vertex_status[v] = i
|
829
|
+
# elif vertex_status[v] == i:
|
830
|
+
# Nothing to do since we have already added the vertex to block i
|
831
|
+
|
832
|
+
result_blocks.append(result_temp)
|
833
|
+
result_temp = []
|
834
|
+
|
835
|
+
# If a vertex does not belong to any block, it must be an isolated vertex.
|
836
|
+
# Hence, it is considered a block.
|
837
|
+
for i in range(g.order()):
|
838
|
+
if vertex_status[i] == -1:
|
839
|
+
result_blocks.append([int_to_vertex[<int> i]])
|
840
|
+
|
841
|
+
return (result_blocks, list(result_cut))
|
842
|
+
|
843
|
+
|
844
|
+
cpdef shortest_paths(g, start, weight_function=None, algorithm=None):
|
845
|
+
r"""
|
846
|
+
Compute the shortest paths from ``start`` to all other vertices.
|
847
|
+
|
848
|
+
This routine outputs all shortest paths from node ``start`` to any other
|
849
|
+
node in the graph. The input graph can be weighted: if the algorithm is
|
850
|
+
Dijkstra, no negative weights are allowed, while if the algorithm is
|
851
|
+
Bellman-Ford, negative weights are allowed, but there must be no negative
|
852
|
+
cycle (otherwise, the shortest paths might not exist).
|
853
|
+
|
854
|
+
However, Dijkstra algorithm is more efficient: for this reason, we suggest
|
855
|
+
to use Bellman-Ford only if necessary (which is also the default option).
|
856
|
+
Note that, if the graph is undirected, a negative edge automatically creates
|
857
|
+
a negative cycle: for this reason, in this case, Dijkstra algorithm is
|
858
|
+
always better.
|
859
|
+
|
860
|
+
The running-time is `O(n \log n+m)` for Dijkstra algorithm and `O(mn)` for
|
861
|
+
Bellman-Ford algorithm, where `n` is the number of nodes and `m` is the
|
862
|
+
number of edges.
|
863
|
+
|
864
|
+
INPUT:
|
865
|
+
|
866
|
+
- ``g`` -- the input Sage graph
|
867
|
+
|
868
|
+
- ``start`` -- the starting vertex to compute shortest paths
|
869
|
+
|
870
|
+
- ``weight_function`` -- function (default: ``None``); a function that
|
871
|
+
associates a weight to each edge. If ``None`` (default), the weights of
|
872
|
+
``g`` are used, if ``g.weighted()==True``, otherwise all edges have
|
873
|
+
weight 1.
|
874
|
+
|
875
|
+
- ``algorithm`` -- string (default: ``None``); one of the following
|
876
|
+
algorithms:
|
877
|
+
|
878
|
+
- ``'Dijkstra'``, ``'Dijkstra_Boost'``: the Dijkstra algorithm implemented
|
879
|
+
in Boost (works only with positive weights)
|
880
|
+
|
881
|
+
- ``'Bellman-Ford'``, ``'Bellman-Ford_Boost'``: the Bellman-Ford algorithm
|
882
|
+
implemented in Boost (works also with negative weights, if there is no
|
883
|
+
negative cycle)
|
884
|
+
|
885
|
+
OUTPUT:
|
886
|
+
|
887
|
+
A pair of dictionaries ``(distances, predecessors)`` such that, for each
|
888
|
+
vertex ``v``, ``distances[v]`` is the distance from ``start`` to ``v``,
|
889
|
+
``predecessors[v]`` is the last vertex in a shortest path from ``start`` to
|
890
|
+
``v``.
|
891
|
+
|
892
|
+
EXAMPLES:
|
893
|
+
|
894
|
+
Undirected graphs::
|
895
|
+
|
896
|
+
sage: from sage.graphs.base.boost_graph import shortest_paths
|
897
|
+
sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
|
898
|
+
sage: shortest_paths(g, 1)
|
899
|
+
({0: 1, 1: 0, 2: 2, 3: 3}, {0: 1, 1: None, 2: 1, 3: 2})
|
900
|
+
sage: g = graphs.GridGraph([2,2])
|
901
|
+
sage: shortest_paths(g,(0,0),weight_function=lambda e:2)
|
902
|
+
({(0, 0): 0, (0, 1): 2, (1, 0): 2, (1, 1): 4},
|
903
|
+
{(0, 0): None, (0, 1): (0, 0), (1, 0): (0, 0), (1, 1): (0, 1)})
|
904
|
+
|
905
|
+
Directed graphs::
|
906
|
+
|
907
|
+
sage: g = DiGraph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
|
908
|
+
sage: shortest_paths(g, 1)
|
909
|
+
({1: 0, 2: 2, 3: 3}, {1: None, 2: 1, 3: 2})
|
910
|
+
|
911
|
+
TESTS:
|
912
|
+
|
913
|
+
Given an input which is not a graph::
|
914
|
+
|
915
|
+
sage: shortest_paths("I am not a graph!", 1)
|
916
|
+
Traceback (most recent call last):
|
917
|
+
...
|
918
|
+
TypeError: the input must be a Sage graph
|
919
|
+
|
920
|
+
If there is a negative cycle::
|
921
|
+
|
922
|
+
sage: from sage.graphs.base.boost_graph import shortest_paths
|
923
|
+
sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True)
|
924
|
+
sage: shortest_paths(g, 1)
|
925
|
+
Traceback (most recent call last):
|
926
|
+
...
|
927
|
+
ValueError: the graph contains a negative cycle
|
928
|
+
|
929
|
+
If Dijkstra is used with negative weights::
|
930
|
+
|
931
|
+
sage: from sage.graphs.base.boost_graph import shortest_paths
|
932
|
+
sage: g = DiGraph([(0,1,1),(1,2,-2),(1,3,4),(2,3,1)], weighted=True)
|
933
|
+
sage: shortest_paths(g, 1, algorithm='Dijkstra')
|
934
|
+
Traceback (most recent call last):
|
935
|
+
...
|
936
|
+
RuntimeError: Dijkstra algorithm does not work with negative weights, use Bellman-Ford instead
|
937
|
+
|
938
|
+
Wrong starting vertex::
|
939
|
+
|
940
|
+
sage: shortest_paths(g, 55)
|
941
|
+
Traceback (most recent call last):
|
942
|
+
...
|
943
|
+
ValueError: the starting vertex 55 is not in the graph
|
944
|
+
"""
|
945
|
+
from sage.graphs.generic_graph import GenericGraph
|
946
|
+
|
947
|
+
if not isinstance(g, GenericGraph):
|
948
|
+
raise TypeError("the input must be a Sage graph")
|
949
|
+
|
950
|
+
if start not in g:
|
951
|
+
raise ValueError("the starting vertex " + str(start) + " is not in " +
|
952
|
+
"the graph")
|
953
|
+
|
954
|
+
if not g.num_edges():
|
955
|
+
return ({start: 0}, {start: None})
|
956
|
+
|
957
|
+
# These variables are automatically deleted when the function terminates.
|
958
|
+
cdef v_index vi
|
959
|
+
cdef dict int_to_v = dict(enumerate(g))
|
960
|
+
cdef dict v_to_int = {vv: vi for vi, vv in enumerate(g)}
|
961
|
+
cdef BoostVecWeightedDiGraphU g_boost_dir
|
962
|
+
cdef BoostVecWeightedGraph g_boost_und
|
963
|
+
cdef result_distances result
|
964
|
+
|
965
|
+
if algorithm is None:
|
966
|
+
# Check if there are edges with negative weights
|
967
|
+
if weight_function is not None:
|
968
|
+
for e in g.edge_iterator():
|
969
|
+
if float(weight_function(e)) < 0:
|
970
|
+
algorithm = 'Bellman-Ford'
|
971
|
+
break
|
972
|
+
elif g.weighted():
|
973
|
+
for _, _, w in g.edge_iterator():
|
974
|
+
if float(w) < 0:
|
975
|
+
algorithm = 'Bellman-Ford'
|
976
|
+
break
|
977
|
+
|
978
|
+
if algorithm is None:
|
979
|
+
algorithm = 'Dijkstra'
|
980
|
+
|
981
|
+
if algorithm in ['Bellman-Ford', 'Bellman-Ford_Boost']:
|
982
|
+
if g.is_directed():
|
983
|
+
boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
|
984
|
+
vi = v_to_int[start]
|
985
|
+
sig_on()
|
986
|
+
result = g_boost_dir.bellman_ford_shortest_paths(vi)
|
987
|
+
sig_off()
|
988
|
+
else:
|
989
|
+
boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
|
990
|
+
vi = v_to_int[start]
|
991
|
+
sig_on()
|
992
|
+
result = g_boost_und.bellman_ford_shortest_paths(vi)
|
993
|
+
sig_off()
|
994
|
+
if not result.distances.size():
|
995
|
+
raise ValueError("the graph contains a negative cycle")
|
996
|
+
|
997
|
+
elif algorithm in ['Dijkstra', 'Dijkstra_Boost']:
|
998
|
+
try:
|
999
|
+
if g.is_directed():
|
1000
|
+
boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
|
1001
|
+
vi = v_to_int[start]
|
1002
|
+
sig_on()
|
1003
|
+
result = g_boost_dir.dijkstra_shortest_paths(vi)
|
1004
|
+
sig_off()
|
1005
|
+
else:
|
1006
|
+
boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
|
1007
|
+
vi = v_to_int[start]
|
1008
|
+
sig_on()
|
1009
|
+
result = g_boost_und.dijkstra_shortest_paths(vi)
|
1010
|
+
sig_off()
|
1011
|
+
if not result.distances.size():
|
1012
|
+
raise RuntimeError("Dijkstra algorithm does not work with negative weights, use Bellman-Ford instead")
|
1013
|
+
except RuntimeError:
|
1014
|
+
raise RuntimeError("Dijkstra algorithm does not work with negative weights, use Bellman-Ford instead")
|
1015
|
+
|
1016
|
+
else:
|
1017
|
+
raise ValueError(f"unknown algorithm {algorithm!r}")
|
1018
|
+
|
1019
|
+
dist = {}
|
1020
|
+
pred = {}
|
1021
|
+
|
1022
|
+
if weight_function is not None:
|
1023
|
+
correct_type = type(weight_function(next(g.edge_iterator())))
|
1024
|
+
elif g.weighted():
|
1025
|
+
correct_type = type(next(g.edge_iterator())[2])
|
1026
|
+
else:
|
1027
|
+
correct_type = int
|
1028
|
+
# Needed for rational curves.
|
1029
|
+
try:
|
1030
|
+
from sage.rings.real_mpfr import RealNumber, RR
|
1031
|
+
except ImportError:
|
1032
|
+
pass
|
1033
|
+
else:
|
1034
|
+
if correct_type == RealNumber:
|
1035
|
+
correct_type = RR
|
1036
|
+
|
1037
|
+
import sys
|
1038
|
+
for v in range(g.num_verts()):
|
1039
|
+
if result.distances[v] != sys.float_info.max:
|
1040
|
+
w = int_to_v[v]
|
1041
|
+
dist[w] = correct_type(result.distances[v])
|
1042
|
+
pred[w] = int_to_v[result.predecessors[v]] if result.predecessors[v] != v else None
|
1043
|
+
return (dist, pred)
|
1044
|
+
|
1045
|
+
|
1046
|
+
cdef get_predecessors(BoostWeightedGraph g, result, int_to_v, directed, weight_type):
|
1047
|
+
r"""
|
1048
|
+
Return the predecessor matrix from the distance matrix of the graph.
|
1049
|
+
|
1050
|
+
INPUT:
|
1051
|
+
|
1052
|
+
- ``g`` -- the input boost graph
|
1053
|
+
|
1054
|
+
- ``result`` -- the matrix of shortest distances
|
1055
|
+
|
1056
|
+
- ``int_to_v`` -- list; it is a mapping from `(0, \ldots, n-1)`
|
1057
|
+
to the vertex set of the original sage graph
|
1058
|
+
|
1059
|
+
- ``directed`` -- boolean; whether the input graph is directed
|
1060
|
+
|
1061
|
+
- ``weight_type`` -- correct data type for edge weights
|
1062
|
+
|
1063
|
+
OUTPUT:
|
1064
|
+
|
1065
|
+
A dictionary of dictionaries ``pred`` such that ``pred[u][v]`` indicates
|
1066
|
+
the predecessor of `v` in the shortest path from `u` to `v`.
|
1067
|
+
|
1068
|
+
TESTS::
|
1069
|
+
|
1070
|
+
sage: from sage.graphs.base.boost_graph import johnson_shortest_paths
|
1071
|
+
sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
|
1072
|
+
sage: expected = {0: {0: None, 1: 0, 2: 1, 3: 2},
|
1073
|
+
....: 1: {0: 1, 1: None, 2: 1, 3: 2},
|
1074
|
+
....: 2: {0: 1, 1: 2, 2: None, 3: 2},
|
1075
|
+
....: 3: {0: 1, 1: 2, 2: 3, 3: None}}
|
1076
|
+
sage: johnson_shortest_paths(g, distances=False, predecessors=True) == expected
|
1077
|
+
True
|
1078
|
+
"""
|
1079
|
+
cdef vector[pair[v_index, pair[v_index, double]]] edges
|
1080
|
+
sig_on()
|
1081
|
+
edges = g.edge_list()
|
1082
|
+
sig_off()
|
1083
|
+
cdef int N = g.num_verts()
|
1084
|
+
cdef dict pred = {v: {v: None} for v in int_to_v}
|
1085
|
+
import sys
|
1086
|
+
for p in edges:
|
1087
|
+
dst = weight_type(p.second.second)
|
1088
|
+
# dst is the weight of the edge (u, v)
|
1089
|
+
u = p.first
|
1090
|
+
v = p.second.first
|
1091
|
+
for k in range(N):
|
1092
|
+
if result[k][u] == sys.float_info.max or result[k][v] == sys.float_info.max:
|
1093
|
+
continue
|
1094
|
+
if weight_type(result[k][u]) + dst == weight_type(result[k][v]):
|
1095
|
+
pred[int_to_v[k]][int_to_v[v]] = int_to_v[u]
|
1096
|
+
if directed:
|
1097
|
+
continue
|
1098
|
+
if weight_type(result[k][u]) == weight_type(result[k][v]) + dst:
|
1099
|
+
pred[int_to_v[k]][int_to_v[u]] = int_to_v[v]
|
1100
|
+
return pred
|
1101
|
+
|
1102
|
+
|
1103
|
+
cpdef johnson_shortest_paths(g, weight_function=None, distances=True, predecessors=False):
|
1104
|
+
r"""
|
1105
|
+
Use Johnson algorithm to solve the all-pairs-shortest-paths.
|
1106
|
+
|
1107
|
+
This routine outputs the distance between each pair of vertices and the
|
1108
|
+
predecessors matrix (depending on the values of boolean ``distances`` and
|
1109
|
+
``predecessors``) using a dictionary of dictionaries. It works on all kinds
|
1110
|
+
of graphs, but it is designed specifically for graphs with negative weights
|
1111
|
+
(otherwise there are more efficient algorithms, like Dijkstra).
|
1112
|
+
|
1113
|
+
The time-complexity is `O(mn\log n)`, where `n` is the number of nodes and
|
1114
|
+
`m` is the number of edges.
|
1115
|
+
|
1116
|
+
INPUT:
|
1117
|
+
|
1118
|
+
- ``g`` -- the input Sage graph
|
1119
|
+
|
1120
|
+
- ``weight_function`` -- function (default: ``None``); a function that
|
1121
|
+
associates a weight to each edge. If ``None`` (default), the weights of
|
1122
|
+
``g`` are used, if ``g.weighted()==True``, otherwise all edges have
|
1123
|
+
weight 1.
|
1124
|
+
|
1125
|
+
- ``distances`` -- boolean (default: ``True``); whether to return the
|
1126
|
+
dictionary of shortest distances
|
1127
|
+
|
1128
|
+
- ``predecessors`` -- boolean (default: ``False``); whether to return the
|
1129
|
+
predecessors matrix
|
1130
|
+
|
1131
|
+
OUTPUT:
|
1132
|
+
|
1133
|
+
Depending on the input, this function return the dictionary of
|
1134
|
+
predecessors, the dictionary of distances, or a pair of dictionaries
|
1135
|
+
``(distances, predecessors)`` where ``distance[u][v]`` denotes the distance
|
1136
|
+
of a shortest path from `u` to `v` and ``predecessors[u][v]`` indicates the
|
1137
|
+
predecessor of `w` on a shortest path from `u` to `v`.
|
1138
|
+
|
1139
|
+
EXAMPLES:
|
1140
|
+
|
1141
|
+
Undirected graphs::
|
1142
|
+
|
1143
|
+
sage: from sage.graphs.base.boost_graph import johnson_shortest_paths
|
1144
|
+
sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
|
1145
|
+
sage: johnson_shortest_paths(g)
|
1146
|
+
{0: {0: 0, 1: 1, 2: 3, 3: 4},
|
1147
|
+
1: {0: 1, 1: 0, 2: 2, 3: 3},
|
1148
|
+
2: {0: 3, 1: 2, 2: 0, 3: 1},
|
1149
|
+
3: {0: 4, 1: 3, 2: 1, 3: 0}}
|
1150
|
+
sage: expected = {0: {0: None, 1: 0, 2: 1, 3: 2},
|
1151
|
+
....: 1: {0: 1, 1: None, 2: 1, 3: 2},
|
1152
|
+
....: 2: {0: 1, 1: 2, 2: None, 3: 2},
|
1153
|
+
....: 3: {0: 1, 1: 2, 2: 3, 3: None}}
|
1154
|
+
sage: johnson_shortest_paths(g, distances=False, predecessors=True) == expected
|
1155
|
+
True
|
1156
|
+
|
1157
|
+
Directed graphs::
|
1158
|
+
|
1159
|
+
sage: g = DiGraph([(0,1,1),(1,2,-2),(1,3,4),(2,3,1)], weighted=True)
|
1160
|
+
sage: johnson_shortest_paths(g)
|
1161
|
+
{0: {0: 0, 1: 1, 2: -1, 3: 0},
|
1162
|
+
1: {1: 0, 2: -2, 3: -1},
|
1163
|
+
2: {2: 0, 3: 1},
|
1164
|
+
3: {3: 0}}
|
1165
|
+
sage: g = DiGraph([(1,2,3),(2,3,2),(1,4,1),(4,2,1)], weighted=True)
|
1166
|
+
sage: johnson_shortest_paths(g, distances=False, predecessors=True)
|
1167
|
+
{1: {1: None, 2: 4, 3: 2, 4: 1},
|
1168
|
+
2: {2: None, 3: 2},
|
1169
|
+
3: {3: None},
|
1170
|
+
4: {2: 4, 3: 2, 4: None}}
|
1171
|
+
|
1172
|
+
TESTS:
|
1173
|
+
|
1174
|
+
Given an input which is not a graph::
|
1175
|
+
|
1176
|
+
sage: johnson_shortest_paths("I am not a graph!")
|
1177
|
+
Traceback (most recent call last):
|
1178
|
+
...
|
1179
|
+
TypeError: the input must be a Sage graph
|
1180
|
+
|
1181
|
+
If there is a negative cycle::
|
1182
|
+
|
1183
|
+
sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True)
|
1184
|
+
sage: johnson_shortest_paths(g)
|
1185
|
+
Traceback (most recent call last):
|
1186
|
+
...
|
1187
|
+
ValueError: the graph contains a negative cycle
|
1188
|
+
"""
|
1189
|
+
from sage.graphs.generic_graph import GenericGraph
|
1190
|
+
cdef dict dist = {}
|
1191
|
+
cdef dict pred = {}
|
1192
|
+
|
1193
|
+
if not isinstance(g, GenericGraph):
|
1194
|
+
raise TypeError("the input must be a Sage graph")
|
1195
|
+
elif not g.num_edges():
|
1196
|
+
dist = {v: {v: 0} for v in g}
|
1197
|
+
pred = {v: {v: None} for v in g}
|
1198
|
+
if distances and predecessors:
|
1199
|
+
return (dist, pred)
|
1200
|
+
if distances:
|
1201
|
+
return dist
|
1202
|
+
if predecessors:
|
1203
|
+
return pred
|
1204
|
+
# These variables are automatically deleted when the function terminates.
|
1205
|
+
cdef v_index i
|
1206
|
+
cdef list int_to_v = list(g)
|
1207
|
+
cdef dict v_to_int = {v: i for i, v in enumerate(int_to_v)}
|
1208
|
+
cdef BoostVecWeightedDiGraphU g_boost_dir
|
1209
|
+
cdef BoostVecWeightedGraph g_boost_und
|
1210
|
+
cdef int N = g.num_verts()
|
1211
|
+
cdef vector[vector[double]] result
|
1212
|
+
|
1213
|
+
if g.is_directed():
|
1214
|
+
boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
|
1215
|
+
sig_on()
|
1216
|
+
result = g_boost_dir.johnson_shortest_paths()
|
1217
|
+
sig_off()
|
1218
|
+
else:
|
1219
|
+
boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
|
1220
|
+
sig_on()
|
1221
|
+
result = g_boost_und.johnson_shortest_paths()
|
1222
|
+
sig_off()
|
1223
|
+
|
1224
|
+
if not result.size():
|
1225
|
+
raise ValueError("the graph contains a negative cycle")
|
1226
|
+
|
1227
|
+
if weight_function is not None:
|
1228
|
+
correct_type = type(weight_function(next(g.edge_iterator())))
|
1229
|
+
elif g.weighted():
|
1230
|
+
correct_type = type(next(g.edge_iterator())[2])
|
1231
|
+
else:
|
1232
|
+
correct_type = int
|
1233
|
+
# Needed for rational curves.
|
1234
|
+
try:
|
1235
|
+
from sage.rings.real_mpfr import RealNumber, RR
|
1236
|
+
except ImportError:
|
1237
|
+
pass
|
1238
|
+
else:
|
1239
|
+
if correct_type == RealNumber:
|
1240
|
+
correct_type = RR
|
1241
|
+
|
1242
|
+
import sys
|
1243
|
+
if distances:
|
1244
|
+
dist = {int_to_v[v]: {int_to_v[w]: correct_type(result[v][w])
|
1245
|
+
for w in range(N) if result[v][w] != sys.float_info.max}
|
1246
|
+
for v in range(N)}
|
1247
|
+
|
1248
|
+
if predecessors:
|
1249
|
+
if g.is_directed():
|
1250
|
+
pred = get_predecessors(g_boost_dir, result, int_to_v, True, correct_type)
|
1251
|
+
else:
|
1252
|
+
pred = get_predecessors(g_boost_und, result, int_to_v, False, correct_type)
|
1253
|
+
|
1254
|
+
if distances and predecessors:
|
1255
|
+
return (dist, pred)
|
1256
|
+
if distances:
|
1257
|
+
return dist
|
1258
|
+
if predecessors:
|
1259
|
+
return pred
|
1260
|
+
|
1261
|
+
|
1262
|
+
cpdef floyd_warshall_shortest_paths(g, weight_function=None, distances=True, predecessors=False):
|
1263
|
+
r"""
|
1264
|
+
Use Floyd-Warshall algorithm to solve the all-pairs-shortest-paths.
|
1265
|
+
|
1266
|
+
This routine outputs the distance between each pair of vertices and the
|
1267
|
+
predecessors matrix (depending on the values of boolean ``distances`` and
|
1268
|
+
``predecessors``) using a dictionary of dictionaries. This method should be
|
1269
|
+
preferred only if the graph is dense. If the graph is sparse the much
|
1270
|
+
faster johnson_shortest_paths should be used.
|
1271
|
+
|
1272
|
+
The time-complexity is `O(n^3 + nm)`, where `n` is the number of nodes and
|
1273
|
+
`m` the number of edges. The factor `nm` in the complexity is added only
|
1274
|
+
when ``predecessors`` is set to ``True``.
|
1275
|
+
|
1276
|
+
INPUT:
|
1277
|
+
|
1278
|
+
- ``g`` -- the input Sage graph
|
1279
|
+
|
1280
|
+
- ``weight_function`` -- function (default: ``None``); a function that
|
1281
|
+
associates a weight to each edge. If ``None`` (default), the weights of
|
1282
|
+
``g`` are used, if ``g.weighted()==True``, otherwise all edges have
|
1283
|
+
weight 1.
|
1284
|
+
|
1285
|
+
- ``distances`` -- boolean (default: ``True``); whether to return
|
1286
|
+
the dictionary of shortest distances
|
1287
|
+
|
1288
|
+
- ``predecessors`` -- boolean (default: ``False``); whether to return the
|
1289
|
+
predecessors matrix
|
1290
|
+
|
1291
|
+
OUTPUT:
|
1292
|
+
|
1293
|
+
Depending on the input, this function return the dictionary of
|
1294
|
+
predecessors, the dictionary of distances, or a pair of dictionaries
|
1295
|
+
``(distances, predecessors)`` where ``distance[u][v]`` denotes the distance
|
1296
|
+
of a shortest path from `u` to `v` and ``predecessors[u][v]`` indicates the
|
1297
|
+
predecessor of `w` on a shortest path from `u` to `v`.
|
1298
|
+
|
1299
|
+
EXAMPLES:
|
1300
|
+
|
1301
|
+
Undirected graphs::
|
1302
|
+
|
1303
|
+
sage: from sage.graphs.base.boost_graph import floyd_warshall_shortest_paths
|
1304
|
+
sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
|
1305
|
+
sage: floyd_warshall_shortest_paths(g)
|
1306
|
+
{0: {0: 0, 1: 1, 2: 3, 3: 4},
|
1307
|
+
1: {0: 1, 1: 0, 2: 2, 3: 3},
|
1308
|
+
2: {0: 3, 1: 2, 2: 0, 3: 1},
|
1309
|
+
3: {0: 4, 1: 3, 2: 1, 3: 0}}
|
1310
|
+
sage: expected = {0: {0: None, 1: 0, 2: 1, 3: 2},
|
1311
|
+
....: 1: {0: 1, 1: None, 2: 1, 3: 2},
|
1312
|
+
....: 2: {0: 1, 1: 2, 2: None, 3: 2},
|
1313
|
+
....: 3: {0: 1, 1: 2, 2: 3, 3: None}}
|
1314
|
+
sage: floyd_warshall_shortest_paths(g, distances=False, predecessors=True) == expected
|
1315
|
+
True
|
1316
|
+
|
1317
|
+
Directed graphs::
|
1318
|
+
|
1319
|
+
sage: g = DiGraph([(0,1,1),(1,2,-2),(1,3,4),(2,3,1)], weighted=True)
|
1320
|
+
sage: floyd_warshall_shortest_paths(g)
|
1321
|
+
{0: {0: 0, 1: 1, 2: -1, 3: 0},
|
1322
|
+
1: {1: 0, 2: -2, 3: -1},
|
1323
|
+
2: {2: 0, 3: 1},
|
1324
|
+
3: {3: 0}}
|
1325
|
+
sage: g = DiGraph([(1,2,3),(2,3,2),(1,4,1),(4,2,1)], weighted=True)
|
1326
|
+
sage: floyd_warshall_shortest_paths(g, distances=False, predecessors=True)
|
1327
|
+
{1: {1: None, 2: 4, 3: 2, 4: 1},
|
1328
|
+
2: {2: None, 3: 2},
|
1329
|
+
3: {3: None},
|
1330
|
+
4: {2: 4, 3: 2, 4: None}}
|
1331
|
+
|
1332
|
+
TESTS:
|
1333
|
+
|
1334
|
+
Given an input which is not a graph::
|
1335
|
+
|
1336
|
+
sage: floyd_warshall_shortest_paths("I am not a graph!")
|
1337
|
+
Traceback (most recent call last):
|
1338
|
+
...
|
1339
|
+
TypeError: the input must be a Sage graph
|
1340
|
+
|
1341
|
+
If there is a negative cycle:
|
1342
|
+
|
1343
|
+
sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True)
|
1344
|
+
sage: floyd_warshall_shortest_paths(g)
|
1345
|
+
Traceback (most recent call last):
|
1346
|
+
...
|
1347
|
+
ValueError: the graph contains a negative cycle
|
1348
|
+
"""
|
1349
|
+
from sage.graphs.generic_graph import GenericGraph
|
1350
|
+
cdef dict dist = {}
|
1351
|
+
cdef dict pred = {}
|
1352
|
+
|
1353
|
+
if not isinstance(g, GenericGraph):
|
1354
|
+
raise TypeError("the input must be a Sage graph")
|
1355
|
+
elif not g.num_edges():
|
1356
|
+
dist = {v: {v: 0} for v in g}
|
1357
|
+
pred = {v: {v: None} for v in g}
|
1358
|
+
if distances and predecessors:
|
1359
|
+
return (dist, pred)
|
1360
|
+
if distances:
|
1361
|
+
return dist
|
1362
|
+
if predecessors:
|
1363
|
+
return pred
|
1364
|
+
# These variables are automatically deleted when the function terminates.
|
1365
|
+
cdef v_index i
|
1366
|
+
cdef list int_to_v = list(g)
|
1367
|
+
cdef dict v_to_int = {v: i for i, v in enumerate(int_to_v)}
|
1368
|
+
cdef BoostVecWeightedDiGraphU g_boost_dir
|
1369
|
+
cdef BoostVecWeightedGraph g_boost_und
|
1370
|
+
cdef int N = g.num_verts()
|
1371
|
+
cdef vector[vector[double]] result
|
1372
|
+
|
1373
|
+
if g.is_directed():
|
1374
|
+
boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
|
1375
|
+
sig_on()
|
1376
|
+
result = g_boost_dir.floyd_warshall_shortest_paths()
|
1377
|
+
sig_off()
|
1378
|
+
else:
|
1379
|
+
boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
|
1380
|
+
sig_on()
|
1381
|
+
result = g_boost_und.floyd_warshall_shortest_paths()
|
1382
|
+
sig_off()
|
1383
|
+
|
1384
|
+
if not result.size():
|
1385
|
+
raise ValueError("the graph contains a negative cycle")
|
1386
|
+
|
1387
|
+
if weight_function is not None:
|
1388
|
+
correct_type = type(weight_function(next(g.edge_iterator())))
|
1389
|
+
elif g.weighted():
|
1390
|
+
correct_type = type(next(g.edge_iterator())[2])
|
1391
|
+
else:
|
1392
|
+
correct_type = int
|
1393
|
+
# Needed for rational curves.
|
1394
|
+
try:
|
1395
|
+
from sage.rings.real_mpfr import RealNumber, RR
|
1396
|
+
except ImportError:
|
1397
|
+
pass
|
1398
|
+
else:
|
1399
|
+
if correct_type == RealNumber:
|
1400
|
+
correct_type = RR
|
1401
|
+
|
1402
|
+
import sys
|
1403
|
+
if distances:
|
1404
|
+
dist = {int_to_v[v]: {int_to_v[w]: correct_type(result[v][w])
|
1405
|
+
for w in range(N) if result[v][w] != sys.float_info.max}
|
1406
|
+
for v in range(N)}
|
1407
|
+
|
1408
|
+
if predecessors:
|
1409
|
+
if g.is_directed():
|
1410
|
+
pred = get_predecessors(g_boost_dir, result, int_to_v, True, correct_type)
|
1411
|
+
else:
|
1412
|
+
pred = get_predecessors(g_boost_und, result, int_to_v, False, correct_type)
|
1413
|
+
|
1414
|
+
if distances and predecessors:
|
1415
|
+
return (dist, pred)
|
1416
|
+
if distances:
|
1417
|
+
return dist
|
1418
|
+
if predecessors:
|
1419
|
+
return pred
|
1420
|
+
|
1421
|
+
|
1422
|
+
cpdef johnson_closeness_centrality(g, weight_function=None):
|
1423
|
+
r"""
|
1424
|
+
Use Johnson algorithm to compute the closeness centrality of all vertices.
|
1425
|
+
|
1426
|
+
This routine is preferable to :func:`~johnson_shortest_paths` because it
|
1427
|
+
does not create a doubly indexed dictionary of distances, saving memory.
|
1428
|
+
|
1429
|
+
The time-complexity is `O(mn\log n)`, where `n` is the number of nodes and
|
1430
|
+
`m` is the number of edges.
|
1431
|
+
|
1432
|
+
INPUT:
|
1433
|
+
|
1434
|
+
- ``g`` -- the input Sage graph
|
1435
|
+
|
1436
|
+
- ``weight_function`` -- function (default: ``None``); a function that
|
1437
|
+
associates a weight to each edge. If ``None`` (default), the weights of
|
1438
|
+
``g`` are used, if ``g.weighted()==True``, otherwise all edges have
|
1439
|
+
weight 1.
|
1440
|
+
|
1441
|
+
OUTPUT: a dictionary associating each vertex ``v`` to its closeness centrality
|
1442
|
+
|
1443
|
+
EXAMPLES:
|
1444
|
+
|
1445
|
+
Undirected graphs::
|
1446
|
+
|
1447
|
+
sage: from sage.graphs.base.boost_graph import johnson_closeness_centrality
|
1448
|
+
sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
|
1449
|
+
sage: johnson_closeness_centrality(g)
|
1450
|
+
{0: 0.375, 1: 0.5, 2: 0.5, 3: 0.375}
|
1451
|
+
|
1452
|
+
Directed graphs::
|
1453
|
+
|
1454
|
+
sage: from sage.graphs.base.boost_graph import johnson_closeness_centrality
|
1455
|
+
sage: g = DiGraph([(0,1,1),(1,2,-2),(1,3,4),(2,3,1)], weighted=True)
|
1456
|
+
sage: johnson_closeness_centrality(g)
|
1457
|
+
{0: inf, 1: -0.4444444444444444, 2: 0.3333333333333333}
|
1458
|
+
|
1459
|
+
TESTS:
|
1460
|
+
|
1461
|
+
Given an input which is not a graph::
|
1462
|
+
|
1463
|
+
sage: from sage.graphs.base.boost_graph import johnson_closeness_centrality
|
1464
|
+
sage: johnson_closeness_centrality("I am not a graph!")
|
1465
|
+
Traceback (most recent call last):
|
1466
|
+
...
|
1467
|
+
TypeError: the input must be a Sage graph
|
1468
|
+
|
1469
|
+
If there is a negative cycle::
|
1470
|
+
|
1471
|
+
sage: from sage.graphs.base.boost_graph import johnson_closeness_centrality
|
1472
|
+
sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True)
|
1473
|
+
sage: johnson_closeness_centrality(g)
|
1474
|
+
Traceback (most recent call last):
|
1475
|
+
...
|
1476
|
+
ValueError: the graph contains a negative cycle
|
1477
|
+
"""
|
1478
|
+
from sage.graphs.generic_graph import GenericGraph
|
1479
|
+
|
1480
|
+
if not isinstance(g, GenericGraph):
|
1481
|
+
raise TypeError("the input must be a Sage graph")
|
1482
|
+
elif not g.num_edges():
|
1483
|
+
return {}
|
1484
|
+
# These variables are automatically deleted when the function terminates.
|
1485
|
+
cdef BoostVecWeightedDiGraphU g_boost_dir
|
1486
|
+
cdef BoostVecWeightedGraph g_boost_und
|
1487
|
+
cdef int N = g.num_verts()
|
1488
|
+
cdef vector[vector[double]] result
|
1489
|
+
cdef vector[double] closeness
|
1490
|
+
cdef double farness
|
1491
|
+
cdef int i, j, reach
|
1492
|
+
cdef list int_to_v = list(g)
|
1493
|
+
cdef dict v_to_int = {v: i for i, v in enumerate(int_to_v)}
|
1494
|
+
|
1495
|
+
if g.is_directed():
|
1496
|
+
boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
|
1497
|
+
sig_on()
|
1498
|
+
result = g_boost_dir.johnson_shortest_paths()
|
1499
|
+
sig_off()
|
1500
|
+
else:
|
1501
|
+
boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
|
1502
|
+
sig_on()
|
1503
|
+
result = g_boost_und.johnson_shortest_paths()
|
1504
|
+
sig_off()
|
1505
|
+
|
1506
|
+
if not result.size():
|
1507
|
+
raise ValueError("the graph contains a negative cycle")
|
1508
|
+
|
1509
|
+
import sys
|
1510
|
+
for i in range(N):
|
1511
|
+
farness = 0
|
1512
|
+
reach = 0
|
1513
|
+
for j in range(N):
|
1514
|
+
if result[i][j] != sys.float_info.max:
|
1515
|
+
farness += result[i][j]
|
1516
|
+
reach += 1
|
1517
|
+
if reach > 1:
|
1518
|
+
closeness.push_back((<double>reach - 1) * (reach - 1) / ((N - 1) * farness))
|
1519
|
+
else:
|
1520
|
+
closeness.push_back(sys.float_info.max)
|
1521
|
+
sig_check()
|
1522
|
+
return {v: closeness[i] for i, v in enumerate(int_to_v) if closeness[i] != sys.float_info.max}
|
1523
|
+
|
1524
|
+
|
1525
|
+
cpdef min_cycle_basis(g_sage, weight_function=None, by_weight=False):
|
1526
|
+
r"""
|
1527
|
+
Return a minimum weight cycle basis of the input graph ``g_sage``.
|
1528
|
+
|
1529
|
+
A cycle basis is a list of cycles (list of vertices forming a cycle) of
|
1530
|
+
``g_sage``. Note that the vertices are not necessarily returned in the order
|
1531
|
+
in which they appear in the cycle.
|
1532
|
+
|
1533
|
+
A minimum weight cycle basis is a cycle basis that minimizes the sum of the
|
1534
|
+
weights (length for unweighted graphs) of its cycles.
|
1535
|
+
|
1536
|
+
Not implemented for directed graphs and multigraphs.
|
1537
|
+
|
1538
|
+
INPUT:
|
1539
|
+
|
1540
|
+
- ``g_sage`` -- a Sage Graph
|
1541
|
+
|
1542
|
+
- ``weight_function`` -- function (default: ``None``); a function that takes
|
1543
|
+
as input an edge ``(u, v, l)`` and outputs its weight. If not ``None``,
|
1544
|
+
``by_weight`` is automatically set to ``True``. If ``None`` and
|
1545
|
+
``by_weight`` is ``True``, the weights of ``g_sage`` are used, if
|
1546
|
+
``g_sage.weighted()==True``, otherwise all edges have weight 1.
|
1547
|
+
|
1548
|
+
- ``by_weight`` -- boolean (default: ``False``); if ``True``, the edges in
|
1549
|
+
the graph are weighted, otherwise all edges have weight 1
|
1550
|
+
|
1551
|
+
EXAMPLES::
|
1552
|
+
|
1553
|
+
sage: g = Graph([(1, 2, 3), (2, 3, 5), (3, 4, 8), (4, 1, 13), (1, 3, 250), (5, 6, 9), (6, 7, 17), (7, 5, 20)])
|
1554
|
+
sage: sorted(g.minimum_cycle_basis(by_weight=True))
|
1555
|
+
[[1, 2, 3], [1, 2, 3, 4], [5, 6, 7]]
|
1556
|
+
sage: sorted(g.minimum_cycle_basis())
|
1557
|
+
[[1, 2, 3], [1, 3, 4], [5, 6, 7]]
|
1558
|
+
|
1559
|
+
.. SEEALSO::
|
1560
|
+
|
1561
|
+
* :wikipedia:`Cycle_basis`
|
1562
|
+
"""
|
1563
|
+
cdef Py_ssize_t u_int, v_int, i, j
|
1564
|
+
cdef object u, v
|
1565
|
+
cdef Py_ssize_t n = g_sage.num_verts()
|
1566
|
+
cdef list int_to_vertex = list(g_sage)
|
1567
|
+
cdef dict vertex_to_int = {u: u_int for u_int, u in enumerate(int_to_vertex)}
|
1568
|
+
cdef list edgelist
|
1569
|
+
if by_weight and weight_function is not None:
|
1570
|
+
edgelist = [(vertex_to_int[e[0]], vertex_to_int[e[1]], float(weight_function(e)))
|
1571
|
+
for e in g_sage.edge_iterator()]
|
1572
|
+
if by_weight:
|
1573
|
+
edgelist = [(vertex_to_int[e[0]], vertex_to_int[e[1]], float(e[2]))
|
1574
|
+
for e in g_sage.edge_iterator()]
|
1575
|
+
else:
|
1576
|
+
edgelist = [(vertex_to_int[u], vertex_to_int[v], 1) for u, v in g_sage.edge_iterator(labels=False)]
|
1577
|
+
|
1578
|
+
# We just need the edges of any spanning tree here not necessarily a
|
1579
|
+
# minimum spanning tree.
|
1580
|
+
|
1581
|
+
cdef list sp_edges = min_spanning_tree(g_sage)
|
1582
|
+
cdef cset[pair[int, int]] edges_s
|
1583
|
+
for a, b, c in sp_edges:
|
1584
|
+
edges_s.insert((a, b))
|
1585
|
+
# Edges of self that are not in the spanning tree
|
1586
|
+
cdef list edges_c = [e for e in g_sage.edge_iterator(labels=False) if not edges_s.count(e)]
|
1587
|
+
cdef list edges_complement = [frozenset((vertex_to_int[u], vertex_to_int[v])) for u, v in edges_c]
|
1588
|
+
cdef Py_ssize_t lec = len(edges_complement)
|
1589
|
+
cdef list orth_set = [set([e]) for e in edges_complement]
|
1590
|
+
cdef list cycle_basis = []
|
1591
|
+
cdef set base
|
1592
|
+
cdef BoostVecWeightedGraph g_boost_und
|
1593
|
+
cdef vector[vector[double]] all_pair_shortest_pathlens
|
1594
|
+
cdef result_distances min_path
|
1595
|
+
cdef list min_path_nodes
|
1596
|
+
cdef dict cross_paths_lens
|
1597
|
+
cdef float edge_w
|
1598
|
+
|
1599
|
+
for i in range(lec):
|
1600
|
+
base = orth_set[i]
|
1601
|
+
# For each edge in g, add 2 edges to g_boost_und: "cross" edges if edge
|
1602
|
+
# is in base, otherwise "in-plane" edges
|
1603
|
+
g_boost_und = BoostVecWeightedGraph()
|
1604
|
+
for u_int in range(2 * n):
|
1605
|
+
g_boost_und.add_vertex()
|
1606
|
+
for u_int, v_int, edge_w in edgelist:
|
1607
|
+
# mapping the nodes in g from 0 to n-1
|
1608
|
+
if frozenset((u_int, v_int)) in base:
|
1609
|
+
g_boost_und.add_edge(u_int, n + v_int, edge_w)
|
1610
|
+
g_boost_und.add_edge(n + u_int, v_int, edge_w)
|
1611
|
+
else:
|
1612
|
+
g_boost_und.add_edge(u_int, v_int, edge_w)
|
1613
|
+
g_boost_und.add_edge(n + u_int, n + v_int, edge_w)
|
1614
|
+
|
1615
|
+
sig_on()
|
1616
|
+
all_pair_shortest_pathlens = g_boost_und.johnson_shortest_paths()
|
1617
|
+
sig_off()
|
1618
|
+
cross_paths_lens = {j: all_pair_shortest_pathlens[j][n + j] for j in range(n)}
|
1619
|
+
u_int = min(cross_paths_lens, key=cross_paths_lens.get)
|
1620
|
+
v_int = n + u_int
|
1621
|
+
sig_on()
|
1622
|
+
min_path = g_boost_und.dijkstra_shortest_paths(u_int)
|
1623
|
+
sig_off()
|
1624
|
+
# Mapping the nodes in G to nodes in g
|
1625
|
+
min_path_nodes = [v_int if v_int < n else v_int - n]
|
1626
|
+
while v_int != u_int:
|
1627
|
+
v_int = min_path.predecessors[v_int]
|
1628
|
+
min_path_nodes.append(v_int if v_int < n else v_int - n)
|
1629
|
+
|
1630
|
+
# removal of edges occurring even number of times
|
1631
|
+
edges = set()
|
1632
|
+
for edge in zip(min_path_nodes[:-1], min_path_nodes[1:]):
|
1633
|
+
edges ^= {edge}
|
1634
|
+
new_cycle = {frozenset(e) for e in edges}
|
1635
|
+
cycle_basis.append([int_to_vertex[u_int] for u_int in set().union(*new_cycle)])
|
1636
|
+
# updating orth_set so that i+1, i+2, ...th elements are orthogonal
|
1637
|
+
# to the newly found cycle
|
1638
|
+
for j in range(i + 1, lec):
|
1639
|
+
if len(orth_set[j] & new_cycle) % 2:
|
1640
|
+
orth_set[j] = orth_set[j] ^ base
|
1641
|
+
return cycle_basis
|
1642
|
+
|
1643
|
+
|
1644
|
+
cpdef eccentricity_DHV(g, vertex_list=None, weight_function=None, check_weight=True):
|
1645
|
+
r"""
|
1646
|
+
Return the vector of eccentricities using the algorithm of [Dragan2018]_.
|
1647
|
+
|
1648
|
+
The array returned is of length `n`, and by default its `i`-th component is
|
1649
|
+
the eccentricity of the `i`-th vertex in ``g.vertices(sort=True)``,
|
1650
|
+
if ``vertex_list is None``, otherwise ``ecc[i]`` is the eccentricity of
|
1651
|
+
vertex ``vertex_list[i]``.
|
1652
|
+
|
1653
|
+
The algorithm proposed in [Dragan2018]_ is based on the observation that for
|
1654
|
+
all nodes `v,w\in V`, we have `\max(ecc[v]-d(v,w), d(v,w))\leq ecc[w] \leq
|
1655
|
+
ecc[v] + d(v,w)`. Also the algorithm iteratively improves upper and lower
|
1656
|
+
bounds on the eccentricity of each vertex until no further improvements can
|
1657
|
+
be done.
|
1658
|
+
|
1659
|
+
INPUT:
|
1660
|
+
|
1661
|
+
- ``g`` -- the input Sage graph
|
1662
|
+
|
1663
|
+
- ``vertex_list`` -- list (default: ``None``); a list of `n` vertices
|
1664
|
+
specifying a mapping from `(0, \ldots, n-1)` to vertex labels in `g`. When
|
1665
|
+
set, ``ecc[i]`` is the eccentricity of vertex ``vertex_list[i]``. When
|
1666
|
+
``vertex_list`` is ``None``, ``ecc[i]`` is the eccentricity of vertex
|
1667
|
+
``g.vertices(sort=True)[i]``.
|
1668
|
+
|
1669
|
+
- ``weight_function`` -- function (default: ``None``); a function that
|
1670
|
+
associates a weight to each edge. If ``None`` (default), the weights of
|
1671
|
+
``g`` are used, if ``g.weighted()==True``, otherwise all edges have
|
1672
|
+
weight 1.
|
1673
|
+
|
1674
|
+
- ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
|
1675
|
+
that the ``weight_function`` outputs a number for each edge
|
1676
|
+
|
1677
|
+
EXAMPLES::
|
1678
|
+
|
1679
|
+
sage: from sage.graphs.base.boost_graph import eccentricity_DHV
|
1680
|
+
sage: G = graphs.BullGraph()
|
1681
|
+
sage: eccentricity_DHV(G)
|
1682
|
+
[2.0, 2.0, 2.0, 3.0, 3.0]
|
1683
|
+
|
1684
|
+
TESTS:
|
1685
|
+
|
1686
|
+
sage: G = Graph(2)
|
1687
|
+
sage: eccentricity_DHV(G)
|
1688
|
+
[+Infinity, +Infinity]
|
1689
|
+
sage: G = graphs.RandomGNP(20, 0.7)
|
1690
|
+
sage: eccentricity_DHV(G) == G.eccentricity()
|
1691
|
+
True
|
1692
|
+
sage: G = Graph([(0,1,-1)], weighted=True)
|
1693
|
+
sage: eccentricity_DHV(G)
|
1694
|
+
Traceback (most recent call last):
|
1695
|
+
...
|
1696
|
+
ValueError: graph contains negative edge weights, use Johnson_Boost instead
|
1697
|
+
"""
|
1698
|
+
if g.is_directed():
|
1699
|
+
raise TypeError("the 'DHV' algorithm only works on undirected graphs")
|
1700
|
+
|
1701
|
+
cdef v_index n = g.order()
|
1702
|
+
if not n:
|
1703
|
+
return []
|
1704
|
+
if n == 1:
|
1705
|
+
return [0]
|
1706
|
+
|
1707
|
+
if weight_function and check_weight:
|
1708
|
+
g._check_weight_function(weight_function)
|
1709
|
+
|
1710
|
+
if weight_function is not None:
|
1711
|
+
for e in g.edge_iterator():
|
1712
|
+
if float(weight_function(e)) < 0:
|
1713
|
+
raise ValueError("graph contains negative edge weights, use Johnson_Boost instead")
|
1714
|
+
elif g.weighted():
|
1715
|
+
for _, _, w in g.edge_iterator():
|
1716
|
+
if w and float(w) < 0:
|
1717
|
+
raise ValueError("graph contains negative edge weights, use Johnson_Boost instead")
|
1718
|
+
|
1719
|
+
if vertex_list is None:
|
1720
|
+
vertex_list = g.vertices(sort=True)
|
1721
|
+
elif not len(vertex_list) == n or not set(vertex_list) == set(g):
|
1722
|
+
raise ValueError("parameter vertex_list is incorrect for this graph")
|
1723
|
+
|
1724
|
+
# These variables are automatically deleted when the function terminates.
|
1725
|
+
cdef dict v_to_int = {vv: vi for vi, vv in enumerate(vertex_list)}
|
1726
|
+
cdef BoostVecWeightedGraph g_boost
|
1727
|
+
boost_weighted_graph_from_sage_graph(&g_boost, g, v_to_int, weight_function)
|
1728
|
+
|
1729
|
+
import sys
|
1730
|
+
cdef v_index u, antipode, v
|
1731
|
+
cdef double ecc_u, ecc_antipode, tmp
|
1732
|
+
cdef v_index i, idx
|
1733
|
+
|
1734
|
+
cdef list active = list(range(n))
|
1735
|
+
cdef vector[double] ecc_lower_bound
|
1736
|
+
cdef vector[double] ecc_upper_bound
|
1737
|
+
cdef vector[double] distances
|
1738
|
+
|
1739
|
+
ecc_lower_bound.assign(n, 0)
|
1740
|
+
ecc_upper_bound.assign(n, sys.float_info.max)
|
1741
|
+
|
1742
|
+
# Algorithm
|
1743
|
+
while active:
|
1744
|
+
# Select vertex with minimum eccentricity in active and update
|
1745
|
+
# eccentricity upper bounds.
|
1746
|
+
# For this, we select u with minimum eccentricity lower bound in active
|
1747
|
+
# if ecc_u == ecc_lb[u], we are done. Otherwise, we update eccentricity
|
1748
|
+
# lower bounds and repeat
|
1749
|
+
|
1750
|
+
tmp = sys.float_info.max
|
1751
|
+
for i, v in enumerate(active):
|
1752
|
+
if ecc_lower_bound[v] < tmp:
|
1753
|
+
tmp = ecc_lower_bound[v]
|
1754
|
+
idx = i
|
1755
|
+
active[idx], active[-1] = active[-1], active[idx]
|
1756
|
+
u = active.pop()
|
1757
|
+
|
1758
|
+
# compute distances from u
|
1759
|
+
sig_on()
|
1760
|
+
distances = g_boost.dijkstra_shortest_paths(u).distances
|
1761
|
+
sig_off()
|
1762
|
+
|
1763
|
+
# Compute eccentricity of u
|
1764
|
+
ecc_u = 0
|
1765
|
+
for v in range(n):
|
1766
|
+
if ecc_u < distances[v]:
|
1767
|
+
ecc_u = distances[v]
|
1768
|
+
antipode = v
|
1769
|
+
ecc_upper_bound[u] = ecc_u
|
1770
|
+
|
1771
|
+
if ecc_u == sys.float_info.max: # Disconnected graph
|
1772
|
+
break
|
1773
|
+
|
1774
|
+
if ecc_u == ecc_lower_bound[u]:
|
1775
|
+
# We found the good vertex.
|
1776
|
+
# Update eccentricity upper bounds and remove from active those
|
1777
|
+
# vertices for which gap is closed
|
1778
|
+
i = 0
|
1779
|
+
while i < len(active):
|
1780
|
+
v = active[i]
|
1781
|
+
ecc_upper_bound[v] = min(ecc_upper_bound[v], distances[v] + ecc_u)
|
1782
|
+
if ecc_upper_bound[v] == ecc_lower_bound[v]:
|
1783
|
+
active[i] = active[-1]
|
1784
|
+
active.pop()
|
1785
|
+
else:
|
1786
|
+
i += 1
|
1787
|
+
|
1788
|
+
else:
|
1789
|
+
# u was not a good choice.
|
1790
|
+
# We use its antipode to update eccentricity lower bounds.
|
1791
|
+
# Observe that this antipode might have already been seen.
|
1792
|
+
for i, v in enumerate(active):
|
1793
|
+
if v == antipode:
|
1794
|
+
active[i] = active[-1]
|
1795
|
+
active.pop()
|
1796
|
+
break
|
1797
|
+
|
1798
|
+
# Compute distances from antipode
|
1799
|
+
sig_on()
|
1800
|
+
distances = g_boost.dijkstra_shortest_paths(antipode).distances
|
1801
|
+
sig_off()
|
1802
|
+
|
1803
|
+
# Compute eccentricity of antipode
|
1804
|
+
ecc_antipode = 0
|
1805
|
+
for v in range(n):
|
1806
|
+
ecc_antipode = max(ecc_antipode, distances[v])
|
1807
|
+
ecc_upper_bound[antipode] = ecc_antipode
|
1808
|
+
|
1809
|
+
# Update eccentricity lower bounds and remove from active those
|
1810
|
+
# vertices for which the gap is closed
|
1811
|
+
i = 0
|
1812
|
+
while i < len(active):
|
1813
|
+
v = active[i]
|
1814
|
+
ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v])
|
1815
|
+
if ecc_upper_bound[v] == ecc_lower_bound[v]:
|
1816
|
+
active[i] = active[-1]
|
1817
|
+
active.pop()
|
1818
|
+
else:
|
1819
|
+
i += 1
|
1820
|
+
|
1821
|
+
from sage.rings.infinity import Infinity
|
1822
|
+
cdef list eccentricity = []
|
1823
|
+
for i in range(n):
|
1824
|
+
if ecc_upper_bound[i] != sys.float_info.max:
|
1825
|
+
eccentricity.append(ecc_upper_bound[i])
|
1826
|
+
else:
|
1827
|
+
eccentricity.append(+Infinity)
|
1828
|
+
|
1829
|
+
return eccentricity
|
1830
|
+
|
1831
|
+
|
1832
|
+
cpdef radius_DHV(g, weight_function=None, check_weight=True):
|
1833
|
+
r"""
|
1834
|
+
Return the radius of weighted graph `g`.
|
1835
|
+
|
1836
|
+
This method computes the radius of undirected graph using the algorithm
|
1837
|
+
given in [Dragan2018]_.
|
1838
|
+
|
1839
|
+
This method returns Infinity if graph is not connected.
|
1840
|
+
|
1841
|
+
INPUT:
|
1842
|
+
|
1843
|
+
- ``g`` -- the input Sage graph
|
1844
|
+
|
1845
|
+
- ``weight_function`` -- function (default: ``None``); a function that
|
1846
|
+
associates a weight to each edge. If ``None`` (default), the weights of
|
1847
|
+
``g`` are used, if ``g.weighted()==True``, otherwise all edges have
|
1848
|
+
weight 1.
|
1849
|
+
|
1850
|
+
- ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
|
1851
|
+
that the ``weight_function`` outputs a number for each edge
|
1852
|
+
|
1853
|
+
EXAMPLES::
|
1854
|
+
|
1855
|
+
sage: from sage.graphs.base.boost_graph import radius_DHV
|
1856
|
+
sage: G = Graph([(0,1,1), (1,2,1), (0,2,3)])
|
1857
|
+
sage: radius_DHV(G)
|
1858
|
+
1.0
|
1859
|
+
sage: G = graphs.PathGraph(7)
|
1860
|
+
sage: radius_DHV(G) == G.radius(algorithm='Dijkstra_Boost')
|
1861
|
+
True
|
1862
|
+
|
1863
|
+
TESTS::
|
1864
|
+
|
1865
|
+
sage: G = Graph()
|
1866
|
+
sage: radius_DHV(G)
|
1867
|
+
0
|
1868
|
+
sage: G = Graph(1)
|
1869
|
+
sage: radius_DHV(G)
|
1870
|
+
0
|
1871
|
+
sage: G = Graph(2)
|
1872
|
+
sage: radius_DHV(G)
|
1873
|
+
+Infinity
|
1874
|
+
sage: G = Graph([(0, 1, 2)],weighted=True)
|
1875
|
+
sage: radius_DHV(G)
|
1876
|
+
2.0
|
1877
|
+
sage: G = DiGraph(1)
|
1878
|
+
sage: radius_DHV(G)
|
1879
|
+
Traceback (most recent call last):
|
1880
|
+
...
|
1881
|
+
TypeError: this method works for undirected graphs only
|
1882
|
+
"""
|
1883
|
+
if g.is_directed():
|
1884
|
+
raise TypeError("this method works for undirected graphs only")
|
1885
|
+
|
1886
|
+
cdef int n = g.order()
|
1887
|
+
if n <= 1:
|
1888
|
+
return 0
|
1889
|
+
|
1890
|
+
if weight_function and check_weight:
|
1891
|
+
g._check_weight_function(weight_function)
|
1892
|
+
|
1893
|
+
if weight_function is not None:
|
1894
|
+
for e in g.edge_iterator():
|
1895
|
+
if float(weight_function(e)) < 0:
|
1896
|
+
raise ValueError("graphs contains negative weights, use Johnson_Boost instead")
|
1897
|
+
elif g.weighted():
|
1898
|
+
for _, _, w in g.edge_iterator():
|
1899
|
+
if w and float(w) < 0:
|
1900
|
+
raise ValueError("graphs contains negative weights, use Johnson_Boost instead")
|
1901
|
+
|
1902
|
+
# These variables are automatically deleted when the function terminates.
|
1903
|
+
cdef dict v_to_int = {vv: vi for vi, vv in enumerate(g)}
|
1904
|
+
cdef BoostVecWeightedGraph g_boost
|
1905
|
+
boost_weighted_graph_from_sage_graph(&g_boost, g, v_to_int, weight_function)
|
1906
|
+
|
1907
|
+
import sys
|
1908
|
+
cdef v_index source = 0
|
1909
|
+
cdef v_index antipode
|
1910
|
+
cdef v_index v
|
1911
|
+
cdef double ecc_source
|
1912
|
+
cdef double UB = sys.float_info.max
|
1913
|
+
cdef double LB = 0
|
1914
|
+
# For storing distances of all nodes from source
|
1915
|
+
cdef vector[double] distances
|
1916
|
+
# For storing lower bound on eccentricity of nodes
|
1917
|
+
cdef vector[double] ecc_lower_bound
|
1918
|
+
|
1919
|
+
# Initializing
|
1920
|
+
for i in range(n):
|
1921
|
+
ecc_lower_bound.push_back(0)
|
1922
|
+
|
1923
|
+
# Algorithm
|
1924
|
+
while LB < UB:
|
1925
|
+
# 1) pick vertex with minimum eccentricity lower bound
|
1926
|
+
# and compute its eccentricity
|
1927
|
+
sig_on()
|
1928
|
+
distances = g_boost.dijkstra_shortest_paths(source).distances
|
1929
|
+
sig_off()
|
1930
|
+
|
1931
|
+
# Determine the eccentricity of source and its antipode, that is a
|
1932
|
+
# vertex at largest distance from source
|
1933
|
+
ecc_source = 0
|
1934
|
+
for v in range(n):
|
1935
|
+
if ecc_source < distances[v]:
|
1936
|
+
ecc_source = distances[v]
|
1937
|
+
antipode = v
|
1938
|
+
|
1939
|
+
if ecc_source == sys.float_info.max: # Disconnected graph
|
1940
|
+
break
|
1941
|
+
|
1942
|
+
UB = min(UB, ecc_source) # minimum among exact computed eccentricities
|
1943
|
+
if ecc_source == ecc_lower_bound[source]:
|
1944
|
+
# we have found minimum eccentricity vertex and hence the radius
|
1945
|
+
break
|
1946
|
+
|
1947
|
+
# 2) Compute distances from antipode
|
1948
|
+
sig_on()
|
1949
|
+
distances = g_boost.dijkstra_shortest_paths(antipode).distances
|
1950
|
+
sig_off()
|
1951
|
+
|
1952
|
+
# 3) Use distances from antipode to improve eccentricity lower bounds.
|
1953
|
+
# We also determine the next source
|
1954
|
+
LB = sys.float_info.max
|
1955
|
+
for v in range(n):
|
1956
|
+
ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v])
|
1957
|
+
if LB > ecc_lower_bound[v]:
|
1958
|
+
LB = ecc_lower_bound[v]
|
1959
|
+
source = v # vertex with minimum eccentricity lower bound
|
1960
|
+
|
1961
|
+
if UB == sys.float_info.max:
|
1962
|
+
from sage.rings.infinity import Infinity
|
1963
|
+
return +Infinity
|
1964
|
+
|
1965
|
+
return UB
|
1966
|
+
|
1967
|
+
|
1968
|
+
cpdef diameter_DHV(g, weight_function=None, check_weight=True):
|
1969
|
+
r"""
|
1970
|
+
Return the diameter of weighted graph `g`.
|
1971
|
+
|
1972
|
+
This method computes the diameter of undirected graph using the
|
1973
|
+
algorithm proposed in [Dragan2018]_.
|
1974
|
+
|
1975
|
+
This method returns Infinity if graph is not connected.
|
1976
|
+
|
1977
|
+
INPUT:
|
1978
|
+
|
1979
|
+
- ``g`` -- the input Sage graph
|
1980
|
+
|
1981
|
+
- ``weight_function`` -- function (default: ``None``); a function that
|
1982
|
+
associates a weight to each edge. If ``None`` (default), the weights of
|
1983
|
+
``g`` are used, if ``g.weighted()==True``, otherwise all edges have
|
1984
|
+
weight 1.
|
1985
|
+
|
1986
|
+
- ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
|
1987
|
+
that the ``weight_function`` outputs a number for each edge
|
1988
|
+
|
1989
|
+
EXAMPLES::
|
1990
|
+
|
1991
|
+
sage: from sage.graphs.base.boost_graph import diameter_DHV
|
1992
|
+
sage: G = graphs.ButterflyGraph()
|
1993
|
+
sage: diameter_DHV(G)
|
1994
|
+
2.0
|
1995
|
+
|
1996
|
+
TESTS::
|
1997
|
+
|
1998
|
+
sage: G = graphs.RandomBarabasiAlbert(17,6) # needs networkx
|
1999
|
+
sage: diameter_DHV(G) == G.diameter(algorithm = 'Dijkstra_Boost') # needs networkx
|
2000
|
+
True
|
2001
|
+
sage: G = Graph([(0,1,-1)], weighted=True)
|
2002
|
+
sage: diameter_DHV(G)
|
2003
|
+
Traceback (most recent call last):
|
2004
|
+
...
|
2005
|
+
ValueError: graph contains negative edge weights, use Johnson_Boost instead
|
2006
|
+
"""
|
2007
|
+
if g.is_directed():
|
2008
|
+
raise TypeError("this method works for undirected graphs only")
|
2009
|
+
|
2010
|
+
cdef int n = g.order()
|
2011
|
+
if n <= 1:
|
2012
|
+
return 0
|
2013
|
+
|
2014
|
+
if weight_function and check_weight:
|
2015
|
+
g._check_weight_function(weight_function)
|
2016
|
+
|
2017
|
+
if weight_function is not None:
|
2018
|
+
for e in g.edges(sort=False):
|
2019
|
+
if float(weight_function(e)) < 0:
|
2020
|
+
raise ValueError("graph contains negative edge weights, use Johnson_Boost instead")
|
2021
|
+
elif g.weighted():
|
2022
|
+
for _, _, w in g.edges(sort=False):
|
2023
|
+
if w and float(w) < 0:
|
2024
|
+
raise ValueError("graph contains negative edge weights, use Johnson_Boost instead")
|
2025
|
+
|
2026
|
+
# These variables are automatically deleted when the function terminates.
|
2027
|
+
cdef dict v_to_int = {vv: vi for vi, vv in enumerate(g)}
|
2028
|
+
cdef BoostVecWeightedGraph g_boost
|
2029
|
+
boost_weighted_graph_from_sage_graph(&g_boost, g, v_to_int, weight_function)
|
2030
|
+
|
2031
|
+
import sys
|
2032
|
+
cdef v_index u, x, antipode
|
2033
|
+
cdef double ecc_u, ecc_x, ecc_antipode
|
2034
|
+
cdef double LB = 0
|
2035
|
+
cdef double UB = sys.float_info.max
|
2036
|
+
cdef v_index v
|
2037
|
+
cdef double tmp
|
2038
|
+
cdef size_t i, idx
|
2039
|
+
|
2040
|
+
cdef list active = list(range(n))
|
2041
|
+
cdef vector[double] ecc_lower_bound, ecc_upper_bound, distances
|
2042
|
+
|
2043
|
+
for i in range(n):
|
2044
|
+
ecc_lower_bound.push_back(0)
|
2045
|
+
ecc_upper_bound.push_back(sys.float_info.max)
|
2046
|
+
|
2047
|
+
# Algorithm
|
2048
|
+
while LB < UB and active:
|
2049
|
+
# 1. Select vertex u with maximum eccentricity upper bound
|
2050
|
+
tmp = 0
|
2051
|
+
for i, v in enumerate(active):
|
2052
|
+
if ecc_upper_bound[v] > tmp:
|
2053
|
+
tmp = ecc_upper_bound[v]
|
2054
|
+
idx = i
|
2055
|
+
active[idx], active[-1] = active[-1], active[idx]
|
2056
|
+
u = active.pop()
|
2057
|
+
|
2058
|
+
# Compute the distances from u
|
2059
|
+
sig_on()
|
2060
|
+
distances = g_boost.dijkstra_shortest_paths(u).distances
|
2061
|
+
sig_off()
|
2062
|
+
|
2063
|
+
# compute the eccentricity of u and update eccentricity lower bounds
|
2064
|
+
ecc_u = 0
|
2065
|
+
for v in range(n):
|
2066
|
+
ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v])
|
2067
|
+
ecc_u = max(ecc_u, distances[v])
|
2068
|
+
|
2069
|
+
LB = max(LB, ecc_u)
|
2070
|
+
|
2071
|
+
if LB == sys.float_info.max: # Disconnected graph
|
2072
|
+
break
|
2073
|
+
|
2074
|
+
# 2. Select x such that dist(u, x) + ecc[x] == ecc[u].
|
2075
|
+
# Since we don't know ecc[x], we select x with minimum eccentricity
|
2076
|
+
# lower bound. If ecc[x] == ecc_lb[x], we are done. Otherwise, we
|
2077
|
+
# update eccentricity lower bounds and repeat
|
2078
|
+
while active:
|
2079
|
+
# Select v with minimum eccentricity lower bound
|
2080
|
+
tmp = sys.float_info.max
|
2081
|
+
for i, v in enumerate(active):
|
2082
|
+
if ecc_lower_bound[v] < tmp:
|
2083
|
+
tmp = ecc_lower_bound[v]
|
2084
|
+
idx = i
|
2085
|
+
active[idx], active[-1] = active[-1], active[idx]
|
2086
|
+
x = active.pop()
|
2087
|
+
|
2088
|
+
# compute the distances from x
|
2089
|
+
sig_on()
|
2090
|
+
distances = g_boost.dijkstra_shortest_paths(x).distances
|
2091
|
+
sig_off()
|
2092
|
+
|
2093
|
+
# compute the eccentricity of x and its antipode
|
2094
|
+
ecc_x = 0
|
2095
|
+
for v in range(n):
|
2096
|
+
if distances[v] > ecc_x:
|
2097
|
+
ecc_x = distances[v]
|
2098
|
+
antipode = v
|
2099
|
+
LB = max(LB, ecc_x)
|
2100
|
+
|
2101
|
+
if ecc_x == ecc_lower_bound[x]:
|
2102
|
+
# We found the good vertex x
|
2103
|
+
# We update eccentricity upper bounds and break
|
2104
|
+
UB = ecc_x
|
2105
|
+
for v in active:
|
2106
|
+
ecc_upper_bound[v] = min(ecc_upper_bound[v], distances[v] + ecc_x)
|
2107
|
+
UB = max(UB, ecc_upper_bound[v])
|
2108
|
+
break
|
2109
|
+
else:
|
2110
|
+
# x was not a good choice
|
2111
|
+
# We use its antipode to update eccentricity lower bounds.
|
2112
|
+
# Observe that this antipode might have already been seen.
|
2113
|
+
for i, v in enumerate(active):
|
2114
|
+
if v == antipode:
|
2115
|
+
active[i] = active[-1]
|
2116
|
+
active.pop()
|
2117
|
+
break
|
2118
|
+
|
2119
|
+
# compute the distances from antipode
|
2120
|
+
sig_on()
|
2121
|
+
distances = g_boost.dijkstra_shortest_paths(antipode).distances
|
2122
|
+
sig_off()
|
2123
|
+
|
2124
|
+
# compute the eccentricity of antipode and update
|
2125
|
+
# eccentricity lower bounds
|
2126
|
+
ecc_antipode = 0
|
2127
|
+
for v in range(n):
|
2128
|
+
ecc_antipode = max(ecc_antipode, distances[v])
|
2129
|
+
ecc_lower_bound[v] = max(ecc_lower_bound[v], distances[v])
|
2130
|
+
LB = max(LB, ecc_antipode)
|
2131
|
+
|
2132
|
+
if LB == sys.float_info.max:
|
2133
|
+
from sage.rings.infinity import Infinity
|
2134
|
+
return +Infinity
|
2135
|
+
|
2136
|
+
return LB
|
2137
|
+
|
2138
|
+
cdef tuple diameter_lower_bound_2Dsweep(BoostVecWeightedDiGraphU g_boost,
|
2139
|
+
BoostVecWeightedDiGraphU rev_g_boost,
|
2140
|
+
v_index source,
|
2141
|
+
str algorithm):
|
2142
|
+
r"""
|
2143
|
+
Return a lower bound on the diameter of `G`.
|
2144
|
+
|
2145
|
+
This method implements the weighted version of the algorithm proposed
|
2146
|
+
in [Broder2000]_ to compute a lower bound on the diameter of the
|
2147
|
+
weighted digraph `G`.
|
2148
|
+
|
2149
|
+
If the digraph is not strongly connected, the returned value is infinity.
|
2150
|
+
|
2151
|
+
Firstly, this method computes forward distances from `source` and selects a
|
2152
|
+
vertex `vf` at maximum forward distance from `source` (i.e. an
|
2153
|
+
antipode). Then, it computes backward eccentricity of `vf`. Observe that the
|
2154
|
+
backward eccentricity of `vf` is at least the forward eccentricity of
|
2155
|
+
`source`.
|
2156
|
+
|
2157
|
+
Secondly, this method computes backward distances from `source` and selects
|
2158
|
+
a vertex `vb` at maximum backward distance from `source`. Then, it computes
|
2159
|
+
the forward eccentricity of `vb`, which is at least the backward
|
2160
|
+
eccentricity of `source`.
|
2161
|
+
|
2162
|
+
The lower bound on the diameter is the maximum among the backward
|
2163
|
+
eccentricity of `vf` and forward eccentricity of `vb`.
|
2164
|
+
|
2165
|
+
The method returns `(LB, s, m, d)`, where `LB` is best found lower bound on
|
2166
|
+
diameter, `s` is a vertex whose forward / backward eccentricity is `LB`, `d`
|
2167
|
+
is a vertex at a distance `LB` from / to `s` , and `m` is a vertex at
|
2168
|
+
distance `LB/2` from / to both `s` and `d`.
|
2169
|
+
|
2170
|
+
INPUT:
|
2171
|
+
|
2172
|
+
- ``g_boost`` -- a boost weighted digraph
|
2173
|
+
|
2174
|
+
- ``rev_g_boost`` -- a copy of ``g_boost`` with edges reversed
|
2175
|
+
|
2176
|
+
- ``source`` -- starting node for forward and backward distance computation
|
2177
|
+
|
2178
|
+
- ``algorithm`` -- string; algorithm for computing single source shortest
|
2179
|
+
distances. If ``g_boost`` contains negative edge weights then it will be
|
2180
|
+
``Bellman-Ford``, otherwise it will be ``Dijkstra_Boost``.
|
2181
|
+
|
2182
|
+
TESTS::
|
2183
|
+
|
2184
|
+
sage: from sage.graphs.base.boost_graph import diameter
|
2185
|
+
sage: G = DiGraph()
|
2186
|
+
sage: diameter(DiGraph(), algorithm='2Dsweep')
|
2187
|
+
0
|
2188
|
+
sage: diameter(DiGraph(1), algorithm='2Dsweep')
|
2189
|
+
0
|
2190
|
+
sage: diameter(DiGraph(2), algorithm='2Dsweep')
|
2191
|
+
+Infinity
|
2192
|
+
"""
|
2193
|
+
import sys
|
2194
|
+
|
2195
|
+
cdef int n = g_boost.num_verts()
|
2196
|
+
|
2197
|
+
if n <= 1:
|
2198
|
+
return (0, 0, 0, 0)
|
2199
|
+
|
2200
|
+
cdef v_index source_1, source_2, m, s, d, antipode_1, antipode_2, v
|
2201
|
+
cdef double LB_1, LB_2, LB, LB_m
|
2202
|
+
cdef result_distances result_1, result_2
|
2203
|
+
source_1 = source_2 = source
|
2204
|
+
|
2205
|
+
# Algorithm
|
2206
|
+
|
2207
|
+
# 1) Compute forward distances from source_1.
|
2208
|
+
# Get forward eccentricity and antipode (vertex at maximum forward distance)
|
2209
|
+
if algorithm == 'Bellman-Ford':
|
2210
|
+
sig_on()
|
2211
|
+
result_1 = g_boost.bellman_ford_shortest_paths(source_1)
|
2212
|
+
sig_off()
|
2213
|
+
else:
|
2214
|
+
sig_on()
|
2215
|
+
result_1 = g_boost.dijkstra_shortest_paths(source_1)
|
2216
|
+
sig_off()
|
2217
|
+
if not result_1.distances.size():
|
2218
|
+
raise ValueError("the graph contains a negative cycle")
|
2219
|
+
|
2220
|
+
LB_1 = -sys.float_info.max
|
2221
|
+
for v in range(n):
|
2222
|
+
if result_1.distances[v] > LB_1:
|
2223
|
+
LB_1 = result_1.distances[v]
|
2224
|
+
antipode_1 = v
|
2225
|
+
|
2226
|
+
if LB_1 == sys.float_info.max:
|
2227
|
+
return (LB_1, 0, 0, 0)
|
2228
|
+
|
2229
|
+
# 2) Compute backward distances from antipode_1.
|
2230
|
+
source_1, antipode_1 = antipode_1, source_1
|
2231
|
+
if algorithm == 'Bellman-Ford':
|
2232
|
+
sig_on()
|
2233
|
+
result_1 = rev_g_boost.bellman_ford_shortest_paths(source_1)
|
2234
|
+
sig_off()
|
2235
|
+
else:
|
2236
|
+
sig_on()
|
2237
|
+
result_1 = rev_g_boost.dijkstra_shortest_paths(source_1)
|
2238
|
+
sig_off()
|
2239
|
+
if not result_1.distances.size():
|
2240
|
+
raise ValueError("the graph contains a negative cycle")
|
2241
|
+
|
2242
|
+
for v in range(n):
|
2243
|
+
if result_1.distances[v] > LB_1:
|
2244
|
+
LB_1 = result_1.distances[v]
|
2245
|
+
antipode_1 = v
|
2246
|
+
|
2247
|
+
if LB_1 == sys.float_info.max:
|
2248
|
+
return (LB_1, 0, 0, 0)
|
2249
|
+
|
2250
|
+
# 3) Compute backward distances from source_2.
|
2251
|
+
# Get backward eccentricity and antipode.
|
2252
|
+
if algorithm == 'Bellman-Ford':
|
2253
|
+
sig_on()
|
2254
|
+
result_2 = rev_g_boost.bellman_ford_shortest_paths(source_2)
|
2255
|
+
sig_off()
|
2256
|
+
else:
|
2257
|
+
sig_on()
|
2258
|
+
result_2 = rev_g_boost.dijkstra_shortest_paths(source_2)
|
2259
|
+
sig_off()
|
2260
|
+
if not result_2.distances.size():
|
2261
|
+
raise ValueError("the graph contains a negative cycle")
|
2262
|
+
|
2263
|
+
LB_2 = -sys.float_info.max
|
2264
|
+
for v in range(n):
|
2265
|
+
if result_2.distances[v] > LB_2:
|
2266
|
+
LB_2 = result_2.distances[v]
|
2267
|
+
antipode_2 = v
|
2268
|
+
|
2269
|
+
if LB_2 == sys.float_info.max:
|
2270
|
+
return (LB_2, 0, 0, 0)
|
2271
|
+
|
2272
|
+
# 4) Compute forward distances from antipode_2.
|
2273
|
+
source_2, antipode_2 = antipode_2, source_2
|
2274
|
+
if algorithm == 'Bellman-Ford':
|
2275
|
+
sig_on()
|
2276
|
+
result_2 = g_boost.bellman_ford_shortest_paths(source_2)
|
2277
|
+
sig_off()
|
2278
|
+
else:
|
2279
|
+
sig_on()
|
2280
|
+
result_2 = g_boost.dijkstra_shortest_paths(source_2)
|
2281
|
+
sig_off()
|
2282
|
+
if not result_2.distances.size():
|
2283
|
+
raise ValueError("the graph contains a negative cycle")
|
2284
|
+
|
2285
|
+
for v in range(n):
|
2286
|
+
if result_2.distances[v] > LB_2:
|
2287
|
+
LB_2 = result_2.distances[v]
|
2288
|
+
antipode_2 = v
|
2289
|
+
|
2290
|
+
if LB_2 == sys.float_info.max:
|
2291
|
+
return (LB_2, 0, 0, 0)
|
2292
|
+
|
2293
|
+
# 5) Select the best found lower bound as LB with corresponding source s and
|
2294
|
+
# antipode d. Then find a vertex m at a distance LB/2 from/to both s and d.
|
2295
|
+
if LB_1 < LB_2:
|
2296
|
+
LB = LB_2
|
2297
|
+
s = source_2
|
2298
|
+
d = antipode_2
|
2299
|
+
LB_m = LB_2 / 2
|
2300
|
+
m = d
|
2301
|
+
while result_2.distances[m] > LB_m:
|
2302
|
+
m = result_2.predecessors[m]
|
2303
|
+
else:
|
2304
|
+
LB = LB_1
|
2305
|
+
s = source_1
|
2306
|
+
d = antipode_1
|
2307
|
+
LB_m = LB_1 / 2
|
2308
|
+
m = d
|
2309
|
+
while result_1.distances[m] > LB_m:
|
2310
|
+
m = result_1.predecessors[m]
|
2311
|
+
|
2312
|
+
return (LB, s, m, d)
|
2313
|
+
|
2314
|
+
|
2315
|
+
cdef double diameter_DiFUB(BoostVecWeightedDiGraphU g_boost,
|
2316
|
+
BoostVecWeightedDiGraphU rev_g_boost,
|
2317
|
+
v_index source,
|
2318
|
+
str algorithm) except? -1:
|
2319
|
+
r"""
|
2320
|
+
Return the diameter of a weighted directed graph.
|
2321
|
+
|
2322
|
+
The ``DiFUB`` (Directed iterative Fringe Upper Bound) algorithm calculates
|
2323
|
+
the exact value of the diameter of a weighted directed graph [CGLM2012]_.
|
2324
|
+
|
2325
|
+
This algorithm starts from a vertex found through a 2Dsweep call (a directed
|
2326
|
+
version of the 2sweep method). The worst case time complexity of the DiFUB
|
2327
|
+
algorithm is `O(nm)`, but it can be very fast in practice. See the code's
|
2328
|
+
documentation and [CGLM2012]_ for more details.
|
2329
|
+
|
2330
|
+
If the digraph is not strongly connected, the returned value is infinity.
|
2331
|
+
|
2332
|
+
INPUT:
|
2333
|
+
|
2334
|
+
- ``g_boost`` -- a boost weighted digraph
|
2335
|
+
|
2336
|
+
- ``rev_g_boost`` -- a copy of ``g_boost`` with edges reversed
|
2337
|
+
|
2338
|
+
- ``source`` -- starting node for forward and backward distance computation
|
2339
|
+
|
2340
|
+
- ``algorithm`` -- string; algorithm for computing single source shortest
|
2341
|
+
distances. If ``g_boost`` contains negative edge weights then it will be
|
2342
|
+
``Bellman-Ford``, otherwise it will be ``Dijkstra_Boost``.
|
2343
|
+
|
2344
|
+
TESTS::
|
2345
|
+
|
2346
|
+
sage: from sage.graphs.base.boost_graph import diameter
|
2347
|
+
sage: G = DiGraph()
|
2348
|
+
sage: diameter(DiGraph(), algorithm='DiFUB')
|
2349
|
+
0
|
2350
|
+
sage: diameter(DiGraph(1), algorithm='DiFUB')
|
2351
|
+
0
|
2352
|
+
sage: diameter(DiGraph(2), algorithm='DiFUB')
|
2353
|
+
+Infinity
|
2354
|
+
"""
|
2355
|
+
cdef v_index n = g_boost.num_verts()
|
2356
|
+
if n <= 1:
|
2357
|
+
return 0
|
2358
|
+
|
2359
|
+
import sys
|
2360
|
+
# These variables are automatically deleted when the function terminates.
|
2361
|
+
cdef double LB, LB_1, LB_2, UB
|
2362
|
+
cdef v_index m, v, tmp
|
2363
|
+
cdef v_index i
|
2364
|
+
cdef vector[double] distances
|
2365
|
+
cdef vector[pair[double, v_index]] order_1, order_2
|
2366
|
+
|
2367
|
+
# We select a vertex with low eccentricity using 2Dsweep
|
2368
|
+
LB, _, m, _ = diameter_lower_bound_2Dsweep(g_boost, rev_g_boost,
|
2369
|
+
source, algorithm)
|
2370
|
+
|
2371
|
+
# If the lower bound is a very large number, it means that the digraph is
|
2372
|
+
# not strongly connected and so the diameter is infinite.
|
2373
|
+
if LB == sys.float_info.max:
|
2374
|
+
return LB
|
2375
|
+
|
2376
|
+
# Compute Forward distances from `m`.
|
2377
|
+
if algorithm == 'Bellman-Ford':
|
2378
|
+
sig_on()
|
2379
|
+
distances = g_boost.bellman_ford_shortest_paths(m).distances
|
2380
|
+
sig_off()
|
2381
|
+
else:
|
2382
|
+
sig_on()
|
2383
|
+
distances = g_boost.dijkstra_shortest_paths(m).distances
|
2384
|
+
sig_off()
|
2385
|
+
if not distances.size():
|
2386
|
+
raise ValueError("the graph contains a negative cycle")
|
2387
|
+
|
2388
|
+
# Obtain Forward eccentricity of `m` and store pair of
|
2389
|
+
# forward distances, vertex in order_1
|
2390
|
+
LB_1 = sys.float_info.min
|
2391
|
+
for v in range(n):
|
2392
|
+
LB_1 = max(LB_1, distances[v])
|
2393
|
+
order_1.push_back(pair[double, v_index](distances[v], v))
|
2394
|
+
# Compute Backward distances from `m`.
|
2395
|
+
if algorithm == 'Bellman-Ford':
|
2396
|
+
sig_on()
|
2397
|
+
distances = rev_g_boost.bellman_ford_shortest_paths(m).distances
|
2398
|
+
sig_off()
|
2399
|
+
else:
|
2400
|
+
sig_on()
|
2401
|
+
distances = rev_g_boost.dijkstra_shortest_paths(m).distances
|
2402
|
+
sig_off()
|
2403
|
+
if not distances.size():
|
2404
|
+
raise ValueError("the graph contains a negative cycle")
|
2405
|
+
|
2406
|
+
# Obtain Backward eccentricity of `m` and store pair of
|
2407
|
+
# backward distances, vertex in order_2.
|
2408
|
+
LB_2 = sys.float_info.min
|
2409
|
+
for v in range(n):
|
2410
|
+
LB_2 = max(LB_2, distances[v])
|
2411
|
+
order_2.push_back(pair[double, v_index](distances[v], v))
|
2412
|
+
|
2413
|
+
# Now sort order_1 / order_2 in decreasing order of forward / backward
|
2414
|
+
# distances respectively.
|
2415
|
+
# Now order_1 and order_2 will contain order of vertices in which
|
2416
|
+
# further distance computations will be done.
|
2417
|
+
order_1 = sorted(order_1, reverse=True)
|
2418
|
+
order_2 = sorted(order_2, reverse=True)
|
2419
|
+
|
2420
|
+
LB = max(LB, LB_1, LB_2)
|
2421
|
+
if LB == sys.float_info.max:
|
2422
|
+
return LB
|
2423
|
+
|
2424
|
+
# The algorithm:
|
2425
|
+
#
|
2426
|
+
# The diameter of the digraph is equal to the maximum forward or backward
|
2427
|
+
# eccentricity of a vertex. Let `\[db_1, db_2,..., db_i\]` represents the
|
2428
|
+
# different backward distances from `m` containing at least one vertex at
|
2429
|
+
# that distance. Similarly, let `\[df_1, df_2,..., df_i\]` represents the
|
2430
|
+
# different forward distances from `m` containing at least one vertex at
|
2431
|
+
# that distance.
|
2432
|
+
#
|
2433
|
+
# The algorithm is based on the following two observations:
|
2434
|
+
#
|
2435
|
+
# 1). All the nodes `x` at a backward distance greater than `\[db_i\]` from
|
2436
|
+
# `m` having forward eccentricity greater than `\[2db_{i-1}\]` have a
|
2437
|
+
# corresponding node `y` whose backward eccentricity is greater than or
|
2438
|
+
# equal to the forward eccentricity of `x`, at a forward distance greater
|
2439
|
+
# than `\[db_i\]` from `m`.
|
2440
|
+
#
|
2441
|
+
# 2). All the nodes `x` at a forward distance greater than `\[df_i\]` from
|
2442
|
+
# `m` having backward eccentricity greater than `\[2df_{i-1}\]` have a
|
2443
|
+
# corresponding node `y` whose forward eccentricity is greater than or equal
|
2444
|
+
# to the backward eccentricity of `x`, at a backward distance greater than
|
2445
|
+
# `\[df_i\]` from `m`.
|
2446
|
+
#
|
2447
|
+
# Therefore, we calculate backward / forward eccentricity of all nodes at
|
2448
|
+
# forward / backward distance `\[df_i / db_i\]` from `m` respectively. And
|
2449
|
+
# their maximum is `LB`. If `LB` is greater than `2(next maximum forward /
|
2450
|
+
# backward distance)` then we are done, else we proceed further.
|
2451
|
+
|
2452
|
+
i = 0
|
2453
|
+
UB = max(2 * order_1[i].first, 2 * order_2[i].first)
|
2454
|
+
|
2455
|
+
while LB < UB:
|
2456
|
+
v = order_1[i].second
|
2457
|
+
if algorithm == 'Bellman-Ford':
|
2458
|
+
sig_on()
|
2459
|
+
distances = rev_g_boost.bellman_ford_shortest_paths(v).distances
|
2460
|
+
sig_off()
|
2461
|
+
else:
|
2462
|
+
sig_on()
|
2463
|
+
distances = rev_g_boost.dijkstra_shortest_paths(v).distances
|
2464
|
+
sig_off()
|
2465
|
+
if not distances.size():
|
2466
|
+
raise ValueError("the graph contains a negative cycle")
|
2467
|
+
|
2468
|
+
LB_1 = sys.float_info.min
|
2469
|
+
for tmp in range(n):
|
2470
|
+
LB_1 = max(LB_1, distances[tmp])
|
2471
|
+
|
2472
|
+
v = order_2[i].second
|
2473
|
+
if algorithm == 'Bellman-Ford':
|
2474
|
+
sig_on()
|
2475
|
+
distances = g_boost.bellman_ford_shortest_paths(v).distances
|
2476
|
+
sig_off()
|
2477
|
+
else:
|
2478
|
+
sig_on()
|
2479
|
+
distances = g_boost.dijkstra_shortest_paths(v).distances
|
2480
|
+
sig_off()
|
2481
|
+
if not distances.size():
|
2482
|
+
raise ValueError("the graph contains a negative cycle")
|
2483
|
+
|
2484
|
+
LB_2 = sys.float_info.min
|
2485
|
+
for tmp in range(n):
|
2486
|
+
LB_2 = max(LB_2, distances[tmp])
|
2487
|
+
|
2488
|
+
# Update the lower bound
|
2489
|
+
LB = max(LB, LB_1, LB_2)
|
2490
|
+
i += 1
|
2491
|
+
|
2492
|
+
if LB == sys.float_info.max or i == n:
|
2493
|
+
break
|
2494
|
+
|
2495
|
+
# next maximum forward / backward distance
|
2496
|
+
UB = max(2 * order_1[i].first, 2 * order_2[i].first)
|
2497
|
+
|
2498
|
+
# Finally return the computed diameter
|
2499
|
+
return LB
|
2500
|
+
|
2501
|
+
cpdef diameter(G, algorithm=None, source=None,
|
2502
|
+
weight_function=None, check_weight=True):
|
2503
|
+
r"""
|
2504
|
+
Return the diameter of `G`.
|
2505
|
+
|
2506
|
+
This method returns Infinity if the digraph is not strongly connected. It
|
2507
|
+
can also quickly return a lower bound on the diameter using the ``2Dsweep``
|
2508
|
+
scheme.
|
2509
|
+
|
2510
|
+
INPUT:
|
2511
|
+
|
2512
|
+
- ``G`` -- the input sage digraph
|
2513
|
+
|
2514
|
+
- ``algorithm`` -- string (default: ``None``); specifies the algorithm to
|
2515
|
+
use among:
|
2516
|
+
|
2517
|
+
- ``'2Dsweep'`` -- computes lower bound on the diameter of a weighted
|
2518
|
+
directed graph using the weighted version of the algorithm proposed in
|
2519
|
+
[Broder2000]_. See the code's documentation for more details.
|
2520
|
+
|
2521
|
+
- ``'DiFUB'`` -- computes the diameter of a weighted directed graph
|
2522
|
+
using the weighted version of the algorithm proposed in [CGLM2012]_.
|
2523
|
+
See the code's documentation for more details.
|
2524
|
+
|
2525
|
+
- ``source`` -- (default: ``None``) vertex from which to start the
|
2526
|
+
computation. If ``source==None``, an arbitrary vertex of the graph is
|
2527
|
+
chosen. Raise an error if the initial vertex is not in `G`.
|
2528
|
+
|
2529
|
+
- ``weight_function`` -- function (default: ``None``); a function that
|
2530
|
+
associates a weight to each edge. If ``None`` (default), the weights of
|
2531
|
+
``G`` are used, if ``G.weighted()==True``, otherwise all edges have
|
2532
|
+
weight 1.
|
2533
|
+
|
2534
|
+
- ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
|
2535
|
+
that the ``weight_function`` outputs a number for each edge
|
2536
|
+
|
2537
|
+
EXAMPLES::
|
2538
|
+
|
2539
|
+
sage: from sage.graphs.base.boost_graph import diameter
|
2540
|
+
sage: G = DiGraph([(0, 1, 2), (1, 0, -1)])
|
2541
|
+
sage: diameter(G, algorithm='DiFUB')
|
2542
|
+
1.0
|
2543
|
+
sage: diameter(G, algorithm='DiFUB', weight_function=lambda e:e[2])
|
2544
|
+
2.0
|
2545
|
+
sage: G = DiGraph([(0, 1, -1), (1, 0, 2)])
|
2546
|
+
sage: diameter(G, algorithm='DiFUB', weight_function=lambda e:e[2])
|
2547
|
+
2.0
|
2548
|
+
|
2549
|
+
TESTS:
|
2550
|
+
|
2551
|
+
Diameter of weakly connected digraph is Infinity::
|
2552
|
+
|
2553
|
+
sage: G = DiGraph(2)
|
2554
|
+
sage: diameter(G, algorithm='DiFUB')
|
2555
|
+
+Infinity
|
2556
|
+
sage: diameter(G, algorithm='2Dsweep')
|
2557
|
+
+Infinity
|
2558
|
+
|
2559
|
+
DiGraph containing negative cycle::
|
2560
|
+
|
2561
|
+
sage: G = DiGraph([(0,1,-2), (1,0,1)])
|
2562
|
+
sage: diameter(G, algorithm='2Dsweep', weight_function=lambda e:e[2])
|
2563
|
+
Traceback (most recent call last):
|
2564
|
+
...
|
2565
|
+
ValueError: the graph contains a negative cycle
|
2566
|
+
sage: diameter(G, algorithm='DiFUB', weight_function=lambda e:e[2])
|
2567
|
+
Traceback (most recent call last):
|
2568
|
+
...
|
2569
|
+
ValueError: the graph contains a negative cycle
|
2570
|
+
"""
|
2571
|
+
import sys
|
2572
|
+
|
2573
|
+
if not G.is_directed():
|
2574
|
+
raise TypeError("this method works only for digraphs")
|
2575
|
+
|
2576
|
+
cdef int n = G.order()
|
2577
|
+
|
2578
|
+
if n <= 1:
|
2579
|
+
return 0
|
2580
|
+
|
2581
|
+
if weight_function and check_weight:
|
2582
|
+
G._check_weight_function(weight_function)
|
2583
|
+
|
2584
|
+
# Algorithm for single source shortest distance computations.
|
2585
|
+
cdef str algo = 'Dijkstra_Boost'
|
2586
|
+
|
2587
|
+
# If digraph contains negative edge weight then
|
2588
|
+
# algo is set to `Bellman-Ford`
|
2589
|
+
if weight_function is not None:
|
2590
|
+
for e in G.edges(sort=False):
|
2591
|
+
if float(weight_function(e)) < 0:
|
2592
|
+
algo = 'Bellman-Ford'
|
2593
|
+
break
|
2594
|
+
elif G.weighted():
|
2595
|
+
for _, _, w in G.edges(sort=False):
|
2596
|
+
if w and float(w) < 0:
|
2597
|
+
algo = 'Bellman-Ford'
|
2598
|
+
break
|
2599
|
+
|
2600
|
+
if algorithm is None: # default algorithm for diameter computation
|
2601
|
+
algorithm = 'DiFUB'
|
2602
|
+
|
2603
|
+
if algorithm not in ['2Dsweep', 'DiFUB']:
|
2604
|
+
raise ValueError("unknown algorithm for computing the diameter of directed graph")
|
2605
|
+
|
2606
|
+
if source is None:
|
2607
|
+
source = next(G.vertex_iterator())
|
2608
|
+
elif not G.has_vertex(source):
|
2609
|
+
raise ValueError("the specified source is not a vertex of the input Graph")
|
2610
|
+
|
2611
|
+
# These variables are automatically deleted when the function terminates.
|
2612
|
+
cdef dict v_to_int = {vv: vi for vi, vv in enumerate(G)}
|
2613
|
+
|
2614
|
+
# boost copy of G
|
2615
|
+
cdef BoostVecWeightedDiGraphU g_boost
|
2616
|
+
# boost copy of G with edges reversed
|
2617
|
+
cdef BoostVecWeightedDiGraphU rev_g_boost
|
2618
|
+
|
2619
|
+
# Initializing
|
2620
|
+
boost_weighted_graph_from_sage_graph(&g_boost, G, v_to_int, weight_function)
|
2621
|
+
boost_weighted_graph_from_sage_graph(&rev_g_boost, G, v_to_int, weight_function, reverse=True)
|
2622
|
+
|
2623
|
+
cdef v_index isource = 0 if source is None else v_to_int[source]
|
2624
|
+
cdef double LB
|
2625
|
+
|
2626
|
+
if algorithm == '2Dsweep':
|
2627
|
+
LB = diameter_lower_bound_2Dsweep(g_boost, rev_g_boost, isource, algo)[0]
|
2628
|
+
else:
|
2629
|
+
LB = diameter_DiFUB(g_boost, rev_g_boost, isource, algo)
|
2630
|
+
|
2631
|
+
if LB == sys.float_info.max:
|
2632
|
+
from sage.rings.infinity import Infinity
|
2633
|
+
return +Infinity
|
2634
|
+
else:
|
2635
|
+
return LB
|
2636
|
+
|
2637
|
+
cpdef shortest_paths_from_vertices(g, vertex_list=None, order=None,
|
2638
|
+
weight_function=None, algorithm=None):
|
2639
|
+
r"""
|
2640
|
+
Compute the shortest paths to all vertices from each vertex in
|
2641
|
+
``vertex_list``.
|
2642
|
+
|
2643
|
+
The input graph can be weighted: if the algorithm is Dijkstra, no negative
|
2644
|
+
weights are allowed, while if the algorithm is Bellman-Ford, negative
|
2645
|
+
weights are allowed, but there must be no negative cycle (otherwise, the
|
2646
|
+
shortest paths might not exist).
|
2647
|
+
|
2648
|
+
However, Dijkstra algorithm is more efficient: for this reason, we suggest
|
2649
|
+
to use Bellman-Ford only if necessary (which is also the default option).
|
2650
|
+
|
2651
|
+
The running-time for each vertex is `O(n \log n+m)` for Dijkstra algorithm
|
2652
|
+
and `O(mn)` for Bellman-Ford algorithm, where `n` is the number of nodes and
|
2653
|
+
`m` is the number of edges.
|
2654
|
+
|
2655
|
+
INPUT:
|
2656
|
+
|
2657
|
+
- ``g`` -- the input Sage graph
|
2658
|
+
|
2659
|
+
- ``vertex_list`` -- list (default: ``None``); list of vertices to compute
|
2660
|
+
shortest paths from. By default (``None``), compute shortest paths from
|
2661
|
+
all vertices.
|
2662
|
+
|
2663
|
+
- ``order`` -- list (default: ``None``); order of vertices of `g`
|
2664
|
+
|
2665
|
+
- ``weight_function`` -- function (default: ``None``); a function that
|
2666
|
+
associates a weight to each edge. If ``None`` (default), the weights of
|
2667
|
+
``g`` are used, if ``g.weighted()==True``, otherwise all edges have
|
2668
|
+
weight 1.
|
2669
|
+
|
2670
|
+
- ``algorithm`` -- string (default: ``None``); one of the following
|
2671
|
+
algorithms:
|
2672
|
+
|
2673
|
+
- ``'Dijkstra'``, ``'Dijkstra_Boost'`` -- the Dijkstra algorithm
|
2674
|
+
implemented in Boost (works only with positive weights)
|
2675
|
+
|
2676
|
+
- ``'Bellman-Ford'``, ``'Bellman-Ford_Boost'`` -- the Bellman-Ford
|
2677
|
+
algorithm implemented in Boost (works also with negative weights,
|
2678
|
+
if there is no negative cycle)
|
2679
|
+
|
2680
|
+
OUTPUT:
|
2681
|
+
|
2682
|
+
The type of output depends on the input. More precisely -
|
2683
|
+
|
2684
|
+
- A pair of dictionaries of list ``(distances, predecessors)``, when
|
2685
|
+
``order is not None``, such that for each vertex ``v`` in ``vertex_list``,
|
2686
|
+
``distances[v][i]`` store the shortest distance between ``v`` and
|
2687
|
+
``order[i]`` and ``predecessors[v][i]`` store the last vertex in the
|
2688
|
+
shortest path from ``v`` to ``order[i]``.
|
2689
|
+
|
2690
|
+
- A pair of dictionaries of dictionaries ``(distances, predecessors)`` such
|
2691
|
+
that for each vertex ``v`` in ``vertex_list``, ``distances[v]`` store the
|
2692
|
+
shortest distances of all the other vertices from ``v``,
|
2693
|
+
``predecessors[v]`` store the last vertices in the shortest path from
|
2694
|
+
``v`` to all the other vertices.
|
2695
|
+
|
2696
|
+
EXAMPLES:
|
2697
|
+
|
2698
|
+
Undirected graphs::
|
2699
|
+
|
2700
|
+
sage: from sage.graphs.base.boost_graph import shortest_paths_from_vertices
|
2701
|
+
sage: g = Graph([(0,1,1),(1,2,2),(1,3,4),(2,3,1)], weighted=True)
|
2702
|
+
sage: shortest_paths_from_vertices(g,[1,2])
|
2703
|
+
({1: {0: 1.0, 1: 0.0, 2: 2.0, 3: 3.0}, 2: {0: 3.0, 1: 2.0, 2: 0.0, 3: 1.0}},
|
2704
|
+
{1: {0: 1, 1: None, 2: 1, 3: 2}, 2: {0: 1, 1: 2, 2: None, 3: 2}})
|
2705
|
+
|
2706
|
+
Directed graphs::
|
2707
|
+
|
2708
|
+
sage: g = DiGraph([(0,1,1),(1,2,-1),(2,0,2),(2,3,1)], weighted=True)
|
2709
|
+
sage: shortest_paths_from_vertices(g,1)
|
2710
|
+
({1: {0: 1.0, 1: 0.0, 2: -1.0, 3: 0.0}}, {1: {0: 2, 1: None, 2: 1, 3: 2}})
|
2711
|
+
sage: shortest_paths_from_vertices(g, 1, [0,1,2,3])
|
2712
|
+
({1: [1.0, 0.0, -1.0, 0.0]}, {1: [2, None, 1, 2]})
|
2713
|
+
|
2714
|
+
TESTS:
|
2715
|
+
|
2716
|
+
Given an input which is not a graph::
|
2717
|
+
|
2718
|
+
sage: shortest_paths_from_vertices("X-AE A-12", 1)
|
2719
|
+
Traceback (most recent call last):
|
2720
|
+
...
|
2721
|
+
TypeError: the input must be a Sage graph
|
2722
|
+
|
2723
|
+
If there is a negative cycle::
|
2724
|
+
|
2725
|
+
sage: g = DiGraph([(0,1,1),(1,2,-2),(2,0,0.5),(2,3,1)], weighted=True)
|
2726
|
+
sage: shortest_paths_from_vertices(g, 1)
|
2727
|
+
Traceback (most recent call last):
|
2728
|
+
...
|
2729
|
+
ValueError: the graph contains a negative cycle
|
2730
|
+
|
2731
|
+
If the given ordering is not valid::
|
2732
|
+
|
2733
|
+
sage: g = DiGraph([(0,1,1),(1,2,2),(2,0,0.5),(2,3,1)], weighted=True)
|
2734
|
+
sage: shortest_paths_from_vertices(g,1,[0,1])
|
2735
|
+
Traceback (most recent call last):
|
2736
|
+
...
|
2737
|
+
ValueError: Given ordering is not valid
|
2738
|
+
|
2739
|
+
If Dijkstra is used with negative weights::
|
2740
|
+
|
2741
|
+
sage: g = Graph([(0,1,1),(1,2,-2),(1,3,4)], weighted=True)
|
2742
|
+
sage: shortest_paths_from_vertices(g, 1, algorithm='Dijkstra')
|
2743
|
+
Traceback (most recent call last):
|
2744
|
+
...
|
2745
|
+
RuntimeError: Dijkstra algorithm does not work with negative weights, use Bellman-Ford instead
|
2746
|
+
|
2747
|
+
Wrong starting vertex::
|
2748
|
+
|
2749
|
+
sage: shortest_paths_from_vertices(g, 55)
|
2750
|
+
Traceback (most recent call last):
|
2751
|
+
...
|
2752
|
+
ValueError: the starting vertex 55 is not in the graph
|
2753
|
+
"""
|
2754
|
+
import sys
|
2755
|
+
from sage.rings.infinity import Infinity
|
2756
|
+
from sage.graphs.generic_graph import GenericGraph
|
2757
|
+
|
2758
|
+
if not isinstance(g, GenericGraph):
|
2759
|
+
raise TypeError("the input must be a Sage graph")
|
2760
|
+
|
2761
|
+
if vertex_list is None:
|
2762
|
+
vertex_list = g
|
2763
|
+
|
2764
|
+
else:
|
2765
|
+
if not isinstance(vertex_list, list):
|
2766
|
+
vertex_list = [vertex_list]
|
2767
|
+
|
2768
|
+
for vertex in vertex_list:
|
2769
|
+
if vertex not in g:
|
2770
|
+
raise ValueError(f"the starting vertex {vertex} is not in the graph")
|
2771
|
+
|
2772
|
+
if order is not None:
|
2773
|
+
if len(g) == len(order):
|
2774
|
+
for vertex in order:
|
2775
|
+
if vertex not in g:
|
2776
|
+
raise ValueError("Given ordering is not valid")
|
2777
|
+
else:
|
2778
|
+
raise ValueError("Given ordering is not valid")
|
2779
|
+
|
2780
|
+
cdef bint use_Bellman_Ford = algorithm in ['Bellman-Ford', 'Bellman-Ford_Boost']
|
2781
|
+
if not use_Bellman_Ford:
|
2782
|
+
# Check if there are edges with negative weights
|
2783
|
+
if weight_function is not None:
|
2784
|
+
for e in g.edges(sort=False):
|
2785
|
+
if float(weight_function(e)) < 0:
|
2786
|
+
use_Bellman_Ford = True
|
2787
|
+
break
|
2788
|
+
elif g.weighted():
|
2789
|
+
for _, _, wt in g.edges(sort=False):
|
2790
|
+
if float(wt) < 0:
|
2791
|
+
use_Bellman_Ford = True
|
2792
|
+
break
|
2793
|
+
|
2794
|
+
if algorithm in ['Dijkstra', 'Dijkstra_Boost']:
|
2795
|
+
if use_Bellman_Ford:
|
2796
|
+
raise RuntimeError("Dijkstra algorithm does not work with "
|
2797
|
+
"negative weights, use Bellman-Ford instead")
|
2798
|
+
elif algorithm is not None:
|
2799
|
+
raise ValueError(f"unknown algorithm {algorithm!r}")
|
2800
|
+
|
2801
|
+
# These variables are automatically deleted when the function terminates.
|
2802
|
+
cdef v_index vi, v, vert, pred, w
|
2803
|
+
cdef list int_to_v = list(g)
|
2804
|
+
cdef dict v_to_int = {vv: vi for vi, vv in enumerate(g)}
|
2805
|
+
cdef result_distances result
|
2806
|
+
cdef BoostVecWeightedDiGraphU g_boost_dir
|
2807
|
+
cdef BoostVecWeightedGraph g_boost_und
|
2808
|
+
cdef dict dist_v_dict, pred_v_dict
|
2809
|
+
cdef list dist_v_list, pred_v_list
|
2810
|
+
|
2811
|
+
if g.is_directed():
|
2812
|
+
boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
|
2813
|
+
else:
|
2814
|
+
boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
|
2815
|
+
|
2816
|
+
distances = {}
|
2817
|
+
predecessors = {}
|
2818
|
+
|
2819
|
+
for v in vertex_list:
|
2820
|
+
vi = v_to_int[v]
|
2821
|
+
if use_Bellman_Ford:
|
2822
|
+
if g.is_directed():
|
2823
|
+
sig_on()
|
2824
|
+
result = g_boost_dir.bellman_ford_shortest_paths(vi)
|
2825
|
+
sig_off()
|
2826
|
+
else:
|
2827
|
+
sig_on()
|
2828
|
+
result = g_boost_und.bellman_ford_shortest_paths(vi)
|
2829
|
+
sig_off()
|
2830
|
+
if not result.distances.size():
|
2831
|
+
raise ValueError("the graph contains a negative cycle")
|
2832
|
+
else:
|
2833
|
+
if g.is_directed():
|
2834
|
+
sig_on()
|
2835
|
+
result = g_boost_dir.dijkstra_shortest_paths(vi)
|
2836
|
+
sig_off()
|
2837
|
+
else:
|
2838
|
+
sig_on()
|
2839
|
+
result = g_boost_und.dijkstra_shortest_paths(vi)
|
2840
|
+
sig_off()
|
2841
|
+
if not result.distances.size():
|
2842
|
+
# This situation should never happen
|
2843
|
+
raise RuntimeError("something goes wrong. Please report the "
|
2844
|
+
"bug on sage-devel@googlegroups.com")
|
2845
|
+
|
2846
|
+
if order is None:
|
2847
|
+
dist_v_dict = {}
|
2848
|
+
pred_v_dict = {}
|
2849
|
+
|
2850
|
+
for vert in range(g.num_verts()):
|
2851
|
+
if result.distances[vert] != sys.float_info.max:
|
2852
|
+
w = int_to_v[vert]
|
2853
|
+
dist_v_dict[w] = result.distances[vert]
|
2854
|
+
pred = result.predecessors[vert]
|
2855
|
+
if pred == vert:
|
2856
|
+
pred_v_dict[w] = None
|
2857
|
+
else:
|
2858
|
+
pred_v_dict[w] = int_to_v[pred]
|
2859
|
+
|
2860
|
+
distances[v] = dist_v_dict
|
2861
|
+
predecessors[v] = pred_v_dict
|
2862
|
+
else:
|
2863
|
+
dist_v_list = []
|
2864
|
+
pred_v_list = []
|
2865
|
+
|
2866
|
+
for w in order:
|
2867
|
+
vert = v_to_int[w]
|
2868
|
+
if result.distances[vert] != sys.float_info.max:
|
2869
|
+
dist_v_list.append(result.distances[vert])
|
2870
|
+
pred = result.predecessors[vert]
|
2871
|
+
if pred == vert:
|
2872
|
+
pred_v_list.append(None)
|
2873
|
+
else:
|
2874
|
+
pred_v_list.append(int_to_v[pred])
|
2875
|
+
|
2876
|
+
distances[v] = dist_v_list
|
2877
|
+
predecessors[v] = pred_v_list
|
2878
|
+
|
2879
|
+
return distances, predecessors
|
2880
|
+
|
2881
|
+
cpdef wiener_index(g, algorithm=None, weight_function=None, check_weight=True):
|
2882
|
+
r"""
|
2883
|
+
Return the Wiener index of the graph.
|
2884
|
+
|
2885
|
+
The Wiener index of an undirected graph `G` is defined as
|
2886
|
+
`W(G) = \frac{1}{2} \sum_{u,v\in G} d(u,v)` where `d(u,v)` denotes the
|
2887
|
+
distance between vertices `u` and `v` (see [KRG1996]_).
|
2888
|
+
|
2889
|
+
The Wiener index of a directed graph `G` is defined as the sum of the
|
2890
|
+
distances between each pairs of vertices, `W(G) = \sum_{u,v\in G} d(u,v)`.
|
2891
|
+
|
2892
|
+
INPUT:
|
2893
|
+
|
2894
|
+
- ``g`` -- the input Sage graph
|
2895
|
+
|
2896
|
+
- ``algorithm`` -- string (default: ``None``); one of the following
|
2897
|
+
algorithms:
|
2898
|
+
|
2899
|
+
- ``'Dijkstra'``, ``'Dijkstra_Boost'``: the Dijkstra algorithm implemented
|
2900
|
+
in Boost (works only with positive weights)
|
2901
|
+
|
2902
|
+
- ``'Bellman-Ford'``, ``'Bellman-Ford_Boost'``: the Bellman-Ford algorithm
|
2903
|
+
implemented in Boost (works also with negative weights, if there is no
|
2904
|
+
negative cycle)
|
2905
|
+
|
2906
|
+
- ``weight_function`` -- function (default: ``None``); a function that
|
2907
|
+
associates a weight to each edge. If ``None`` (default), the weights of
|
2908
|
+
``g`` are used, if ``g.weighted()==True``, otherwise all edges have
|
2909
|
+
weight 1.
|
2910
|
+
|
2911
|
+
- ``check_weight`` -- boolean (default: ``True``); if ``True``, we check
|
2912
|
+
that the ``weight_function`` outputs a number for each edge
|
2913
|
+
|
2914
|
+
EXAMPLES:
|
2915
|
+
|
2916
|
+
sage: from sage.graphs.base.boost_graph import wiener_index
|
2917
|
+
sage: g = Graph([(0,1,9), (1,2,7), (2,3,4), (3,0,3)])
|
2918
|
+
sage: wiener_index(g)
|
2919
|
+
8.0
|
2920
|
+
sage: g.weighted(True)
|
2921
|
+
sage: wiener_index(g)
|
2922
|
+
41.0
|
2923
|
+
|
2924
|
+
Wiener index of circuit digraphs::
|
2925
|
+
|
2926
|
+
sage: n = 10
|
2927
|
+
sage: g = digraphs.Circuit(n)
|
2928
|
+
sage: w = lambda x: (x*x*(x-1))/2
|
2929
|
+
sage: wiener_index(g) == w(n)
|
2930
|
+
True
|
2931
|
+
|
2932
|
+
Wiener index of a graph of order 1::
|
2933
|
+
|
2934
|
+
sage: wiener_index(Graph(1))
|
2935
|
+
0
|
2936
|
+
|
2937
|
+
The Wiener index is not defined on the empty graph::
|
2938
|
+
|
2939
|
+
sage: wiener_index(Graph())
|
2940
|
+
Traceback (most recent call last):
|
2941
|
+
...
|
2942
|
+
ValueError: Wiener index is not defined for the empty graph
|
2943
|
+
|
2944
|
+
TESTS:
|
2945
|
+
|
2946
|
+
Using ``'Dijkstra'`` on a graph with negative weights::
|
2947
|
+
|
2948
|
+
sage: g = Graph([(0, 1, -1), (1, 2, 1)])
|
2949
|
+
sage: def weight_of(e):
|
2950
|
+
....: return e[2]
|
2951
|
+
sage: wiener_index(g, algorithm='Dijkstra', weight_function=weight_of)
|
2952
|
+
Traceback (most recent call last):
|
2953
|
+
...
|
2954
|
+
RuntimeError: Dijkstra algorithm does not work with negative weights, use Bellman-Ford instead
|
2955
|
+
|
2956
|
+
Directed graph with a negative weight cycle::
|
2957
|
+
|
2958
|
+
sage: g = DiGraph([(0, 1, -1), (1, 2, -1), (2, 0, -1)])
|
2959
|
+
sage: def weight_of(e):
|
2960
|
+
....: return e[2]
|
2961
|
+
sage: wiener_index(g, algorithm='Bellman-Ford', weight_function=weight_of)
|
2962
|
+
Traceback (most recent call last):
|
2963
|
+
...
|
2964
|
+
ValueError: the graph contains a negative cycle
|
2965
|
+
"""
|
2966
|
+
if not g:
|
2967
|
+
raise ValueError("Wiener index is not defined for the empty graph")
|
2968
|
+
|
2969
|
+
cdef unsigned int n = g.order()
|
2970
|
+
if n == 1:
|
2971
|
+
return 0
|
2972
|
+
|
2973
|
+
import sys
|
2974
|
+
|
2975
|
+
if weight_function and check_weight:
|
2976
|
+
g._check_weight_function(weight_function)
|
2977
|
+
|
2978
|
+
cdef bint use_Bellman_Ford = algorithm in ['Bellman-Ford', 'Bellman-Ford_Boost']
|
2979
|
+
if not use_Bellman_Ford:
|
2980
|
+
# Check if there are edges with negative weights
|
2981
|
+
if weight_function is not None:
|
2982
|
+
for e in g.edges(sort=False):
|
2983
|
+
if float(weight_function(e)) < 0:
|
2984
|
+
use_Bellman_Ford = True
|
2985
|
+
break
|
2986
|
+
elif g.weighted():
|
2987
|
+
for _, _, w in g.edges(sort=False):
|
2988
|
+
if float(w) < 0:
|
2989
|
+
use_Bellman_Ford = True
|
2990
|
+
break
|
2991
|
+
|
2992
|
+
if algorithm in ['Dijkstra', 'Dijkstra_Boost']:
|
2993
|
+
if use_Bellman_Ford:
|
2994
|
+
raise RuntimeError("Dijkstra algorithm does not work with "
|
2995
|
+
"negative weights, use Bellman-Ford instead")
|
2996
|
+
elif algorithm is not None:
|
2997
|
+
raise ValueError(f"unknown algorithm {algorithm!r}")
|
2998
|
+
|
2999
|
+
# These variables are automatically deleted when the function terminates.
|
3000
|
+
cdef v_index vi, u, v
|
3001
|
+
cdef dict v_to_int = {vv: vi for vi, vv in enumerate(g)}
|
3002
|
+
cdef BoostVecWeightedDiGraphU g_boost_dir
|
3003
|
+
cdef BoostVecWeightedGraph g_boost_und
|
3004
|
+
cdef vector[double] distances
|
3005
|
+
cdef double s = 0
|
3006
|
+
|
3007
|
+
if g.is_directed():
|
3008
|
+
boost_weighted_graph_from_sage_graph(&g_boost_dir, g, v_to_int, weight_function)
|
3009
|
+
else:
|
3010
|
+
boost_weighted_graph_from_sage_graph(&g_boost_und, g, v_to_int, weight_function)
|
3011
|
+
|
3012
|
+
for u in range(n):
|
3013
|
+
if use_Bellman_Ford:
|
3014
|
+
if g.is_directed():
|
3015
|
+
sig_on()
|
3016
|
+
distances = g_boost_dir.bellman_ford_shortest_paths(u).distances
|
3017
|
+
sig_off()
|
3018
|
+
else:
|
3019
|
+
sig_on()
|
3020
|
+
distances = g_boost_und.bellman_ford_shortest_paths(u).distances
|
3021
|
+
sig_off()
|
3022
|
+
if not distances.size():
|
3023
|
+
raise ValueError("the graph contains a negative cycle")
|
3024
|
+
else:
|
3025
|
+
if g.is_directed():
|
3026
|
+
sig_on()
|
3027
|
+
distances = g_boost_dir.dijkstra_shortest_paths(u).distances
|
3028
|
+
sig_off()
|
3029
|
+
else:
|
3030
|
+
sig_on()
|
3031
|
+
distances = g_boost_und.dijkstra_shortest_paths(u).distances
|
3032
|
+
sig_off()
|
3033
|
+
if not distances.size():
|
3034
|
+
# This situation should never happen
|
3035
|
+
raise RuntimeError("something goes wrong. Please report the "
|
3036
|
+
"bug on sage-devel@googlegroups.com")
|
3037
|
+
|
3038
|
+
for v in range(0 if g.is_directed() else (u + 1), n):
|
3039
|
+
if distances[v] == sys.float_info.max:
|
3040
|
+
from sage.rings.infinity import Infinity
|
3041
|
+
return +Infinity
|
3042
|
+
else:
|
3043
|
+
s += distances[v]
|
3044
|
+
|
3045
|
+
return s
|