scikit-network 0.28.3__cp39-cp39-macosx_12_0_arm64.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.
Potentially problematic release.
This version of scikit-network might be problematic. Click here for more details.
- scikit_network-0.28.3.dist-info/AUTHORS.rst +41 -0
- scikit_network-0.28.3.dist-info/LICENSE +34 -0
- scikit_network-0.28.3.dist-info/METADATA +457 -0
- scikit_network-0.28.3.dist-info/RECORD +240 -0
- scikit_network-0.28.3.dist-info/WHEEL +5 -0
- scikit_network-0.28.3.dist-info/top_level.txt +1 -0
- sknetwork/__init__.py +21 -0
- sknetwork/classification/__init__.py +8 -0
- sknetwork/classification/base.py +84 -0
- sknetwork/classification/base_rank.py +143 -0
- sknetwork/classification/diffusion.py +134 -0
- sknetwork/classification/knn.py +162 -0
- sknetwork/classification/metrics.py +205 -0
- sknetwork/classification/pagerank.py +66 -0
- sknetwork/classification/propagation.py +152 -0
- sknetwork/classification/tests/__init__.py +1 -0
- sknetwork/classification/tests/test_API.py +35 -0
- sknetwork/classification/tests/test_diffusion.py +37 -0
- sknetwork/classification/tests/test_knn.py +24 -0
- sknetwork/classification/tests/test_metrics.py +53 -0
- sknetwork/classification/tests/test_pagerank.py +20 -0
- sknetwork/classification/tests/test_propagation.py +24 -0
- sknetwork/classification/vote.cpython-39-darwin.so +0 -0
- sknetwork/classification/vote.pyx +58 -0
- sknetwork/clustering/__init__.py +7 -0
- sknetwork/clustering/base.py +102 -0
- sknetwork/clustering/kmeans.py +142 -0
- sknetwork/clustering/louvain.py +255 -0
- sknetwork/clustering/louvain_core.cpython-39-darwin.so +0 -0
- sknetwork/clustering/louvain_core.pyx +134 -0
- sknetwork/clustering/metrics.py +91 -0
- sknetwork/clustering/postprocess.py +66 -0
- sknetwork/clustering/propagation_clustering.py +108 -0
- sknetwork/clustering/tests/__init__.py +1 -0
- sknetwork/clustering/tests/test_API.py +37 -0
- sknetwork/clustering/tests/test_kmeans.py +47 -0
- sknetwork/clustering/tests/test_louvain.py +104 -0
- sknetwork/clustering/tests/test_metrics.py +50 -0
- sknetwork/clustering/tests/test_post_processing.py +23 -0
- sknetwork/clustering/tests/test_postprocess.py +39 -0
- sknetwork/data/__init__.py +5 -0
- sknetwork/data/load.py +408 -0
- sknetwork/data/models.py +459 -0
- sknetwork/data/parse.py +621 -0
- sknetwork/data/test_graphs.py +84 -0
- sknetwork/data/tests/__init__.py +1 -0
- sknetwork/data/tests/test_API.py +30 -0
- sknetwork/data/tests/test_load.py +95 -0
- sknetwork/data/tests/test_models.py +52 -0
- sknetwork/data/tests/test_parse.py +253 -0
- sknetwork/data/tests/test_test_graphs.py +30 -0
- sknetwork/data/tests/test_toy_graphs.py +68 -0
- sknetwork/data/toy_graphs.py +619 -0
- sknetwork/embedding/__init__.py +10 -0
- sknetwork/embedding/base.py +90 -0
- sknetwork/embedding/force_atlas.py +197 -0
- sknetwork/embedding/louvain_embedding.py +174 -0
- sknetwork/embedding/louvain_hierarchy.py +142 -0
- sknetwork/embedding/metrics.py +66 -0
- sknetwork/embedding/random_projection.py +133 -0
- sknetwork/embedding/spectral.py +214 -0
- sknetwork/embedding/spring.py +198 -0
- sknetwork/embedding/svd.py +363 -0
- sknetwork/embedding/tests/__init__.py +1 -0
- sknetwork/embedding/tests/test_API.py +73 -0
- sknetwork/embedding/tests/test_force_atlas.py +35 -0
- sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
- sknetwork/embedding/tests/test_louvain_hierarchy.py +19 -0
- sknetwork/embedding/tests/test_metrics.py +29 -0
- sknetwork/embedding/tests/test_random_projection.py +28 -0
- sknetwork/embedding/tests/test_spectral.py +84 -0
- sknetwork/embedding/tests/test_spring.py +50 -0
- sknetwork/embedding/tests/test_svd.py +37 -0
- sknetwork/flow/__init__.py +3 -0
- sknetwork/flow/flow.py +73 -0
- sknetwork/flow/tests/__init__.py +1 -0
- sknetwork/flow/tests/test_flow.py +17 -0
- sknetwork/flow/tests/test_utils.py +69 -0
- sknetwork/flow/utils.py +91 -0
- sknetwork/gnn/__init__.py +10 -0
- sknetwork/gnn/activation.py +117 -0
- sknetwork/gnn/base.py +155 -0
- sknetwork/gnn/base_activation.py +89 -0
- sknetwork/gnn/base_layer.py +109 -0
- sknetwork/gnn/gnn_classifier.py +381 -0
- sknetwork/gnn/layer.py +153 -0
- sknetwork/gnn/layers.py +127 -0
- sknetwork/gnn/loss.py +180 -0
- sknetwork/gnn/neighbor_sampler.py +65 -0
- sknetwork/gnn/optimizer.py +163 -0
- sknetwork/gnn/tests/__init__.py +1 -0
- sknetwork/gnn/tests/test_activation.py +56 -0
- sknetwork/gnn/tests/test_base.py +79 -0
- sknetwork/gnn/tests/test_base_layer.py +37 -0
- sknetwork/gnn/tests/test_gnn_classifier.py +192 -0
- sknetwork/gnn/tests/test_layers.py +80 -0
- sknetwork/gnn/tests/test_loss.py +33 -0
- sknetwork/gnn/tests/test_neigh_sampler.py +23 -0
- sknetwork/gnn/tests/test_optimizer.py +43 -0
- sknetwork/gnn/tests/test_utils.py +93 -0
- sknetwork/gnn/utils.py +219 -0
- sknetwork/hierarchy/__init__.py +7 -0
- sknetwork/hierarchy/base.py +69 -0
- sknetwork/hierarchy/louvain_hierarchy.py +264 -0
- sknetwork/hierarchy/metrics.py +234 -0
- sknetwork/hierarchy/paris.cpython-39-darwin.so +0 -0
- sknetwork/hierarchy/paris.pyx +317 -0
- sknetwork/hierarchy/postprocess.py +350 -0
- sknetwork/hierarchy/tests/__init__.py +1 -0
- sknetwork/hierarchy/tests/test_API.py +25 -0
- sknetwork/hierarchy/tests/test_algos.py +29 -0
- sknetwork/hierarchy/tests/test_metrics.py +62 -0
- sknetwork/hierarchy/tests/test_postprocess.py +57 -0
- sknetwork/hierarchy/tests/test_ward.py +25 -0
- sknetwork/hierarchy/ward.py +94 -0
- sknetwork/linalg/__init__.py +9 -0
- sknetwork/linalg/basics.py +37 -0
- sknetwork/linalg/diteration.cpython-39-darwin.so +0 -0
- sknetwork/linalg/diteration.pyx +49 -0
- sknetwork/linalg/eig_solver.py +93 -0
- sknetwork/linalg/laplacian.py +15 -0
- sknetwork/linalg/normalization.py +66 -0
- sknetwork/linalg/operators.py +225 -0
- sknetwork/linalg/polynome.py +76 -0
- sknetwork/linalg/ppr_solver.py +170 -0
- sknetwork/linalg/push.cpython-39-darwin.so +0 -0
- sknetwork/linalg/push.pyx +73 -0
- sknetwork/linalg/sparse_lowrank.py +142 -0
- sknetwork/linalg/svd_solver.py +91 -0
- sknetwork/linalg/tests/__init__.py +1 -0
- sknetwork/linalg/tests/test_eig.py +44 -0
- sknetwork/linalg/tests/test_laplacian.py +18 -0
- sknetwork/linalg/tests/test_normalization.py +38 -0
- sknetwork/linalg/tests/test_operators.py +70 -0
- sknetwork/linalg/tests/test_polynome.py +38 -0
- sknetwork/linalg/tests/test_ppr.py +50 -0
- sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
- sknetwork/linalg/tests/test_svd.py +38 -0
- sknetwork/linkpred/__init__.py +4 -0
- sknetwork/linkpred/base.py +80 -0
- sknetwork/linkpred/first_order.py +508 -0
- sknetwork/linkpred/first_order_core.cpython-39-darwin.so +0 -0
- sknetwork/linkpred/first_order_core.pyx +315 -0
- sknetwork/linkpred/postprocessing.py +98 -0
- sknetwork/linkpred/tests/__init__.py +1 -0
- sknetwork/linkpred/tests/test_API.py +49 -0
- sknetwork/linkpred/tests/test_postprocessing.py +21 -0
- sknetwork/path/__init__.py +4 -0
- sknetwork/path/metrics.py +148 -0
- sknetwork/path/search.py +65 -0
- sknetwork/path/shortest_path.py +186 -0
- sknetwork/path/tests/__init__.py +1 -0
- sknetwork/path/tests/test_metrics.py +29 -0
- sknetwork/path/tests/test_search.py +25 -0
- sknetwork/path/tests/test_shortest_path.py +45 -0
- sknetwork/ranking/__init__.py +9 -0
- sknetwork/ranking/base.py +56 -0
- sknetwork/ranking/betweenness.cpython-39-darwin.so +0 -0
- sknetwork/ranking/betweenness.pyx +99 -0
- sknetwork/ranking/closeness.py +95 -0
- sknetwork/ranking/harmonic.py +82 -0
- sknetwork/ranking/hits.py +94 -0
- sknetwork/ranking/katz.py +81 -0
- sknetwork/ranking/pagerank.py +107 -0
- sknetwork/ranking/postprocess.py +25 -0
- sknetwork/ranking/tests/__init__.py +1 -0
- sknetwork/ranking/tests/test_API.py +34 -0
- sknetwork/ranking/tests/test_betweenness.py +38 -0
- sknetwork/ranking/tests/test_closeness.py +34 -0
- sknetwork/ranking/tests/test_hits.py +20 -0
- sknetwork/ranking/tests/test_pagerank.py +69 -0
- sknetwork/regression/__init__.py +4 -0
- sknetwork/regression/base.py +56 -0
- sknetwork/regression/diffusion.py +190 -0
- sknetwork/regression/tests/__init__.py +1 -0
- sknetwork/regression/tests/test_API.py +34 -0
- sknetwork/regression/tests/test_diffusion.py +48 -0
- sknetwork/sknetwork.py +3 -0
- sknetwork/topology/__init__.py +9 -0
- sknetwork/topology/dag.py +74 -0
- sknetwork/topology/dag_core.cpython-39-darwin.so +0 -0
- sknetwork/topology/dag_core.pyx +38 -0
- sknetwork/topology/kcliques.cpython-39-darwin.so +0 -0
- sknetwork/topology/kcliques.pyx +193 -0
- sknetwork/topology/kcore.cpython-39-darwin.so +0 -0
- sknetwork/topology/kcore.pyx +120 -0
- sknetwork/topology/structure.py +234 -0
- sknetwork/topology/tests/__init__.py +1 -0
- sknetwork/topology/tests/test_cliques.py +28 -0
- sknetwork/topology/tests/test_cores.py +21 -0
- sknetwork/topology/tests/test_dag.py +26 -0
- sknetwork/topology/tests/test_structure.py +99 -0
- sknetwork/topology/tests/test_triangles.py +42 -0
- sknetwork/topology/tests/test_wl_coloring.py +49 -0
- sknetwork/topology/tests/test_wl_kernel.py +31 -0
- sknetwork/topology/triangles.cpython-39-darwin.so +0 -0
- sknetwork/topology/triangles.pyx +166 -0
- sknetwork/topology/weisfeiler_lehman.py +163 -0
- sknetwork/topology/weisfeiler_lehman_core.cpython-39-darwin.so +0 -0
- sknetwork/topology/weisfeiler_lehman_core.pyx +116 -0
- sknetwork/utils/__init__.py +40 -0
- sknetwork/utils/base.py +35 -0
- sknetwork/utils/check.py +354 -0
- sknetwork/utils/co_neighbor.py +71 -0
- sknetwork/utils/format.py +219 -0
- sknetwork/utils/kmeans.py +89 -0
- sknetwork/utils/knn.py +166 -0
- sknetwork/utils/knn1d.cpython-39-darwin.so +0 -0
- sknetwork/utils/knn1d.pyx +80 -0
- sknetwork/utils/membership.py +82 -0
- sknetwork/utils/minheap.cpython-39-darwin.so +0 -0
- sknetwork/utils/minheap.pxd +22 -0
- sknetwork/utils/minheap.pyx +111 -0
- sknetwork/utils/neighbors.py +115 -0
- sknetwork/utils/seeds.py +75 -0
- sknetwork/utils/simplex.py +140 -0
- sknetwork/utils/tests/__init__.py +1 -0
- sknetwork/utils/tests/test_base.py +28 -0
- sknetwork/utils/tests/test_bunch.py +16 -0
- sknetwork/utils/tests/test_check.py +190 -0
- sknetwork/utils/tests/test_co_neighbor.py +43 -0
- sknetwork/utils/tests/test_format.py +61 -0
- sknetwork/utils/tests/test_kmeans.py +21 -0
- sknetwork/utils/tests/test_knn.py +32 -0
- sknetwork/utils/tests/test_membership.py +24 -0
- sknetwork/utils/tests/test_neighbors.py +41 -0
- sknetwork/utils/tests/test_projection_simplex.py +33 -0
- sknetwork/utils/tests/test_seeds.py +67 -0
- sknetwork/utils/tests/test_verbose.py +15 -0
- sknetwork/utils/tests/test_ward.py +20 -0
- sknetwork/utils/timeout.py +38 -0
- sknetwork/utils/verbose.py +37 -0
- sknetwork/utils/ward.py +60 -0
- sknetwork/visualization/__init__.py +4 -0
- sknetwork/visualization/colors.py +34 -0
- sknetwork/visualization/dendrograms.py +229 -0
- sknetwork/visualization/graphs.py +819 -0
- sknetwork/visualization/tests/__init__.py +1 -0
- sknetwork/visualization/tests/test_dendrograms.py +53 -0
- sknetwork/visualization/tests/test_graphs.py +167 -0
sknetwork/path/search.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created on Jul 24, 2019
|
|
5
|
+
"""
|
|
6
|
+
import numpy as np
|
|
7
|
+
from scipy import sparse
|
|
8
|
+
|
|
9
|
+
from sknetwork.utils.check import is_symmetric
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def breadth_first_search(adjacency: sparse.csr_matrix, source: int, return_predecessors: bool = True):
|
|
13
|
+
"""Breadth-first ordering starting with specified node.
|
|
14
|
+
|
|
15
|
+
Based on SciPy (scipy.sparse.csgraph.breadth_first_order)
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
adjacency :
|
|
20
|
+
The adjacency matrix of the graph
|
|
21
|
+
source : int
|
|
22
|
+
The node from which to start the ordering
|
|
23
|
+
return_predecessors : bool
|
|
24
|
+
If ``True``, the size predecessor matrix is returned
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
node_array : np.ndarray
|
|
29
|
+
The breadth-first list of nodes, starting with specified node. The length of node_array is the number of nodes
|
|
30
|
+
reachable from the specified node.
|
|
31
|
+
predecessors : np.ndarray
|
|
32
|
+
Returned only if ``return_predecessors == True``. The list of predecessors of each node in a breadth-first tree.
|
|
33
|
+
If node ``i`` is in the tree, then its parent is given by ``predecessors[i]``. If node ``i`` is not in the tree
|
|
34
|
+
(and for the parent node) then ``predecessors[i] = -9999``.
|
|
35
|
+
"""
|
|
36
|
+
directed = not is_symmetric(adjacency)
|
|
37
|
+
return sparse.csgraph.breadth_first_order(adjacency, source, directed, return_predecessors)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def depth_first_search(adjacency: sparse.csr_matrix, source: int, return_predecessors: bool = True):
|
|
41
|
+
"""Depth-first ordering starting with specified node.
|
|
42
|
+
|
|
43
|
+
Based on SciPy (scipy.sparse.csgraph.depth_first_order)
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
adjacency :
|
|
48
|
+
The adjacency matrix of the graph
|
|
49
|
+
source :
|
|
50
|
+
The node from which to start the ordering
|
|
51
|
+
return_predecessors:
|
|
52
|
+
If ``True``, the size predecessor matrix is returned
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
node_array : np.ndarray
|
|
57
|
+
The depth-first list of nodes, starting with specified node. The length of node_array is the number of nodes
|
|
58
|
+
reachable from the specified node.
|
|
59
|
+
predecessors : np.ndarray
|
|
60
|
+
Returned only if ``return_predecessors == True``. The list of predecessors of each node in a depth-first tree.
|
|
61
|
+
If node ``i`` is in the tree, then its parent is given by ``predecessors[i]``. If node ``i`` is not in the tree
|
|
62
|
+
(and for the parent node) then ``predecessors[i] = -9999``.
|
|
63
|
+
"""
|
|
64
|
+
directed = not is_symmetric(adjacency)
|
|
65
|
+
return sparse.csgraph.depth_first_order(adjacency, source, directed, return_predecessors)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created on November 12, 2019
|
|
5
|
+
@author: Quentin Lutz <qlutz@enst.fr>
|
|
6
|
+
"""
|
|
7
|
+
from functools import partial
|
|
8
|
+
from multiprocessing import Pool
|
|
9
|
+
from typing import Optional, Union, Iterable
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from scipy import sparse
|
|
13
|
+
|
|
14
|
+
from sknetwork.utils.check import check_n_jobs, is_symmetric
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_distances(adjacency: sparse.csr_matrix, sources: Optional[Union[int, Iterable]] = None, method: str = 'D',
|
|
18
|
+
return_predecessors: bool = False, unweighted: bool = False, n_jobs: Optional[int] = None):
|
|
19
|
+
"""Compute distances between nodes.
|
|
20
|
+
|
|
21
|
+
* Graphs
|
|
22
|
+
* Digraphs
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
Based on SciPy (scipy.sparse.csgraph.shortest_path)
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
adjacency :
|
|
30
|
+
The adjacency matrix of the graph
|
|
31
|
+
sources :
|
|
32
|
+
If specified, only compute the paths for the points at the given indices. Will not work with ``method =='FW'``.
|
|
33
|
+
method :
|
|
34
|
+
The method to be used.
|
|
35
|
+
|
|
36
|
+
* ``'D'`` (Dijkstra),
|
|
37
|
+
* ``'BF'`` (Bellman-Ford),
|
|
38
|
+
* ``'J'`` (Johnson).
|
|
39
|
+
return_predecessors :
|
|
40
|
+
If ``True``, the size predecessor matrix is returned
|
|
41
|
+
unweighted :
|
|
42
|
+
If ``True``, the weights of the edges are ignored
|
|
43
|
+
n_jobs :
|
|
44
|
+
If an integer value is given, denotes the number of workers to use (-1 means the maximum number will be used).
|
|
45
|
+
If ``None``, no parallel computations are made.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
dist_matrix : np.ndarray
|
|
50
|
+
Matrix of distances between nodes. ``dist_matrix[i,j]`` gives the shortest
|
|
51
|
+
distance from the ``i``-th source to node ``j`` in the graph (infinite if no path exists
|
|
52
|
+
from the ``i``-th source to node ``j``).
|
|
53
|
+
predecessors : np.ndarray, optional
|
|
54
|
+
Returned only if ``return_predecessors == True``. The matrix of predecessors, which can be used to reconstruct
|
|
55
|
+
the shortest paths. Row ``i`` of the predecessor matrix contains information on the shortest paths from the
|
|
56
|
+
``i``-th source: each entry ``predecessors[i, j]`` gives the index of the previous node in the path from
|
|
57
|
+
the ``i``-th source to node ``j`` (-1 if no path exists from the ``i``-th source to node ``j``).
|
|
58
|
+
|
|
59
|
+
Examples
|
|
60
|
+
--------
|
|
61
|
+
>>> from sknetwork.data import cyclic_digraph
|
|
62
|
+
>>> adjacency = cyclic_digraph(3)
|
|
63
|
+
>>> get_distances(adjacency, sources=0)
|
|
64
|
+
array([0., 1., 2.])
|
|
65
|
+
>>> get_distances(adjacency, sources=0, return_predecessors=True)
|
|
66
|
+
(array([0., 1., 2.]), array([-1, 0, 1]))
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
n_jobs = check_n_jobs(n_jobs)
|
|
70
|
+
if method == 'FW' and n_jobs != 1:
|
|
71
|
+
raise ValueError('The Floyd-Warshall algorithm cannot be used with parallel computations.')
|
|
72
|
+
if sources is None:
|
|
73
|
+
sources = np.arange(adjacency.shape[0])
|
|
74
|
+
elif np.issubdtype(type(sources), np.integer):
|
|
75
|
+
sources = np.array([sources])
|
|
76
|
+
n = len(sources)
|
|
77
|
+
directed = not is_symmetric(adjacency)
|
|
78
|
+
local_function = partial(sparse.csgraph.shortest_path,
|
|
79
|
+
adjacency, method, directed, return_predecessors, unweighted, False)
|
|
80
|
+
if n_jobs == 1 or n == 1:
|
|
81
|
+
try:
|
|
82
|
+
res = sparse.csgraph.shortest_path(adjacency, method, directed, return_predecessors,
|
|
83
|
+
unweighted, False, sources)
|
|
84
|
+
except sparse.csgraph.NegativeCycleError:
|
|
85
|
+
raise ValueError("The shortest path computation could not be completed because a negative cycle is present.")
|
|
86
|
+
else:
|
|
87
|
+
try:
|
|
88
|
+
with Pool(n_jobs) as pool:
|
|
89
|
+
res = np.array(pool.map(local_function, sources))
|
|
90
|
+
except sparse.csgraph.NegativeCycleError:
|
|
91
|
+
pool.terminate()
|
|
92
|
+
raise ValueError("The shortest path computation could not be completed because a negative cycle is present.")
|
|
93
|
+
if return_predecessors:
|
|
94
|
+
res[1][res[1] < 0] = -1
|
|
95
|
+
if n == 1:
|
|
96
|
+
return res[0].ravel(), res[1].astype(int).ravel()
|
|
97
|
+
else:
|
|
98
|
+
return res[0], res[1].astype(int)
|
|
99
|
+
else:
|
|
100
|
+
if n == 1:
|
|
101
|
+
return res.ravel()
|
|
102
|
+
else:
|
|
103
|
+
return res
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_shortest_path(adjacency: sparse.csr_matrix, sources: Union[int, Iterable], targets: Union[int, Iterable],
|
|
107
|
+
method: str = 'D', unweighted: bool = False, n_jobs: Optional[int] = None):
|
|
108
|
+
"""Compute the shortest paths in the graph.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
adjacency :
|
|
113
|
+
The adjacency matrix of the graph
|
|
114
|
+
sources : int or iterable
|
|
115
|
+
Sources nodes.
|
|
116
|
+
targets : int or iterable
|
|
117
|
+
Target nodes.
|
|
118
|
+
method :
|
|
119
|
+
The method to be used.
|
|
120
|
+
|
|
121
|
+
* ``'D'`` (Dijkstra),
|
|
122
|
+
* ``'BF'`` (Bellman-Ford),
|
|
123
|
+
* ``'J'`` (Johnson).
|
|
124
|
+
unweighted :
|
|
125
|
+
If ``True``, the weights of the edges are ignored
|
|
126
|
+
n_jobs :
|
|
127
|
+
If an integer value is given, denotes the number of workers to use (-1 means the maximum number will be used).
|
|
128
|
+
If ``None``, no parallel computations are made.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
paths : list
|
|
133
|
+
If single source and single target, return a list containing the nodes on the path from source to target.
|
|
134
|
+
If multiple sources or multiple targets, return a list of paths as lists.
|
|
135
|
+
An empty list means that the path does not exist.
|
|
136
|
+
|
|
137
|
+
Examples
|
|
138
|
+
--------
|
|
139
|
+
>>> from sknetwork.data import linear_digraph
|
|
140
|
+
>>> adjacency = linear_digraph(3)
|
|
141
|
+
>>> get_shortest_path(adjacency, 0, 2)
|
|
142
|
+
[0, 1, 2]
|
|
143
|
+
>>> get_shortest_path(adjacency, 2, 0)
|
|
144
|
+
[]
|
|
145
|
+
>>> get_shortest_path(adjacency, 0, [1, 2])
|
|
146
|
+
[[0, 1], [0, 1, 2]]
|
|
147
|
+
>>> get_shortest_path(adjacency, [0, 1], 2)
|
|
148
|
+
[[0, 1, 2], [1, 2]]
|
|
149
|
+
"""
|
|
150
|
+
if np.issubdtype(type(sources), np.integer):
|
|
151
|
+
sources = [sources]
|
|
152
|
+
if np.issubdtype(type(targets), np.integer):
|
|
153
|
+
targets = [targets]
|
|
154
|
+
|
|
155
|
+
if len(sources) == 1:
|
|
156
|
+
source2target = True
|
|
157
|
+
source = sources[0]
|
|
158
|
+
elif len(targets) == 1:
|
|
159
|
+
source2target = False
|
|
160
|
+
source = targets[0]
|
|
161
|
+
targets = sources
|
|
162
|
+
else:
|
|
163
|
+
raise ValueError(
|
|
164
|
+
'This request is ambiguous. Either use one source and multiple targets or multiple sources and one target.')
|
|
165
|
+
|
|
166
|
+
if source2target:
|
|
167
|
+
dists, preds = get_distances(adjacency, source, method, True, unweighted, n_jobs)
|
|
168
|
+
else:
|
|
169
|
+
dists, preds = get_distances(adjacency.T, source, method, True, unweighted, n_jobs)
|
|
170
|
+
|
|
171
|
+
paths = []
|
|
172
|
+
for target in targets:
|
|
173
|
+
if dists[target] == np.inf:
|
|
174
|
+
path = []
|
|
175
|
+
else:
|
|
176
|
+
path = [target]
|
|
177
|
+
node = target
|
|
178
|
+
while node != source:
|
|
179
|
+
node = preds[node]
|
|
180
|
+
path.append(node)
|
|
181
|
+
if source2target:
|
|
182
|
+
path.reverse()
|
|
183
|
+
paths.append(path)
|
|
184
|
+
if len(paths) == 1:
|
|
185
|
+
paths = paths[0]
|
|
186
|
+
return paths
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""tests for path module"""
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
""""tests for metrics.py"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sknetwork.data import house
|
|
7
|
+
from sknetwork.path import get_diameter, get_eccentricity, get_radius
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestMetrics(unittest.TestCase):
|
|
12
|
+
|
|
13
|
+
def test_diameter_1(self):
|
|
14
|
+
adjacency = house()
|
|
15
|
+
with self.assertRaises(ValueError):
|
|
16
|
+
get_diameter(adjacency, 2.5)
|
|
17
|
+
def test_diameter_2(self):
|
|
18
|
+
adjacency = house()
|
|
19
|
+
self.assertEqual(get_diameter(adjacency), 2)
|
|
20
|
+
def test_eccentricity_1(self):
|
|
21
|
+
adjacency = house()
|
|
22
|
+
self.assertEqual(get_eccentricity(adjacency, 1), 2)
|
|
23
|
+
def test_radius_1(self):
|
|
24
|
+
adjacency = house()
|
|
25
|
+
self.assertEqual(get_radius(adjacency), 2)
|
|
26
|
+
def test_radius_2(self):
|
|
27
|
+
adjacency = house()
|
|
28
|
+
self.assertEqual(get_radius(adjacency,[0,1]), 2)
|
|
29
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
""""tests for search.py"""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from sknetwork.data import cyclic_digraph
|
|
10
|
+
from sknetwork.path import breadth_first_search, depth_first_search
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestSearch(unittest.TestCase):
|
|
14
|
+
|
|
15
|
+
def setUp(self) -> None:
|
|
16
|
+
"""Load graph for tests."""
|
|
17
|
+
self.adjacency = cyclic_digraph(3).astype(bool)
|
|
18
|
+
|
|
19
|
+
def test_bfs(self):
|
|
20
|
+
self.assertTrue((breadth_first_search(
|
|
21
|
+
self.adjacency, 0, return_predecessors=False) == np.array([0, 1, 2])).all())
|
|
22
|
+
|
|
23
|
+
def test_dfs(self):
|
|
24
|
+
self.assertTrue((depth_first_search(
|
|
25
|
+
self.adjacency, 0, return_predecessors=False) == np.array([0, 1, 2])).all())
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
""""tests for shortest_path.py"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sknetwork.data import karate_club, cyclic_digraph
|
|
7
|
+
from sknetwork.path.shortest_path import get_distances, get_shortest_path
|
|
8
|
+
import numpy as np
|
|
9
|
+
from scipy.sparse import csr_matrix
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestShortestPath(unittest.TestCase):
|
|
13
|
+
|
|
14
|
+
def test_parallel(self):
|
|
15
|
+
adjacency = karate_club()
|
|
16
|
+
dist1 = get_distances(adjacency)
|
|
17
|
+
dist2 = get_distances(adjacency, n_jobs=-1)
|
|
18
|
+
self.assertTrue((dist1 == dist2).all())
|
|
19
|
+
|
|
20
|
+
def test_predecessors(self):
|
|
21
|
+
adjacency = karate_club()
|
|
22
|
+
_, predecessors = get_distances(adjacency, return_predecessors=True)
|
|
23
|
+
self.assertTupleEqual(predecessors.shape, adjacency.shape)
|
|
24
|
+
|
|
25
|
+
def test_shortest_paths(self):
|
|
26
|
+
with self.assertRaises(ValueError):
|
|
27
|
+
get_shortest_path(cyclic_digraph(3), [0, 1], [0, 1])
|
|
28
|
+
|
|
29
|
+
def test_error_on_parallel_FW(self):
|
|
30
|
+
adjacency = karate_club()
|
|
31
|
+
self.assertRaises(ValueError, get_distances, adjacency, n_jobs=2, method='FW')
|
|
32
|
+
|
|
33
|
+
def test_error_neg_cycle_parallel(self):
|
|
34
|
+
adj = np.ones((5, 5), dtype=int)
|
|
35
|
+
adj[:][:] = -1
|
|
36
|
+
adj = csr_matrix(adj)
|
|
37
|
+
with self.assertRaises(ValueError):
|
|
38
|
+
get_distances(adj, method='BF', n_jobs=2)
|
|
39
|
+
|
|
40
|
+
def test_error_neg_cycle(self):
|
|
41
|
+
adj = np.ones((5, 5), dtype=int)
|
|
42
|
+
adj[:][:] = -1
|
|
43
|
+
adj = csr_matrix(adj)
|
|
44
|
+
with self.assertRaises(ValueError):
|
|
45
|
+
get_distances(adj, method='BF', n_jobs=1)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""ranking module"""
|
|
2
|
+
from sknetwork.ranking.base import BaseRanking
|
|
3
|
+
from sknetwork.ranking.betweenness import Betweenness
|
|
4
|
+
from sknetwork.ranking.closeness import Closeness
|
|
5
|
+
from sknetwork.ranking.harmonic import Harmonic
|
|
6
|
+
from sknetwork.ranking.hits import HITS
|
|
7
|
+
from sknetwork.ranking.katz import Katz
|
|
8
|
+
from sknetwork.ranking.pagerank import PageRank
|
|
9
|
+
from sknetwork.ranking.postprocess import top_k
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created on November 2019
|
|
5
|
+
@author: Nathan de Lara <nathan.delara@polytechnique.org>
|
|
6
|
+
"""
|
|
7
|
+
from abc import ABC
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from sknetwork.utils.base import Algorithm
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseRanking(Algorithm, ABC):
|
|
15
|
+
"""Base class for ranking algorithms.
|
|
16
|
+
|
|
17
|
+
Attributes
|
|
18
|
+
----------
|
|
19
|
+
scores_ : np.ndarray
|
|
20
|
+
Score of each node.
|
|
21
|
+
scores_row_: np.ndarray
|
|
22
|
+
Scores of rows, for bipartite graphs.
|
|
23
|
+
scores_col_: np.ndarray
|
|
24
|
+
Scores of columns, for bipartite graphs.
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self.scores_ = None
|
|
28
|
+
|
|
29
|
+
def fit_predict(self, *args, **kwargs) -> np.ndarray:
|
|
30
|
+
"""Fit algorithm to data and return the scores. Same parameters as the ``fit`` method.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
scores : np.ndarray
|
|
35
|
+
Scores.
|
|
36
|
+
"""
|
|
37
|
+
self.fit(*args, **kwargs)
|
|
38
|
+
return self.scores_
|
|
39
|
+
|
|
40
|
+
def fit_transform(self, *args, **kwargs) -> np.ndarray:
|
|
41
|
+
"""Fit algorithm to data and return the scores. Alias for ``fit_predict``.
|
|
42
|
+
Same parameters as the ``fit`` method.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
scores : np.ndarray
|
|
47
|
+
Scores.
|
|
48
|
+
"""
|
|
49
|
+
self.fit(*args, **kwargs)
|
|
50
|
+
return self.scores_
|
|
51
|
+
|
|
52
|
+
def _split_vars(self, shape):
|
|
53
|
+
n_row = shape[0]
|
|
54
|
+
self.scores_row_ = self.scores_[:n_row]
|
|
55
|
+
self.scores_col_ = self.scores_[n_row:]
|
|
56
|
+
self.scores_ = self.scores_row_
|
|
Binary file
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# distutils: language = c++
|
|
2
|
+
# cython: language_level=3
|
|
3
|
+
# cython: linetrace=True
|
|
4
|
+
# distutils: define_macros=CYTHON_TRACE_NOGIL=1
|
|
5
|
+
"""
|
|
6
|
+
Created on September 17 2020
|
|
7
|
+
@author: Tiphaine Viard <tiphaine.viard@telecom-paris.fr>
|
|
8
|
+
"""
|
|
9
|
+
from typing import Union
|
|
10
|
+
import numpy as np
|
|
11
|
+
from scipy import sparse
|
|
12
|
+
|
|
13
|
+
from sknetwork.ranking.base import BaseRanking
|
|
14
|
+
from sknetwork.utils.check import check_format, check_square, check_connected
|
|
15
|
+
|
|
16
|
+
from libcpp.vector cimport vector
|
|
17
|
+
from libcpp.queue cimport queue
|
|
18
|
+
|
|
19
|
+
class Betweenness(BaseRanking):
|
|
20
|
+
""" Betweenness centrality, based on Brandes' algorithm.
|
|
21
|
+
|
|
22
|
+
Attributes
|
|
23
|
+
----------
|
|
24
|
+
scores_ : np.ndarray
|
|
25
|
+
Betweenness centrality value of each node
|
|
26
|
+
|
|
27
|
+
Example
|
|
28
|
+
-------
|
|
29
|
+
>>> from sknetwork.ranking import Betweenness
|
|
30
|
+
>>> from sknetwork.data.toy_graphs import bow_tie
|
|
31
|
+
>>> betweenness = Betweenness()
|
|
32
|
+
>>> adjacency = bow_tie()
|
|
33
|
+
>>> scores = betweenness.fit_transform(adjacency)
|
|
34
|
+
>>> scores
|
|
35
|
+
array([4., 0., 0., 0., 0.])
|
|
36
|
+
|
|
37
|
+
References
|
|
38
|
+
----------
|
|
39
|
+
Brandes, Ulrik (2001). A faster algorithm for betweenness centrality. Journal of Mathematical Sociology.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, normalized: bool = False):
|
|
43
|
+
super(Betweenness, self).__init__()
|
|
44
|
+
self.normalized_ = normalized
|
|
45
|
+
|
|
46
|
+
def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray]) -> 'Betweenness':
|
|
47
|
+
adjacency = check_format(adjacency)
|
|
48
|
+
check_square(adjacency)
|
|
49
|
+
check_connected(adjacency)
|
|
50
|
+
|
|
51
|
+
cdef int source
|
|
52
|
+
cdef vector[ vector[int] ] preds
|
|
53
|
+
cdef vector[int] sigma
|
|
54
|
+
cdef vector[int] dists
|
|
55
|
+
cdef int i
|
|
56
|
+
cdef int j
|
|
57
|
+
cdef vector[float] delta
|
|
58
|
+
|
|
59
|
+
cdef int n = adjacency.shape[0]
|
|
60
|
+
self.scores_ = np.zeros(n)
|
|
61
|
+
cdef vector[int] seen # Using list as stack
|
|
62
|
+
cdef queue[int] bfs_queue
|
|
63
|
+
|
|
64
|
+
for source in range(n):
|
|
65
|
+
preds = [[] for _ in range(n)]
|
|
66
|
+
sigma = np.zeros(n)
|
|
67
|
+
sigma[source] = 1
|
|
68
|
+
dists = -np.ones(n, dtype=int)
|
|
69
|
+
dists[source] = 0
|
|
70
|
+
bfs_queue.push(source)
|
|
71
|
+
|
|
72
|
+
while bfs_queue.size() != 0:
|
|
73
|
+
i = bfs_queue.front()
|
|
74
|
+
bfs_queue.pop()
|
|
75
|
+
|
|
76
|
+
seen.push_back(i)
|
|
77
|
+
neighbors = adjacency.indices[adjacency.indptr[i]:adjacency.indptr[i + 1]]
|
|
78
|
+
for j in neighbors:
|
|
79
|
+
if dists[j] < 0: # j found for the first time?
|
|
80
|
+
dists[j] = dists[i] + 1
|
|
81
|
+
bfs_queue.push(j)
|
|
82
|
+
if dists[j] == dists[i] + 1: # shortest path to j via i?
|
|
83
|
+
sigma[j] += sigma[i]
|
|
84
|
+
preds[j].push_back(i)
|
|
85
|
+
|
|
86
|
+
# Now backtrack to compute betweenness scores
|
|
87
|
+
delta = np.zeros(n)
|
|
88
|
+
while len(seen) != 0:
|
|
89
|
+
j = seen.back()
|
|
90
|
+
seen.pop_back()
|
|
91
|
+
for i in preds[j]:
|
|
92
|
+
delta[i] += sigma[i] / sigma[j] * (1 + delta[j])
|
|
93
|
+
if j != source:
|
|
94
|
+
self.scores_[j] += delta[j]
|
|
95
|
+
|
|
96
|
+
# Undirected graph, divide all values by two
|
|
97
|
+
self.scores_ = 1 / 2 * self.scores_
|
|
98
|
+
|
|
99
|
+
return self
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created on November 12 2019
|
|
5
|
+
@author: Quentin Lutz <qlutz@enst.fr>
|
|
6
|
+
"""
|
|
7
|
+
from math import log
|
|
8
|
+
from typing import Union, Optional
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
from scipy import sparse
|
|
12
|
+
|
|
13
|
+
from sknetwork.path.shortest_path import get_distances
|
|
14
|
+
from sknetwork.ranking.base import BaseRanking
|
|
15
|
+
from sknetwork.utils.check import check_format, check_square, check_connected
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Closeness(BaseRanking):
|
|
19
|
+
"""Closeness centrality of each node in a connected graph, corresponding to the average length of the
|
|
20
|
+
shortest paths from that node to all the other ones.
|
|
21
|
+
|
|
22
|
+
For a directed graph, the closeness centrality is computed in terms of outgoing paths.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
method :
|
|
27
|
+
Denotes if the results should be exact or approximate.
|
|
28
|
+
tol: float
|
|
29
|
+
If ``method=='approximate'``, the allowed tolerance on each score entry.
|
|
30
|
+
n_jobs:
|
|
31
|
+
If an integer value is given, denotes the number of workers to use (-1 means the maximum number will be used).
|
|
32
|
+
If ``None``, no parallel computations are made.
|
|
33
|
+
|
|
34
|
+
Attributes
|
|
35
|
+
----------
|
|
36
|
+
scores_ : np.ndarray
|
|
37
|
+
Closeness centrality of each node.
|
|
38
|
+
|
|
39
|
+
Example
|
|
40
|
+
-------
|
|
41
|
+
>>> from sknetwork.ranking import Closeness
|
|
42
|
+
>>> from sknetwork.data import cyclic_digraph
|
|
43
|
+
>>> closeness = Closeness()
|
|
44
|
+
>>> adjacency = cyclic_digraph(3)
|
|
45
|
+
>>> scores = closeness.fit_predict(adjacency)
|
|
46
|
+
>>> np.round(scores, 2)
|
|
47
|
+
array([0.67, 0.67, 0.67])
|
|
48
|
+
|
|
49
|
+
References
|
|
50
|
+
----------
|
|
51
|
+
Eppstein, D., & Wang, J. (2001, January).
|
|
52
|
+
`Fast approximation of centrality.
|
|
53
|
+
<http://jgaa.info/accepted/2004/EppsteinWang2004.8.1.pdf>`_
|
|
54
|
+
In Proceedings of the twelfth annual ACM-SIAM symposium on Discrete algorithms (pp. 228-229).
|
|
55
|
+
Society for Industrial and Applied Mathematics.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, method: str = 'exact', tol: float = 1e-1, n_jobs: Optional[int] = None):
|
|
59
|
+
super(Closeness, self).__init__()
|
|
60
|
+
|
|
61
|
+
self.method = method
|
|
62
|
+
self.tol = tol
|
|
63
|
+
self.n_jobs = n_jobs
|
|
64
|
+
|
|
65
|
+
def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray]) -> 'Closeness':
|
|
66
|
+
"""Closeness centrality for connected graphs.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
adjacency :
|
|
71
|
+
Adjacency matrix of the graph.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
self: :class:`Closeness`
|
|
76
|
+
"""
|
|
77
|
+
adjacency = check_format(adjacency)
|
|
78
|
+
check_square(adjacency)
|
|
79
|
+
check_connected(adjacency)
|
|
80
|
+
n = adjacency.shape[0]
|
|
81
|
+
|
|
82
|
+
if self.method == 'exact':
|
|
83
|
+
n_sources = n
|
|
84
|
+
sources = np.arange(n)
|
|
85
|
+
elif self.method == 'approximate':
|
|
86
|
+
n_sources = min(int(log(n) / self.tol ** 2), n)
|
|
87
|
+
sources = np.random.choice(np.arange(n), n_sources, replace=False)
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError("Method should be either 'exact' or 'approximate'.")
|
|
90
|
+
|
|
91
|
+
dists = get_distances(adjacency, n_jobs=self.n_jobs, sources=sources)
|
|
92
|
+
|
|
93
|
+
self.scores_ = ((n - 1) * n_sources / n) / dists.T.dot(np.ones(n_sources))
|
|
94
|
+
|
|
95
|
+
return self
|