scikit-network 0.30.0__cp310-cp310-win_amd64.whl → 0.32.1__cp310-cp310-win_amd64.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.30.0.dist-info → scikit_network-0.32.1.dist-info}/AUTHORS.rst +3 -0
- {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/METADATA +31 -3
- scikit_network-0.32.1.dist-info/RECORD +228 -0
- {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/WHEEL +1 -1
- sknetwork/__init__.py +1 -1
- sknetwork/base.py +67 -0
- sknetwork/classification/base.py +24 -24
- sknetwork/classification/base_rank.py +17 -25
- sknetwork/classification/diffusion.py +35 -35
- sknetwork/classification/knn.py +24 -21
- sknetwork/classification/metrics.py +1 -1
- sknetwork/classification/pagerank.py +10 -10
- sknetwork/classification/propagation.py +23 -20
- sknetwork/classification/tests/test_diffusion.py +13 -3
- sknetwork/classification/vote.cp310-win_amd64.pyd +0 -0
- sknetwork/classification/vote.cpp +14482 -10351
- sknetwork/classification/vote.pyx +1 -3
- sknetwork/clustering/__init__.py +3 -1
- sknetwork/clustering/base.py +36 -40
- sknetwork/clustering/kcenters.py +253 -0
- sknetwork/clustering/leiden.py +241 -0
- sknetwork/clustering/leiden_core.cp310-win_amd64.pyd +0 -0
- sknetwork/clustering/leiden_core.cpp +31564 -0
- sknetwork/clustering/leiden_core.pyx +124 -0
- sknetwork/clustering/louvain.py +133 -102
- sknetwork/clustering/louvain_core.cp310-win_amd64.pyd +0 -0
- sknetwork/clustering/louvain_core.cpp +22457 -18792
- sknetwork/clustering/louvain_core.pyx +86 -96
- sknetwork/clustering/postprocess.py +2 -2
- sknetwork/clustering/propagation_clustering.py +15 -19
- sknetwork/clustering/tests/test_API.py +8 -4
- sknetwork/clustering/tests/test_kcenters.py +92 -0
- sknetwork/clustering/tests/test_leiden.py +34 -0
- sknetwork/clustering/tests/test_louvain.py +3 -4
- sknetwork/data/__init__.py +2 -1
- sknetwork/data/base.py +28 -0
- sknetwork/data/load.py +38 -37
- sknetwork/data/models.py +18 -18
- sknetwork/data/parse.py +54 -33
- sknetwork/data/test_graphs.py +2 -2
- sknetwork/data/tests/test_API.py +1 -1
- sknetwork/data/tests/test_base.py +14 -0
- sknetwork/data/tests/test_load.py +1 -1
- sknetwork/data/tests/test_parse.py +9 -12
- sknetwork/data/tests/test_test_graphs.py +1 -2
- sknetwork/data/toy_graphs.py +18 -18
- sknetwork/embedding/__init__.py +0 -1
- sknetwork/embedding/base.py +21 -20
- sknetwork/embedding/force_atlas.py +3 -2
- sknetwork/embedding/louvain_embedding.py +2 -2
- sknetwork/embedding/random_projection.py +5 -3
- sknetwork/embedding/spectral.py +0 -73
- sknetwork/embedding/tests/test_API.py +4 -28
- sknetwork/embedding/tests/test_louvain_embedding.py +4 -9
- sknetwork/embedding/tests/test_random_projection.py +2 -2
- sknetwork/embedding/tests/test_spectral.py +5 -8
- sknetwork/embedding/tests/test_svd.py +1 -1
- sknetwork/gnn/base.py +4 -4
- sknetwork/gnn/base_layer.py +3 -3
- sknetwork/gnn/gnn_classifier.py +45 -89
- sknetwork/gnn/layer.py +1 -1
- sknetwork/gnn/loss.py +1 -1
- sknetwork/gnn/optimizer.py +4 -3
- sknetwork/gnn/tests/test_base_layer.py +4 -4
- sknetwork/gnn/tests/test_gnn_classifier.py +12 -35
- sknetwork/gnn/utils.py +8 -8
- sknetwork/hierarchy/base.py +29 -2
- sknetwork/hierarchy/louvain_hierarchy.py +45 -41
- sknetwork/hierarchy/paris.cp310-win_amd64.pyd +0 -0
- sknetwork/hierarchy/paris.cpp +27369 -22852
- sknetwork/hierarchy/paris.pyx +7 -9
- sknetwork/hierarchy/postprocess.py +16 -16
- sknetwork/hierarchy/tests/test_API.py +1 -1
- sknetwork/hierarchy/tests/test_algos.py +5 -0
- sknetwork/hierarchy/tests/test_metrics.py +1 -1
- sknetwork/linalg/__init__.py +1 -1
- sknetwork/linalg/diteration.cp310-win_amd64.pyd +0 -0
- sknetwork/linalg/diteration.cpp +13474 -9454
- sknetwork/linalg/diteration.pyx +0 -2
- sknetwork/linalg/eig_solver.py +1 -1
- sknetwork/linalg/{normalization.py → normalizer.py} +18 -15
- sknetwork/linalg/operators.py +1 -1
- sknetwork/linalg/ppr_solver.py +1 -1
- sknetwork/linalg/push.cp310-win_amd64.pyd +0 -0
- sknetwork/linalg/push.cpp +22993 -18807
- sknetwork/linalg/push.pyx +0 -2
- sknetwork/linalg/svd_solver.py +1 -1
- sknetwork/linalg/tests/test_normalization.py +3 -7
- sknetwork/linalg/tests/test_operators.py +4 -8
- sknetwork/linalg/tests/test_ppr.py +1 -1
- sknetwork/linkpred/base.py +13 -2
- sknetwork/linkpred/nn.py +6 -6
- sknetwork/log.py +19 -0
- sknetwork/path/__init__.py +4 -3
- sknetwork/path/dag.py +54 -0
- sknetwork/path/distances.py +98 -0
- sknetwork/path/search.py +13 -47
- sknetwork/path/shortest_path.py +37 -162
- sknetwork/path/tests/test_dag.py +37 -0
- sknetwork/path/tests/test_distances.py +62 -0
- sknetwork/path/tests/test_search.py +26 -11
- sknetwork/path/tests/test_shortest_path.py +31 -36
- sknetwork/ranking/__init__.py +0 -1
- sknetwork/ranking/base.py +13 -8
- sknetwork/ranking/betweenness.cp310-win_amd64.pyd +0 -0
- sknetwork/ranking/betweenness.cpp +5709 -3017
- sknetwork/ranking/betweenness.pyx +0 -2
- sknetwork/ranking/closeness.py +7 -10
- sknetwork/ranking/pagerank.py +14 -14
- sknetwork/ranking/postprocess.py +12 -3
- sknetwork/ranking/tests/test_API.py +2 -4
- sknetwork/ranking/tests/test_betweenness.py +3 -3
- sknetwork/ranking/tests/test_closeness.py +3 -7
- sknetwork/ranking/tests/test_pagerank.py +11 -5
- sknetwork/ranking/tests/test_postprocess.py +5 -0
- sknetwork/regression/base.py +19 -2
- sknetwork/regression/diffusion.py +24 -10
- sknetwork/regression/tests/test_diffusion.py +8 -0
- sknetwork/test_base.py +35 -0
- sknetwork/test_log.py +15 -0
- sknetwork/topology/__init__.py +7 -8
- sknetwork/topology/cliques.cp310-win_amd64.pyd +0 -0
- sknetwork/topology/{kcliques.cpp → cliques.cpp} +23412 -20276
- sknetwork/topology/cliques.pyx +149 -0
- sknetwork/topology/core.cp310-win_amd64.pyd +0 -0
- sknetwork/topology/{kcore.cpp → core.cpp} +21732 -18867
- sknetwork/topology/core.pyx +90 -0
- sknetwork/topology/cycles.py +243 -0
- sknetwork/topology/minheap.cp310-win_amd64.pyd +0 -0
- sknetwork/{utils → topology}/minheap.cpp +19452 -15368
- sknetwork/{utils → topology}/minheap.pxd +1 -3
- sknetwork/{utils → topology}/minheap.pyx +1 -3
- sknetwork/topology/structure.py +3 -43
- sknetwork/topology/tests/test_cliques.py +11 -11
- sknetwork/topology/tests/test_core.py +19 -0
- sknetwork/topology/tests/test_cycles.py +65 -0
- sknetwork/topology/tests/test_structure.py +2 -16
- sknetwork/topology/tests/test_triangles.py +11 -15
- sknetwork/topology/tests/test_wl.py +72 -0
- sknetwork/topology/triangles.cp310-win_amd64.pyd +0 -0
- sknetwork/topology/triangles.cpp +5056 -2696
- sknetwork/topology/triangles.pyx +74 -89
- sknetwork/topology/weisfeiler_lehman.py +56 -86
- sknetwork/topology/weisfeiler_lehman_core.cp310-win_amd64.pyd +0 -0
- sknetwork/topology/weisfeiler_lehman_core.cpp +14727 -10622
- sknetwork/topology/weisfeiler_lehman_core.pyx +0 -2
- sknetwork/utils/__init__.py +1 -31
- sknetwork/utils/check.py +2 -2
- sknetwork/utils/format.py +5 -3
- sknetwork/utils/membership.py +2 -2
- sknetwork/utils/tests/test_check.py +3 -3
- sknetwork/utils/tests/test_format.py +3 -1
- sknetwork/utils/values.py +1 -1
- sknetwork/visualization/__init__.py +2 -2
- sknetwork/visualization/dendrograms.py +55 -7
- sknetwork/visualization/graphs.py +292 -72
- sknetwork/visualization/tests/test_dendrograms.py +9 -9
- sknetwork/visualization/tests/test_graphs.py +71 -62
- scikit_network-0.30.0.dist-info/RECORD +0 -227
- sknetwork/embedding/louvain_hierarchy.py +0 -142
- sknetwork/embedding/tests/test_louvain_hierarchy.py +0 -19
- sknetwork/path/metrics.py +0 -148
- sknetwork/path/tests/test_metrics.py +0 -29
- sknetwork/ranking/harmonic.py +0 -82
- sknetwork/topology/dag.py +0 -74
- sknetwork/topology/dag_core.cp310-win_amd64.pyd +0 -0
- sknetwork/topology/dag_core.cpp +0 -23350
- sknetwork/topology/dag_core.pyx +0 -38
- sknetwork/topology/kcliques.cp310-win_amd64.pyd +0 -0
- sknetwork/topology/kcliques.pyx +0 -193
- sknetwork/topology/kcore.cp310-win_amd64.pyd +0 -0
- sknetwork/topology/kcore.pyx +0 -120
- sknetwork/topology/tests/test_cores.py +0 -21
- sknetwork/topology/tests/test_dag.py +0 -26
- sknetwork/topology/tests/test_wl_coloring.py +0 -49
- sknetwork/topology/tests/test_wl_kernel.py +0 -31
- sknetwork/utils/base.py +0 -35
- sknetwork/utils/minheap.cp310-win_amd64.pyd +0 -0
- sknetwork/utils/simplex.py +0 -140
- sknetwork/utils/tests/test_base.py +0 -28
- sknetwork/utils/tests/test_bunch.py +0 -16
- sknetwork/utils/tests/test_projection_simplex.py +0 -33
- sknetwork/utils/tests/test_verbose.py +0 -15
- sknetwork/utils/verbose.py +0 -37
- {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/LICENSE +0 -0
- {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/top_level.txt +0 -0
- /sknetwork/{utils → data}/timeout.py +0 -0
sknetwork/linalg/push.pyx
CHANGED
sknetwork/linalg/svd_solver.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""
|
|
4
|
-
Created
|
|
4
|
+
Created in April 2020
|
|
5
5
|
@author: Nathan de Lara <nathan.delara@polytechnique.org>
|
|
6
6
|
"""
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@ import unittest
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
from scipy import sparse
|
|
12
12
|
|
|
13
|
-
from sknetwork.linalg import normalize
|
|
13
|
+
from sknetwork.linalg import normalize
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class TestNormalization(unittest.TestCase):
|
|
@@ -19,12 +19,10 @@ class TestNormalization(unittest.TestCase):
|
|
|
19
19
|
n = 5
|
|
20
20
|
mat1 = normalize(np.eye(n))
|
|
21
21
|
mat2 = normalize(sparse.eye(n))
|
|
22
|
-
mat3 = normalize(CoNeighbor(mat2))
|
|
23
22
|
|
|
24
23
|
x = np.random.randn(n)
|
|
25
24
|
self.assertAlmostEqual(np.linalg.norm(mat1.dot(x) - x), 0)
|
|
26
25
|
self.assertAlmostEqual(np.linalg.norm(mat2.dot(x) - x), 0)
|
|
27
|
-
self.assertAlmostEqual(np.linalg.norm(mat3.dot(x) - x), 0)
|
|
28
26
|
|
|
29
27
|
mat1 = np.random.rand(n**2).reshape((n, n))
|
|
30
28
|
mat2 = sparse.csr_matrix(mat1)
|
|
@@ -32,7 +30,5 @@ class TestNormalization(unittest.TestCase):
|
|
|
32
30
|
mat2 = normalize(mat2, p=2)
|
|
33
31
|
self.assertAlmostEqual(np.linalg.norm(mat1.dot(x) - mat2.dot(x)), 0)
|
|
34
32
|
|
|
35
|
-
with self.assertRaises(
|
|
36
|
-
normalize(mat3, p=2)
|
|
37
|
-
with self.assertRaises(NotImplementedError):
|
|
33
|
+
with self.assertRaises(ValueError):
|
|
38
34
|
normalize(mat1, p=3)
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""
|
|
4
|
-
Created
|
|
4
|
+
Created in April 2020
|
|
5
5
|
@author: Thomas Bonald <bonald@enst.fr>
|
|
6
6
|
@author: Nathan de Lara <nathan.delara@polytechnique.org>
|
|
7
7
|
"""
|
|
8
8
|
import unittest
|
|
9
9
|
|
|
10
10
|
from sknetwork.data.test_graphs import *
|
|
11
|
-
from sknetwork.linalg import Laplacian, Normalizer, CoNeighbor
|
|
11
|
+
from sknetwork.linalg import Laplacian, Normalizer, CoNeighbor
|
|
12
12
|
from sknetwork.linalg.basics import safe_sparse_dot
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class TestOperators(unittest.TestCase):
|
|
16
16
|
|
|
17
17
|
def test_laplacian(self):
|
|
18
|
-
for adjacency in [test_graph(),
|
|
18
|
+
for adjacency in [test_graph(), test_disconnected_graph()]:
|
|
19
19
|
n = adjacency.shape[1]
|
|
20
20
|
# regular Laplacian
|
|
21
21
|
laplacian = Laplacian(adjacency)
|
|
@@ -35,7 +35,7 @@ class TestOperators(unittest.TestCase):
|
|
|
35
35
|
self.assertEqual(safe_sparse_dot(laplacian, np.ones(shape)).shape, shape)
|
|
36
36
|
|
|
37
37
|
def test_normalizer(self):
|
|
38
|
-
for adjacency in [test_graph(),
|
|
38
|
+
for adjacency in [test_graph(), test_disconnected_graph()]:
|
|
39
39
|
n_row, n_col = adjacency.shape
|
|
40
40
|
# square matrix
|
|
41
41
|
normalizer = Normalizer(adjacency)
|
|
@@ -53,10 +53,6 @@ class TestOperators(unittest.TestCase):
|
|
|
53
53
|
def test_coneighbors(self):
|
|
54
54
|
biadjacency = test_bigraph()
|
|
55
55
|
operator = CoNeighbor(biadjacency)
|
|
56
|
-
transition = normalize(operator)
|
|
57
|
-
x = transition.dot(np.ones(transition.shape[1]))
|
|
58
|
-
|
|
59
|
-
self.assertAlmostEqual(np.linalg.norm(x - np.ones(operator.shape[0])), 0)
|
|
60
56
|
operator.astype('float')
|
|
61
57
|
operator.right_sparse_dot(sparse.eye(operator.shape[1], format='csr'))
|
|
62
58
|
|
sknetwork/linkpred/base.py
CHANGED
|
@@ -7,8 +7,9 @@ Created in March 2022
|
|
|
7
7
|
from abc import ABC
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
|
+
from scipy import sparse
|
|
10
11
|
|
|
11
|
-
from sknetwork.
|
|
12
|
+
from sknetwork.base import Algorithm
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class BaseLinker(Algorithm, ABC):
|
|
@@ -23,7 +24,17 @@ class BaseLinker(Algorithm, ABC):
|
|
|
23
24
|
def __init__(self):
|
|
24
25
|
self.links_ = None
|
|
25
26
|
|
|
26
|
-
def
|
|
27
|
+
def predict(self) -> sparse.csr_matrix:
|
|
28
|
+
"""Return the predicted links.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
links_ : sparse.csr_matrix
|
|
33
|
+
Link matrix.
|
|
34
|
+
"""
|
|
35
|
+
return self.links_
|
|
36
|
+
|
|
37
|
+
def fit_predict(self, *args, **kwargs) -> sparse.csr_matrix:
|
|
27
38
|
"""Fit algorithm to data and return the links. Same parameters as the ``fit`` method.
|
|
28
39
|
|
|
29
40
|
Returns
|
sknetwork/linkpred/nn.py
CHANGED
|
@@ -11,7 +11,7 @@ from scipy import sparse
|
|
|
11
11
|
|
|
12
12
|
from sknetwork.linkpred.base import BaseLinker
|
|
13
13
|
from sknetwork.embedding.base import BaseEmbedding
|
|
14
|
-
from sknetwork.linalg.
|
|
14
|
+
from sknetwork.linalg.normalizer import normalize
|
|
15
15
|
from sknetwork.utils.check import check_n_neighbors
|
|
16
16
|
from sknetwork.utils.format import get_adjacency
|
|
17
17
|
|
|
@@ -23,11 +23,11 @@ class NNLinker(BaseLinker):
|
|
|
23
23
|
|
|
24
24
|
Parameters
|
|
25
25
|
----------
|
|
26
|
-
n_neighbors :
|
|
26
|
+
n_neighbors : int
|
|
27
27
|
Number of nearest neighbors. If ``None``, all nodes are considered.
|
|
28
|
-
threshold :
|
|
28
|
+
threshold : float
|
|
29
29
|
Threshold on cosine similarity. Only links above this threshold are kept.
|
|
30
|
-
embedding_method :
|
|
30
|
+
embedding_method : :class:`BaseEmbedding`
|
|
31
31
|
Embedding method used to represent nodes in vector space.
|
|
32
32
|
If ``None`` (default), use identity.
|
|
33
33
|
|
|
@@ -95,9 +95,9 @@ class NNLinker(BaseLinker):
|
|
|
95
95
|
|
|
96
96
|
Parameters
|
|
97
97
|
----------
|
|
98
|
-
input_matrix :
|
|
98
|
+
input_matrix : sparse.csr_matrix, np.ndarray
|
|
99
99
|
Adjacency matrix or biadjacency matrix of the graph.
|
|
100
|
-
index :
|
|
100
|
+
index : np.ndarray
|
|
101
101
|
Index of source nodes to consider. If ``None``, the links are predicted for all nodes.
|
|
102
102
|
|
|
103
103
|
Returns
|
sknetwork/log.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created in December 2019
|
|
5
|
+
@author: Quentin Lutz <qlutz@enst.fr>
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Log:
|
|
10
|
+
"""Log class for verbosity features"""
|
|
11
|
+
def __init__(self, verbose: bool = False):
|
|
12
|
+
self.verbose = verbose
|
|
13
|
+
self.log = ''
|
|
14
|
+
|
|
15
|
+
def print_log(self, *args):
|
|
16
|
+
"""Fill log with text."""
|
|
17
|
+
if self.verbose:
|
|
18
|
+
print(*args)
|
|
19
|
+
self.log += ' '.join(map(str, args)) + '\n'
|
sknetwork/path/__init__.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Path module"""
|
|
2
|
-
from sknetwork.path.
|
|
3
|
-
from sknetwork.path.
|
|
4
|
-
from sknetwork.path.
|
|
2
|
+
from sknetwork.path.dag import get_dag
|
|
3
|
+
from sknetwork.path.distances import get_distances
|
|
4
|
+
from sknetwork.path.search import breadth_first_search
|
|
5
|
+
from sknetwork.path.shortest_path import get_shortest_path
|
sknetwork/path/dag.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created in May 2023
|
|
5
|
+
@author: Thomas Bonald <bonald@enst.fr>
|
|
6
|
+
"""
|
|
7
|
+
from typing import Iterable, Optional, Union
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from scipy import sparse
|
|
11
|
+
|
|
12
|
+
from sknetwork.path.distances import get_distances
|
|
13
|
+
from sknetwork.utils.check import check_format, check_square
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_dag(adjacency: sparse.csr_matrix, source: Optional[Union[int, Iterable]] = None,
|
|
17
|
+
order: Optional[np.ndarray] = None) -> sparse.csr_matrix:
|
|
18
|
+
"""Get a Directed Acyclic Graph (DAG) from a graph.
|
|
19
|
+
If the order is specified, keep only edges i -> j such that 0 <= order[i] < order[j].
|
|
20
|
+
If the source is specified, use the distances from this source node (or set of source nodes) as order.
|
|
21
|
+
If neither the order nor the source is specified, use the node indices as order.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
adjacency :
|
|
26
|
+
Adjacency matrix of the graph.
|
|
27
|
+
source :
|
|
28
|
+
Source node (or set of source nodes).
|
|
29
|
+
order :
|
|
30
|
+
Order of nodes. Negative values ignored.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
dag :
|
|
35
|
+
Adjacency matrix of the directed acyclic graph.
|
|
36
|
+
"""
|
|
37
|
+
adjacency = check_format(adjacency, allow_empty=True)
|
|
38
|
+
check_square(adjacency)
|
|
39
|
+
|
|
40
|
+
if order is None:
|
|
41
|
+
if source is None:
|
|
42
|
+
order = np.arange(adjacency.shape[0])
|
|
43
|
+
else:
|
|
44
|
+
order = get_distances(adjacency, source)
|
|
45
|
+
|
|
46
|
+
dag = adjacency.astype(bool).tocoo()
|
|
47
|
+
for value in np.unique(order):
|
|
48
|
+
if value < 0:
|
|
49
|
+
dag.data[order[dag.row] == value] = 0
|
|
50
|
+
else:
|
|
51
|
+
dag.data[(order[dag.row] == value) & (order[dag.col] <= value)] = 0
|
|
52
|
+
dag.eliminate_zeros()
|
|
53
|
+
|
|
54
|
+
return dag.tocsr()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created in May 2023
|
|
5
|
+
@author: Thomas Bonald <bonald@enst.fr>
|
|
6
|
+
"""
|
|
7
|
+
from typing import Iterable, Optional, Union, Tuple
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from scipy import sparse
|
|
11
|
+
|
|
12
|
+
from sknetwork.utils.format import get_adjacency
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_distances(input_matrix: sparse.csr_matrix, source: Optional[Union[int, Iterable]] = None,
|
|
16
|
+
source_row: Optional[Union[int, Iterable]] = None,
|
|
17
|
+
source_col: Optional[Union[int, Iterable]] = None, transpose: bool = False,
|
|
18
|
+
force_bipartite: bool = False) \
|
|
19
|
+
-> Union[np.ndarray, Tuple[np.ndarray, np.ndarray], Tuple[np.ndarray, np.ndarray]]:
|
|
20
|
+
"""Get the distances from a source (or a set of sources) in number of hops.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
input_matrix :
|
|
25
|
+
Adjacency matrix or biadjacency matrix of the graph.
|
|
26
|
+
source :
|
|
27
|
+
If an integer, index of the source node.
|
|
28
|
+
If a list or array, indices of source nodes (the shortest distances to one of these nodes is returned).
|
|
29
|
+
source_row, source_col :
|
|
30
|
+
For bipartite graphs, index of source nodes on rows and columns.
|
|
31
|
+
The parameter source_row is an alias for source (at least one of them must be ``None``).
|
|
32
|
+
transpose :
|
|
33
|
+
If ``True``, transpose the input matrix.
|
|
34
|
+
force_bipartite :
|
|
35
|
+
If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
|
|
36
|
+
Set to ``True`` is the parameters source_row or source_col re specified.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
distances : np.ndarray of shape (n_nodes,)
|
|
41
|
+
Vector of distances from source (distance = -1 if no path exists from the source).
|
|
42
|
+
For a bipartite graph, two vectors are returned, one for the rows and one for the columns.
|
|
43
|
+
|
|
44
|
+
Examples
|
|
45
|
+
--------
|
|
46
|
+
>>> from sknetwork.data import cyclic_digraph
|
|
47
|
+
>>> adjacency = cyclic_digraph(3)
|
|
48
|
+
>>> get_distances(adjacency, source=0)
|
|
49
|
+
array([0, 1, 2])
|
|
50
|
+
>>> get_distances(adjacency, source=[0, 2])
|
|
51
|
+
array([0, 1, 0])
|
|
52
|
+
"""
|
|
53
|
+
if transpose:
|
|
54
|
+
matrix = sparse.csr_matrix(input_matrix.T)
|
|
55
|
+
else:
|
|
56
|
+
matrix = input_matrix
|
|
57
|
+
if source_row is not None or source_col is not None:
|
|
58
|
+
force_bipartite = True
|
|
59
|
+
adjacency, bipartite = get_adjacency(matrix, force_bipartite=force_bipartite, allow_empty=True)
|
|
60
|
+
adjacency_transpose = adjacency.astype(bool).T.tocsr()
|
|
61
|
+
n_row, n_col = matrix.shape
|
|
62
|
+
n_nodes = adjacency.shape[0]
|
|
63
|
+
|
|
64
|
+
mask = np.zeros(n_nodes, dtype=bool)
|
|
65
|
+
if bipartite:
|
|
66
|
+
if source is not None:
|
|
67
|
+
if source_row is not None:
|
|
68
|
+
raise ValueError('Only one of the parameters source and source_row can be specified.')
|
|
69
|
+
source_row = source
|
|
70
|
+
if source_row is None and source_col is None:
|
|
71
|
+
raise ValueError('At least one of the parameters source_row or source_col must be specified.')
|
|
72
|
+
if source_row is not None:
|
|
73
|
+
mask[source_row] = 1
|
|
74
|
+
if source_col is not None:
|
|
75
|
+
mask[n_row + np.array(source_col)] = 1
|
|
76
|
+
else:
|
|
77
|
+
if source is None:
|
|
78
|
+
raise ValueError('The parameter source must be specified.')
|
|
79
|
+
mask[source] = 1
|
|
80
|
+
|
|
81
|
+
distances = -np.ones(n_nodes, dtype=int)
|
|
82
|
+
distances[mask] = 0
|
|
83
|
+
|
|
84
|
+
distance = 0
|
|
85
|
+
reach = mask
|
|
86
|
+
|
|
87
|
+
while 1:
|
|
88
|
+
distance += 1
|
|
89
|
+
mask = adjacency_transpose.dot(reach).astype(bool) & ~reach
|
|
90
|
+
if np.sum(mask) == 0:
|
|
91
|
+
break
|
|
92
|
+
distances[mask] = distance
|
|
93
|
+
reach |= mask
|
|
94
|
+
|
|
95
|
+
if bipartite:
|
|
96
|
+
return distances[:n_row], distances[n_row:]
|
|
97
|
+
else:
|
|
98
|
+
return distances
|
sknetwork/path/search.py
CHANGED
|
@@ -1,65 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""
|
|
4
|
-
Created
|
|
4
|
+
Created in May 2023
|
|
5
5
|
"""
|
|
6
6
|
import numpy as np
|
|
7
7
|
from scipy import sparse
|
|
8
8
|
|
|
9
|
-
from sknetwork.
|
|
9
|
+
from sknetwork.path.distances import get_distances
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def breadth_first_search(adjacency: sparse.csr_matrix, source: int
|
|
13
|
-
"""Breadth-first ordering starting
|
|
14
|
-
|
|
15
|
-
Based on SciPy (scipy.sparse.csgraph.breadth_first_order)
|
|
12
|
+
def breadth_first_search(adjacency: sparse.csr_matrix, source: int):
|
|
13
|
+
"""Breadth-first ordering starting from some node.
|
|
16
14
|
|
|
17
15
|
Parameters
|
|
18
16
|
----------
|
|
19
17
|
adjacency :
|
|
20
|
-
|
|
18
|
+
Adjacency matrix of the graph.
|
|
21
19
|
source : int
|
|
22
|
-
|
|
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
|
|
20
|
+
Source node.
|
|
53
21
|
|
|
54
22
|
Returns
|
|
55
23
|
-------
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
reachable from the
|
|
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``.
|
|
24
|
+
index : np.ndarray
|
|
25
|
+
Node index corresponding to the breadth-first-search from the source.
|
|
26
|
+
The length of the vector is the number of nodes reachable from the source.
|
|
63
27
|
"""
|
|
64
|
-
|
|
65
|
-
|
|
28
|
+
distances = get_distances(adjacency, source)
|
|
29
|
+
indices = np.argsort(distances)
|
|
30
|
+
n = np.sum(distances < 0)
|
|
31
|
+
return indices[n:]
|
sknetwork/path/shortest_path.py
CHANGED
|
@@ -1,186 +1,61 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""
|
|
4
|
-
Created
|
|
5
|
-
@author:
|
|
4
|
+
Created in May 2023
|
|
5
|
+
@author: Thomas Bonald <bonald@enst.fr>
|
|
6
6
|
"""
|
|
7
|
-
from
|
|
8
|
-
from multiprocessing import Pool
|
|
9
|
-
from typing import Optional, Union, Iterable
|
|
7
|
+
from typing import Iterable, Optional, Union, Tuple
|
|
10
8
|
|
|
11
9
|
import numpy as np
|
|
12
10
|
from scipy import sparse
|
|
13
11
|
|
|
14
|
-
from sknetwork.
|
|
12
|
+
from sknetwork.path.dag import get_dag
|
|
13
|
+
from sknetwork.utils.format import bipartite2undirected
|
|
14
|
+
from sknetwork.path.distances import get_distances
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* Digraphs
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Based on SciPy (scipy.sparse.csgraph.shortest_path)
|
|
17
|
+
def get_shortest_path(input_matrix: sparse.csr_matrix, source: Optional[Union[int, Iterable]] = None,
|
|
18
|
+
source_row: Optional[Union[int, Iterable]] = None,
|
|
19
|
+
source_col: Optional[Union[int, Iterable]] = None, force_bipartite: bool = False) \
|
|
20
|
+
-> Union[np.ndarray, Tuple[np.ndarray, np.ndarray], Tuple[np.ndarray, np.ndarray]]:
|
|
21
|
+
"""Get the shortest paths from a source (or a set of sources) in number of hops.
|
|
26
22
|
|
|
27
23
|
Parameters
|
|
28
24
|
----------
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
If
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
|
25
|
+
input_matrix :
|
|
26
|
+
Adjacency matrix or biadjacency matrix of the graph.
|
|
27
|
+
source :
|
|
28
|
+
If an integer, index of the source node.
|
|
29
|
+
If a list, indices of source nodes (the shortest distances to one of these nodes in returned).
|
|
30
|
+
source_row, source_col :
|
|
31
|
+
For bipartite graphs, index of source nodes on rows and columns.
|
|
32
|
+
The parameter source_row is an alias for source (at least one of them must be ``None``).
|
|
33
|
+
force_bipartite :
|
|
34
|
+
If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
|
|
35
|
+
Set to ``True`` is the parameters source_row or source_col are specified.
|
|
46
36
|
|
|
47
37
|
Returns
|
|
48
38
|
-------
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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``).
|
|
39
|
+
path : sparse.csr_matrix
|
|
40
|
+
Adjacency matrix of the graph of the shortest paths from the source node (or the set of source nodes).
|
|
41
|
+
If the input graph is a bipartite graph, the shape of the matrix is (n_row + n_col, n_row + n_col) with the new
|
|
42
|
+
index corresponding to the rows then the columns of the original graph.
|
|
58
43
|
|
|
59
44
|
Examples
|
|
60
45
|
--------
|
|
61
46
|
>>> from sknetwork.data import cyclic_digraph
|
|
62
47
|
>>> adjacency = cyclic_digraph(3)
|
|
63
|
-
>>>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
48
|
+
>>> path = get_shortest_path(adjacency, source=0)
|
|
49
|
+
>>> path.toarray().astype(int)
|
|
50
|
+
array([[0, 1, 0],
|
|
51
|
+
[0, 0, 1],
|
|
52
|
+
[0, 0, 0]])
|
|
68
53
|
"""
|
|
69
|
-
|
|
70
|
-
if
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
54
|
+
distances = get_distances(input_matrix, source, source_row, source_col, force_bipartite)
|
|
55
|
+
if type(distances) == tuple:
|
|
56
|
+
adjacency = bipartite2undirected(input_matrix)
|
|
57
|
+
distances = np.hstack(distances)
|
|
168
58
|
else:
|
|
169
|
-
|
|
59
|
+
adjacency = input_matrix
|
|
60
|
+
return get_dag(adjacency, order=distances)
|
|
170
61
|
|
|
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
|