scikit-network 0.33.0__cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.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.33.0.dist-info/AUTHORS.rst +43 -0
- scikit_network-0.33.0.dist-info/LICENSE +34 -0
- scikit_network-0.33.0.dist-info/METADATA +517 -0
- scikit_network-0.33.0.dist-info/RECORD +217 -0
- scikit_network-0.33.0.dist-info/WHEEL +6 -0
- scikit_network-0.33.0.dist-info/top_level.txt +1 -0
- scikit_network.libs/libgomp-a34b3233.so.1.0.0 +0 -0
- sknetwork/__init__.py +21 -0
- sknetwork/base.py +67 -0
- sknetwork/classification/__init__.py +8 -0
- sknetwork/classification/base.py +142 -0
- sknetwork/classification/base_rank.py +133 -0
- sknetwork/classification/diffusion.py +134 -0
- sknetwork/classification/knn.py +139 -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 +30 -0
- sknetwork/classification/tests/test_diffusion.py +77 -0
- sknetwork/classification/tests/test_knn.py +23 -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-312-x86_64-linux-gnu.so +0 -0
- sknetwork/classification/vote.pyx +56 -0
- sknetwork/clustering/__init__.py +8 -0
- sknetwork/clustering/base.py +172 -0
- sknetwork/clustering/kcenters.py +253 -0
- sknetwork/clustering/leiden.py +242 -0
- sknetwork/clustering/leiden_core.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/clustering/leiden_core.pyx +124 -0
- sknetwork/clustering/louvain.py +286 -0
- sknetwork/clustering/louvain_core.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/clustering/louvain_core.pyx +124 -0
- sknetwork/clustering/metrics.py +91 -0
- sknetwork/clustering/postprocess.py +66 -0
- sknetwork/clustering/propagation_clustering.py +104 -0
- sknetwork/clustering/tests/__init__.py +1 -0
- sknetwork/clustering/tests/test_API.py +38 -0
- sknetwork/clustering/tests/test_kcenters.py +60 -0
- sknetwork/clustering/tests/test_leiden.py +34 -0
- sknetwork/clustering/tests/test_louvain.py +129 -0
- sknetwork/clustering/tests/test_metrics.py +50 -0
- sknetwork/clustering/tests/test_postprocess.py +39 -0
- sknetwork/data/__init__.py +6 -0
- sknetwork/data/base.py +33 -0
- sknetwork/data/load.py +406 -0
- sknetwork/data/models.py +459 -0
- sknetwork/data/parse.py +644 -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_base.py +14 -0
- sknetwork/data/tests/test_load.py +95 -0
- sknetwork/data/tests/test_models.py +52 -0
- sknetwork/data/tests/test_parse.py +250 -0
- sknetwork/data/tests/test_test_graphs.py +29 -0
- sknetwork/data/tests/test_toy_graphs.py +68 -0
- sknetwork/data/timeout.py +38 -0
- sknetwork/data/toy_graphs.py +611 -0
- sknetwork/embedding/__init__.py +8 -0
- sknetwork/embedding/base.py +94 -0
- sknetwork/embedding/force_atlas.py +198 -0
- sknetwork/embedding/louvain_embedding.py +148 -0
- sknetwork/embedding/random_projection.py +135 -0
- sknetwork/embedding/spectral.py +141 -0
- sknetwork/embedding/spring.py +198 -0
- sknetwork/embedding/svd.py +359 -0
- sknetwork/embedding/tests/__init__.py +1 -0
- sknetwork/embedding/tests/test_API.py +49 -0
- sknetwork/embedding/tests/test_force_atlas.py +35 -0
- sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
- sknetwork/embedding/tests/test_random_projection.py +28 -0
- sknetwork/embedding/tests/test_spectral.py +81 -0
- sknetwork/embedding/tests/test_spring.py +50 -0
- sknetwork/embedding/tests/test_svd.py +43 -0
- sknetwork/gnn/__init__.py +10 -0
- sknetwork/gnn/activation.py +117 -0
- sknetwork/gnn/base.py +181 -0
- sknetwork/gnn/base_activation.py +89 -0
- sknetwork/gnn/base_layer.py +109 -0
- sknetwork/gnn/gnn_classifier.py +305 -0
- sknetwork/gnn/layer.py +153 -0
- sknetwork/gnn/loss.py +180 -0
- sknetwork/gnn/neighbor_sampler.py +65 -0
- sknetwork/gnn/optimizer.py +164 -0
- sknetwork/gnn/tests/__init__.py +1 -0
- sknetwork/gnn/tests/test_activation.py +56 -0
- sknetwork/gnn/tests/test_base.py +75 -0
- sknetwork/gnn/tests/test_base_layer.py +37 -0
- sknetwork/gnn/tests/test_gnn_classifier.py +130 -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 +41 -0
- sknetwork/gnn/utils.py +127 -0
- sknetwork/hierarchy/__init__.py +6 -0
- sknetwork/hierarchy/base.py +96 -0
- sknetwork/hierarchy/louvain_hierarchy.py +272 -0
- sknetwork/hierarchy/metrics.py +234 -0
- sknetwork/hierarchy/paris.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/hierarchy/paris.pyx +316 -0
- sknetwork/hierarchy/postprocess.py +350 -0
- sknetwork/hierarchy/tests/__init__.py +1 -0
- sknetwork/hierarchy/tests/test_API.py +24 -0
- sknetwork/hierarchy/tests/test_algos.py +34 -0
- sknetwork/hierarchy/tests/test_metrics.py +62 -0
- sknetwork/hierarchy/tests/test_postprocess.py +57 -0
- sknetwork/linalg/__init__.py +9 -0
- sknetwork/linalg/basics.py +37 -0
- sknetwork/linalg/diteration.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/linalg/diteration.pyx +47 -0
- sknetwork/linalg/eig_solver.py +93 -0
- sknetwork/linalg/laplacian.py +15 -0
- sknetwork/linalg/normalizer.py +86 -0
- sknetwork/linalg/operators.py +225 -0
- sknetwork/linalg/polynome.py +76 -0
- sknetwork/linalg/ppr_solver.py +170 -0
- sknetwork/linalg/push.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/linalg/push.pyx +71 -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 +34 -0
- sknetwork/linalg/tests/test_operators.py +66 -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 +2 -0
- sknetwork/linkpred/base.py +46 -0
- sknetwork/linkpred/nn.py +126 -0
- sknetwork/linkpred/tests/__init__.py +1 -0
- sknetwork/linkpred/tests/test_nn.py +27 -0
- sknetwork/log.py +19 -0
- sknetwork/path/__init__.py +5 -0
- sknetwork/path/dag.py +54 -0
- sknetwork/path/distances.py +98 -0
- sknetwork/path/search.py +31 -0
- sknetwork/path/shortest_path.py +61 -0
- sknetwork/path/tests/__init__.py +1 -0
- sknetwork/path/tests/test_dag.py +37 -0
- sknetwork/path/tests/test_distances.py +62 -0
- sknetwork/path/tests/test_search.py +40 -0
- sknetwork/path/tests/test_shortest_path.py +40 -0
- sknetwork/ranking/__init__.py +8 -0
- sknetwork/ranking/base.py +61 -0
- sknetwork/ranking/betweenness.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/ranking/betweenness.pyx +97 -0
- sknetwork/ranking/closeness.py +92 -0
- sknetwork/ranking/hits.py +94 -0
- sknetwork/ranking/katz.py +83 -0
- sknetwork/ranking/pagerank.py +110 -0
- sknetwork/ranking/postprocess.py +37 -0
- sknetwork/ranking/tests/__init__.py +1 -0
- sknetwork/ranking/tests/test_API.py +32 -0
- sknetwork/ranking/tests/test_betweenness.py +38 -0
- sknetwork/ranking/tests/test_closeness.py +30 -0
- sknetwork/ranking/tests/test_hits.py +20 -0
- sknetwork/ranking/tests/test_pagerank.py +62 -0
- sknetwork/ranking/tests/test_postprocess.py +26 -0
- sknetwork/regression/__init__.py +4 -0
- sknetwork/regression/base.py +61 -0
- sknetwork/regression/diffusion.py +210 -0
- sknetwork/regression/tests/__init__.py +1 -0
- sknetwork/regression/tests/test_API.py +32 -0
- sknetwork/regression/tests/test_diffusion.py +56 -0
- sknetwork/sknetwork.py +3 -0
- sknetwork/test_base.py +35 -0
- sknetwork/test_log.py +15 -0
- sknetwork/topology/__init__.py +8 -0
- sknetwork/topology/cliques.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/topology/cliques.pyx +149 -0
- sknetwork/topology/core.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/topology/core.pyx +90 -0
- sknetwork/topology/cycles.py +243 -0
- sknetwork/topology/minheap.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/topology/minheap.pxd +20 -0
- sknetwork/topology/minheap.pyx +109 -0
- sknetwork/topology/structure.py +194 -0
- sknetwork/topology/tests/__init__.py +1 -0
- sknetwork/topology/tests/test_cliques.py +28 -0
- sknetwork/topology/tests/test_core.py +19 -0
- sknetwork/topology/tests/test_cycles.py +65 -0
- sknetwork/topology/tests/test_structure.py +85 -0
- sknetwork/topology/tests/test_triangles.py +38 -0
- sknetwork/topology/tests/test_wl.py +72 -0
- sknetwork/topology/triangles.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/topology/triangles.pyx +151 -0
- sknetwork/topology/weisfeiler_lehman.py +133 -0
- sknetwork/topology/weisfeiler_lehman_core.cpython-312-x86_64-linux-gnu.so +0 -0
- sknetwork/topology/weisfeiler_lehman_core.pyx +114 -0
- sknetwork/utils/__init__.py +7 -0
- sknetwork/utils/check.py +355 -0
- sknetwork/utils/format.py +221 -0
- sknetwork/utils/membership.py +82 -0
- sknetwork/utils/neighbors.py +115 -0
- sknetwork/utils/tests/__init__.py +1 -0
- sknetwork/utils/tests/test_check.py +190 -0
- sknetwork/utils/tests/test_format.py +63 -0
- sknetwork/utils/tests/test_membership.py +24 -0
- sknetwork/utils/tests/test_neighbors.py +41 -0
- sknetwork/utils/tests/test_tfidf.py +18 -0
- sknetwork/utils/tests/test_values.py +66 -0
- sknetwork/utils/tfidf.py +37 -0
- sknetwork/utils/values.py +76 -0
- sknetwork/visualization/__init__.py +4 -0
- sknetwork/visualization/colors.py +34 -0
- sknetwork/visualization/dendrograms.py +277 -0
- sknetwork/visualization/graphs.py +1039 -0
- sknetwork/visualization/tests/__init__.py +1 -0
- sknetwork/visualization/tests/test_dendrograms.py +53 -0
- sknetwork/visualization/tests/test_graphs.py +176 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created in April 2020
|
|
5
|
+
@author: Thomas Bonald <bonald@enst.fr>
|
|
6
|
+
@author: Nathan de Lara <nathan.delara@polytechnique.org>
|
|
7
|
+
"""
|
|
8
|
+
import unittest
|
|
9
|
+
|
|
10
|
+
from sknetwork.data.test_graphs import *
|
|
11
|
+
from sknetwork.linalg import Laplacian, Normalizer, CoNeighbor
|
|
12
|
+
from sknetwork.linalg.basics import safe_sparse_dot
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestOperators(unittest.TestCase):
|
|
16
|
+
|
|
17
|
+
def test_laplacian(self):
|
|
18
|
+
for adjacency in [test_graph(), test_disconnected_graph()]:
|
|
19
|
+
n = adjacency.shape[1]
|
|
20
|
+
# regular Laplacian
|
|
21
|
+
laplacian = Laplacian(adjacency)
|
|
22
|
+
self.assertAlmostEqual(np.linalg.norm(laplacian.dot(np.ones(n))), 0)
|
|
23
|
+
# normalized Laplacian
|
|
24
|
+
laplacian = Laplacian(adjacency, normalized_laplacian=True)
|
|
25
|
+
weights = adjacency.dot(np.ones(n))
|
|
26
|
+
self.assertAlmostEqual(np.linalg.norm(laplacian.dot(np.sqrt(weights))), 0)
|
|
27
|
+
# regularization
|
|
28
|
+
regularization = 0.1
|
|
29
|
+
laplacian = Laplacian(adjacency, regularization=regularization, normalized_laplacian=True)
|
|
30
|
+
weights = adjacency.dot(np.ones(n)) + regularization
|
|
31
|
+
self.assertAlmostEqual(np.linalg.norm(laplacian.dot(np.sqrt(weights))), 0)
|
|
32
|
+
# product
|
|
33
|
+
shape = (n, 3)
|
|
34
|
+
self.assertEqual(laplacian.dot(np.ones(shape)).shape, shape)
|
|
35
|
+
self.assertEqual(safe_sparse_dot(laplacian, np.ones(shape)).shape, shape)
|
|
36
|
+
|
|
37
|
+
def test_normalizer(self):
|
|
38
|
+
for adjacency in [test_graph(), test_disconnected_graph()]:
|
|
39
|
+
n_row, n_col = adjacency.shape
|
|
40
|
+
# square matrix
|
|
41
|
+
normalizer = Normalizer(adjacency)
|
|
42
|
+
non_zeros = adjacency.dot(np.ones(n_col)) > 0
|
|
43
|
+
self.assertAlmostEqual(np.linalg.norm(normalizer.dot(np.ones(n_col)) - non_zeros), 0)
|
|
44
|
+
# single row
|
|
45
|
+
normalizer = Normalizer(adjacency[1])
|
|
46
|
+
self.assertAlmostEqual(float(normalizer.dot(np.ones(n_col))), 1)
|
|
47
|
+
normalizer = Normalizer(adjacency[2].toarray().ravel())
|
|
48
|
+
self.assertAlmostEqual(float(normalizer.dot(np.ones(n_col))), 1)
|
|
49
|
+
# regularization
|
|
50
|
+
normalizer = Normalizer(adjacency, 1)
|
|
51
|
+
self.assertAlmostEqual(np.linalg.norm(normalizer.dot(np.ones(n_col)) - np.ones(n_row)), 0)
|
|
52
|
+
|
|
53
|
+
def test_coneighbors(self):
|
|
54
|
+
biadjacency = test_bigraph()
|
|
55
|
+
operator = CoNeighbor(biadjacency)
|
|
56
|
+
operator.astype('float')
|
|
57
|
+
operator.right_sparse_dot(sparse.eye(operator.shape[1], format='csr'))
|
|
58
|
+
|
|
59
|
+
operator1 = CoNeighbor(biadjacency, normalized=False)
|
|
60
|
+
operator2 = CoNeighbor(biadjacency, normalized=False)
|
|
61
|
+
x = np.random.randn(operator.shape[1])
|
|
62
|
+
x1 = (-operator1).dot(x)
|
|
63
|
+
x2 = (operator2 * -1).dot(x)
|
|
64
|
+
x3 = operator1.T.dot(x)
|
|
65
|
+
self.assertAlmostEqual(np.linalg.norm(x1 - x2), 0)
|
|
66
|
+
self.assertAlmostEqual(np.linalg.norm(x2 - x3), 0)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for polynomials."""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from scipy import sparse
|
|
9
|
+
|
|
10
|
+
from sknetwork.data.test_graphs import test_graph
|
|
11
|
+
from sknetwork.linalg import Polynome
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestPolynome(unittest.TestCase):
|
|
15
|
+
|
|
16
|
+
def test_init(self):
|
|
17
|
+
adjacency = test_graph()
|
|
18
|
+
with self.assertRaises(ValueError):
|
|
19
|
+
Polynome(adjacency, np.array([]))
|
|
20
|
+
|
|
21
|
+
def test_operations(self):
|
|
22
|
+
adjacency = test_graph()
|
|
23
|
+
n = adjacency.shape[0]
|
|
24
|
+
polynome = Polynome(adjacency, np.arange(3))
|
|
25
|
+
x = np.random.randn(n)
|
|
26
|
+
|
|
27
|
+
y1 = (polynome * 2).dot(x)
|
|
28
|
+
y2 = (-polynome).dot(x)
|
|
29
|
+
self.assertAlmostEqual(np.linalg.norm(0.5 * y1 + y2), 0)
|
|
30
|
+
|
|
31
|
+
def test_dot(self):
|
|
32
|
+
adjacency = sparse.eye(5, format='csr')
|
|
33
|
+
polynome = Polynome(adjacency, np.arange(2))
|
|
34
|
+
|
|
35
|
+
x = np.random.randn(5, 3)
|
|
36
|
+
y = polynome.dot(x)
|
|
37
|
+
self.assertAlmostEqual(np.linalg.norm(x - y), 0)
|
|
38
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for d-iteration"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from sknetwork.data import house, karate_club
|
|
9
|
+
from sknetwork.data.parse import from_edge_list
|
|
10
|
+
from sknetwork.data.test_graphs import *
|
|
11
|
+
from sknetwork.linalg.operators import Regularizer
|
|
12
|
+
from sknetwork.linalg.ppr_solver import get_pagerank
|
|
13
|
+
from sknetwork.utils.check import is_proba_array
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestPPR(unittest.TestCase):
|
|
17
|
+
|
|
18
|
+
def test_diteration(self):
|
|
19
|
+
# test convergence by tolerance
|
|
20
|
+
for adjacency in [house(), test_graph(), test_digraph()]:
|
|
21
|
+
seeds = np.ones(adjacency.shape[0]) / adjacency.shape[0]
|
|
22
|
+
pr = get_pagerank(adjacency, damping_factor=0.85, n_iter=100, tol=10, solver='diteration', seeds=seeds)
|
|
23
|
+
self.assertTrue(is_proba_array(pr))
|
|
24
|
+
|
|
25
|
+
# test graph with some null out-degree
|
|
26
|
+
adjacency = from_edge_list([(0, 1)])
|
|
27
|
+
seeds = np.ones(adjacency.shape[0]) / adjacency.shape[0]
|
|
28
|
+
pr = get_pagerank(adjacency, damping_factor=0.85, n_iter=100, tol=10, solver='diteration', seeds=seeds)
|
|
29
|
+
self.assertTrue(is_proba_array(pr))
|
|
30
|
+
|
|
31
|
+
# test invalid entry
|
|
32
|
+
adjacency = Regularizer(house(), 0.1)
|
|
33
|
+
seeds = np.ones(adjacency.shape[0]) / adjacency.shape[0]
|
|
34
|
+
with self.assertRaises(ValueError):
|
|
35
|
+
get_pagerank(adjacency, damping_factor=0.85, n_iter=100, tol=10, solver='diteration', seeds=seeds)
|
|
36
|
+
|
|
37
|
+
def test_push(self):
|
|
38
|
+
# test convergence by tolerance
|
|
39
|
+
adjacency = karate_club()
|
|
40
|
+
seeds = np.ones(adjacency.shape[0]) / adjacency.shape[0]
|
|
41
|
+
pr = get_pagerank(adjacency, damping_factor=0.85,
|
|
42
|
+
n_iter=100, tol=1e-1, solver='push', seeds=seeds)
|
|
43
|
+
self.assertTrue(is_proba_array(pr))
|
|
44
|
+
|
|
45
|
+
def test_piteration(self):
|
|
46
|
+
# test on SparseLR matrix
|
|
47
|
+
adjacency = Regularizer(house(), 0.1)
|
|
48
|
+
seeds = np.ones(adjacency.shape[0]) / adjacency.shape[0]
|
|
49
|
+
pr = get_pagerank(adjacency, damping_factor=0.85, n_iter=100, tol=10, solver='piteration', seeds=seeds)
|
|
50
|
+
self.assertTrue(is_proba_array(pr))
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for embeddings metrics."""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from sknetwork.data import house, star_wars
|
|
10
|
+
from sknetwork.linalg.sparse_lowrank import SparseLR
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestSparseLowRank(unittest.TestCase):
|
|
14
|
+
|
|
15
|
+
def setUp(self):
|
|
16
|
+
"""Simple regularized adjacency and biadjacency for tests."""
|
|
17
|
+
self.undirected = SparseLR(house(), [(np.ones(5), np.ones(5))])
|
|
18
|
+
self.bipartite = SparseLR(star_wars(), [(np.ones(4), np.ones(3))])
|
|
19
|
+
|
|
20
|
+
def test_init(self):
|
|
21
|
+
with self.assertRaises(ValueError):
|
|
22
|
+
SparseLR(house(), [(np.ones(5), np.ones(4))])
|
|
23
|
+
with self.assertRaises(ValueError):
|
|
24
|
+
SparseLR(house(), [(np.ones(4), np.ones(5))])
|
|
25
|
+
|
|
26
|
+
def test_addition(self):
|
|
27
|
+
addition = self.undirected + self.undirected
|
|
28
|
+
expected = SparseLR(2 * house(), [(np.ones(5), 2 * np.ones(5))])
|
|
29
|
+
err = (addition.sparse_mat - expected.sparse_mat).count_nonzero()
|
|
30
|
+
self.assertEqual(err, 0)
|
|
31
|
+
x = np.random.rand(5)
|
|
32
|
+
self.assertAlmostEqual(np.linalg.norm(addition.dot(x) - expected.dot(x)), 0)
|
|
33
|
+
|
|
34
|
+
def test_operations(self):
|
|
35
|
+
adjacency = self.undirected.sparse_mat
|
|
36
|
+
slr = -self.undirected
|
|
37
|
+
slr += adjacency
|
|
38
|
+
slr -= adjacency
|
|
39
|
+
slr.left_sparse_dot(adjacency)
|
|
40
|
+
slr.right_sparse_dot(adjacency)
|
|
41
|
+
slr.astype(float)
|
|
42
|
+
|
|
43
|
+
def test_product(self):
|
|
44
|
+
prod = self.undirected.dot(np.ones(5))
|
|
45
|
+
self.assertEqual(prod.shape, (5,))
|
|
46
|
+
prod = self.bipartite.dot(np.ones(3))
|
|
47
|
+
self.assertEqual(np.linalg.norm(prod - np.array([5., 4., 6., 5.])), 0.)
|
|
48
|
+
prod = self.bipartite.dot(0.5 * np.ones(3))
|
|
49
|
+
self.assertEqual(np.linalg.norm(prod - np.array([2.5, 2., 3., 2.5])), 0.)
|
|
50
|
+
prod = (2 * self.bipartite).dot(0.5 * np.ones(3))
|
|
51
|
+
self.assertEqual(np.linalg.norm(prod - 2 * np.array([2.5, 2., 3., 2.5])), 0.)
|
|
52
|
+
|
|
53
|
+
def test_transposition(self):
|
|
54
|
+
transposed = self.undirected.T
|
|
55
|
+
error = (self.undirected.sparse_mat - transposed.sparse_mat).data
|
|
56
|
+
self.assertEqual(abs(error).sum(), 0.)
|
|
57
|
+
transposed = self.bipartite.T
|
|
58
|
+
x, y = transposed.low_rank_tuples[0]
|
|
59
|
+
self.assertTrue((x == np.ones(3)).all())
|
|
60
|
+
self.assertTrue((y == np.ones(4)).all())
|
|
61
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for svd."""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from sknetwork.data import movie_actor
|
|
10
|
+
from sknetwork.linalg import LanczosSVD, SparseLR
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def svd_err(matrix, u, v, sigma):
|
|
14
|
+
"""Approximation error for singular vectors."""
|
|
15
|
+
err = matrix.dot(v) - u * sigma
|
|
16
|
+
return np.linalg.norm(err)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# noinspection DuplicatedCode
|
|
20
|
+
class TestSolvers(unittest.TestCase):
|
|
21
|
+
|
|
22
|
+
def setUp(self):
|
|
23
|
+
"""Simple biadjacency for tests."""
|
|
24
|
+
self.biadjacency = movie_actor()
|
|
25
|
+
n_row, n_col = self.biadjacency.shape
|
|
26
|
+
self.slr = SparseLR(self.biadjacency, [(np.random.rand(n_row), np.random.rand(n_col))])
|
|
27
|
+
|
|
28
|
+
def test_lanczos(self):
|
|
29
|
+
solver = LanczosSVD()
|
|
30
|
+
solver.fit(self.biadjacency, 2)
|
|
31
|
+
self.assertEqual(len(solver.singular_values_), 2)
|
|
32
|
+
self.assertAlmostEqual(svd_err(self.biadjacency, solver.singular_vectors_left_, solver.singular_vectors_right_,
|
|
33
|
+
solver.singular_values_), 0)
|
|
34
|
+
|
|
35
|
+
solver.fit(self.slr, 2)
|
|
36
|
+
self.assertEqual(len(solver.singular_values_), 2)
|
|
37
|
+
self.assertAlmostEqual(svd_err(self.slr, solver.singular_vectors_left_, solver.singular_vectors_right_,
|
|
38
|
+
solver.singular_values_), 0)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created in March 2022
|
|
5
|
+
@author: Thomas Bonald <thomas.bonald@telecom-paris.fr>
|
|
6
|
+
"""
|
|
7
|
+
from abc import ABC
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from scipy import sparse
|
|
11
|
+
|
|
12
|
+
from sknetwork.base import Algorithm
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseLinker(Algorithm, ABC):
|
|
16
|
+
"""Base class for link prediction.
|
|
17
|
+
|
|
18
|
+
Attributes
|
|
19
|
+
----------
|
|
20
|
+
links_: sparse.csr_matrix
|
|
21
|
+
Link matrix.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.links_ = None
|
|
26
|
+
|
|
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:
|
|
38
|
+
"""Fit algorithm to data and return the links. Same parameters as the ``fit`` method.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
links_ : sparse.csr_matrix
|
|
43
|
+
Link matrix.
|
|
44
|
+
"""
|
|
45
|
+
self.fit(*args, **kwargs)
|
|
46
|
+
return self.links_
|
sknetwork/linkpred/nn.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created in March 2023
|
|
5
|
+
@author: Thomas Bonald <thomas.bonald@telecom-paris.fr>
|
|
6
|
+
"""
|
|
7
|
+
from typing import Optional, Union
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from scipy import sparse
|
|
11
|
+
|
|
12
|
+
from sknetwork.linkpred.base import BaseLinker
|
|
13
|
+
from sknetwork.embedding.base import BaseEmbedding
|
|
14
|
+
from sknetwork.linalg.normalizer import normalize
|
|
15
|
+
from sknetwork.utils.check import check_n_neighbors
|
|
16
|
+
from sknetwork.utils.format import get_adjacency
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NNLinker(BaseLinker):
|
|
20
|
+
"""Link prediction by nearest neighbors in the embedding space, using cosine similarity.
|
|
21
|
+
|
|
22
|
+
For bipartite graphs, predict links between rows and columns only.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
n_neighbors : int
|
|
27
|
+
Number of nearest neighbors. If ``None``, all nodes are considered.
|
|
28
|
+
threshold : float
|
|
29
|
+
Threshold on cosine similarity. Only links above this threshold are kept.
|
|
30
|
+
embedding_method : :class:`BaseEmbedding`
|
|
31
|
+
Embedding method used to represent nodes in vector space.
|
|
32
|
+
If ``None`` (default), use identity.
|
|
33
|
+
|
|
34
|
+
Attributes
|
|
35
|
+
----------
|
|
36
|
+
links_ : sparse.csr_matrix
|
|
37
|
+
Link matrix.
|
|
38
|
+
|
|
39
|
+
Example
|
|
40
|
+
-------
|
|
41
|
+
>>> from sknetwork.linkpred import NNLinker
|
|
42
|
+
>>> from sknetwork.data import karate_club
|
|
43
|
+
>>> linker = NNLinker(n_neighbors=5, threshold=0.5)
|
|
44
|
+
>>> graph = karate_club(metadata=True)
|
|
45
|
+
>>> adjacency = graph.adjacency
|
|
46
|
+
>>> links = linker.fit_predict(adjacency)
|
|
47
|
+
>>> links.shape
|
|
48
|
+
(34, 34)
|
|
49
|
+
"""
|
|
50
|
+
def __init__(self, n_neighbors: Optional[int] = 10, threshold: float = 0,
|
|
51
|
+
embedding_method: Optional[BaseEmbedding] = None):
|
|
52
|
+
super(NNLinker, self).__init__()
|
|
53
|
+
self.n_neighbors = n_neighbors
|
|
54
|
+
self.threshold = threshold
|
|
55
|
+
self.embedding_method = embedding_method
|
|
56
|
+
self.bipartite = None
|
|
57
|
+
|
|
58
|
+
def _fit_core(self, embedding, mask):
|
|
59
|
+
n = embedding.shape[0]
|
|
60
|
+
n_row = len(mask)
|
|
61
|
+
if n_row < n:
|
|
62
|
+
# bipartite graphs
|
|
63
|
+
index_col = np.arange(n_row, n)
|
|
64
|
+
n_col = n - n_row
|
|
65
|
+
else:
|
|
66
|
+
index_col = np.arange(n)
|
|
67
|
+
n_col = n
|
|
68
|
+
n_neighbors = check_n_neighbors(self.n_neighbors, len(index_col))
|
|
69
|
+
|
|
70
|
+
row = []
|
|
71
|
+
col = []
|
|
72
|
+
data = []
|
|
73
|
+
|
|
74
|
+
for i in np.flatnonzero(mask):
|
|
75
|
+
vector = embedding[i]
|
|
76
|
+
if sparse.issparse(vector):
|
|
77
|
+
vector = vector.toarray().ravel()
|
|
78
|
+
similarities = embedding[index_col].dot(vector)
|
|
79
|
+
nn = np.argpartition(-similarities, n_neighbors)[:n_neighbors]
|
|
80
|
+
mask_nn = np.zeros(n_col, dtype=bool)
|
|
81
|
+
mask_nn[nn] = 1
|
|
82
|
+
mask_nn[similarities < self.threshold] = 0
|
|
83
|
+
nn = np.flatnonzero(mask_nn)
|
|
84
|
+
|
|
85
|
+
row += len(nn) * [i]
|
|
86
|
+
col += list(nn)
|
|
87
|
+
data += list(similarities[nn])
|
|
88
|
+
|
|
89
|
+
links = sparse.csr_matrix((data, (row, col)), shape=(n_row, n_col))
|
|
90
|
+
|
|
91
|
+
return links
|
|
92
|
+
|
|
93
|
+
def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray], index: Optional[np.ndarray] = None) -> 'NNLinker':
|
|
94
|
+
"""Link prediction by nearest neighbors in the embedding space, using cosine similarity
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
input_matrix : sparse.csr_matrix, np.ndarray
|
|
99
|
+
Adjacency matrix or biadjacency matrix of the graph.
|
|
100
|
+
index : np.ndarray
|
|
101
|
+
Index of source nodes to consider. If ``None``, the links are predicted for all nodes.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
self: :class:`NN`
|
|
106
|
+
"""
|
|
107
|
+
n_row, _ = input_matrix.shape
|
|
108
|
+
|
|
109
|
+
adjacency, self.bipartite = get_adjacency(input_matrix)
|
|
110
|
+
|
|
111
|
+
if index is None:
|
|
112
|
+
index = np.arange(n_row)
|
|
113
|
+
mask = np.zeros(n_row, dtype=bool)
|
|
114
|
+
mask[index] = 1
|
|
115
|
+
|
|
116
|
+
if self.embedding_method is None:
|
|
117
|
+
embedding = adjacency
|
|
118
|
+
else:
|
|
119
|
+
embedding = self.embedding_method.fit_transform(adjacency)
|
|
120
|
+
|
|
121
|
+
embedding = normalize(embedding, p=2)
|
|
122
|
+
links = self._fit_core(embedding, mask)
|
|
123
|
+
|
|
124
|
+
self.links_ = links
|
|
125
|
+
|
|
126
|
+
return self
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""tests for link prediction"""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for link prediction by nearest neighbors"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sknetwork.linkpred import NNLinker
|
|
7
|
+
from sknetwork.data.test_graphs import *
|
|
8
|
+
from sknetwork.embedding import Spectral
|
|
9
|
+
from sknetwork.utils import get_degrees
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestNNLinker(unittest.TestCase):
|
|
13
|
+
|
|
14
|
+
def test_link_prediction(self):
|
|
15
|
+
for input_matrix in [test_graph(), test_digraph(), test_bigraph()]:
|
|
16
|
+
|
|
17
|
+
n_neighbors = 5
|
|
18
|
+
threshold = 0.2
|
|
19
|
+
algo = NNLinker(n_neighbors=n_neighbors, threshold=threshold)
|
|
20
|
+
links = algo.fit_predict(input_matrix)
|
|
21
|
+
self.assertTrue(links.shape == input_matrix.shape)
|
|
22
|
+
self.assertTrue(np.all(get_degrees(links) <= n_neighbors))
|
|
23
|
+
self.assertTrue(np.all(links.data >= threshold))
|
|
24
|
+
|
|
25
|
+
algo = NNLinker(embedding_method=Spectral(2))
|
|
26
|
+
links = algo.fit_predict(input_matrix)
|
|
27
|
+
self.assertTrue(links.shape == input_matrix.shape)
|
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/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
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created in May 2023
|
|
5
|
+
"""
|
|
6
|
+
import numpy as np
|
|
7
|
+
from scipy import sparse
|
|
8
|
+
|
|
9
|
+
from sknetwork.path.distances import get_distances
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def breadth_first_search(adjacency: sparse.csr_matrix, source: int):
|
|
13
|
+
"""Breadth-first ordering starting from some node.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
adjacency :
|
|
18
|
+
Adjacency matrix of the graph.
|
|
19
|
+
source : int
|
|
20
|
+
Source node.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
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.
|
|
27
|
+
"""
|
|
28
|
+
distances = get_distances(adjacency, source)
|
|
29
|
+
indices = np.argsort(distances)
|
|
30
|
+
n = np.sum(distances < 0)
|
|
31
|
+
return indices[n:]
|