scikit-network 0.30.0__cp39-cp39-win_amd64.whl → 0.32.1__cp39-cp39-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.

Files changed (187) hide show
  1. {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/AUTHORS.rst +3 -0
  2. {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/METADATA +31 -3
  3. scikit_network-0.32.1.dist-info/RECORD +228 -0
  4. {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/WHEEL +1 -1
  5. sknetwork/__init__.py +1 -1
  6. sknetwork/base.py +67 -0
  7. sknetwork/classification/base.py +24 -24
  8. sknetwork/classification/base_rank.py +17 -25
  9. sknetwork/classification/diffusion.py +35 -35
  10. sknetwork/classification/knn.py +24 -21
  11. sknetwork/classification/metrics.py +1 -1
  12. sknetwork/classification/pagerank.py +10 -10
  13. sknetwork/classification/propagation.py +23 -20
  14. sknetwork/classification/tests/test_diffusion.py +13 -3
  15. sknetwork/classification/vote.cp39-win_amd64.pyd +0 -0
  16. sknetwork/classification/vote.cpp +14482 -10351
  17. sknetwork/classification/vote.pyx +1 -3
  18. sknetwork/clustering/__init__.py +3 -1
  19. sknetwork/clustering/base.py +36 -40
  20. sknetwork/clustering/kcenters.py +253 -0
  21. sknetwork/clustering/leiden.py +241 -0
  22. sknetwork/clustering/leiden_core.cp39-win_amd64.pyd +0 -0
  23. sknetwork/clustering/leiden_core.cpp +31564 -0
  24. sknetwork/clustering/leiden_core.pyx +124 -0
  25. sknetwork/clustering/louvain.py +133 -102
  26. sknetwork/clustering/louvain_core.cp39-win_amd64.pyd +0 -0
  27. sknetwork/clustering/louvain_core.cpp +22457 -18792
  28. sknetwork/clustering/louvain_core.pyx +86 -96
  29. sknetwork/clustering/postprocess.py +2 -2
  30. sknetwork/clustering/propagation_clustering.py +15 -19
  31. sknetwork/clustering/tests/test_API.py +8 -4
  32. sknetwork/clustering/tests/test_kcenters.py +92 -0
  33. sknetwork/clustering/tests/test_leiden.py +34 -0
  34. sknetwork/clustering/tests/test_louvain.py +3 -4
  35. sknetwork/data/__init__.py +2 -1
  36. sknetwork/data/base.py +28 -0
  37. sknetwork/data/load.py +38 -37
  38. sknetwork/data/models.py +18 -18
  39. sknetwork/data/parse.py +54 -33
  40. sknetwork/data/test_graphs.py +2 -2
  41. sknetwork/data/tests/test_API.py +1 -1
  42. sknetwork/data/tests/test_base.py +14 -0
  43. sknetwork/data/tests/test_load.py +1 -1
  44. sknetwork/data/tests/test_parse.py +9 -12
  45. sknetwork/data/tests/test_test_graphs.py +1 -2
  46. sknetwork/data/toy_graphs.py +18 -18
  47. sknetwork/embedding/__init__.py +0 -1
  48. sknetwork/embedding/base.py +21 -20
  49. sknetwork/embedding/force_atlas.py +3 -2
  50. sknetwork/embedding/louvain_embedding.py +2 -2
  51. sknetwork/embedding/random_projection.py +5 -3
  52. sknetwork/embedding/spectral.py +0 -73
  53. sknetwork/embedding/tests/test_API.py +4 -28
  54. sknetwork/embedding/tests/test_louvain_embedding.py +4 -9
  55. sknetwork/embedding/tests/test_random_projection.py +2 -2
  56. sknetwork/embedding/tests/test_spectral.py +5 -8
  57. sknetwork/embedding/tests/test_svd.py +1 -1
  58. sknetwork/gnn/base.py +4 -4
  59. sknetwork/gnn/base_layer.py +3 -3
  60. sknetwork/gnn/gnn_classifier.py +45 -89
  61. sknetwork/gnn/layer.py +1 -1
  62. sknetwork/gnn/loss.py +1 -1
  63. sknetwork/gnn/optimizer.py +4 -3
  64. sknetwork/gnn/tests/test_base_layer.py +4 -4
  65. sknetwork/gnn/tests/test_gnn_classifier.py +12 -35
  66. sknetwork/gnn/utils.py +8 -8
  67. sknetwork/hierarchy/base.py +29 -2
  68. sknetwork/hierarchy/louvain_hierarchy.py +45 -41
  69. sknetwork/hierarchy/paris.cp39-win_amd64.pyd +0 -0
  70. sknetwork/hierarchy/paris.cpp +27369 -22852
  71. sknetwork/hierarchy/paris.pyx +7 -9
  72. sknetwork/hierarchy/postprocess.py +16 -16
  73. sknetwork/hierarchy/tests/test_API.py +1 -1
  74. sknetwork/hierarchy/tests/test_algos.py +5 -0
  75. sknetwork/hierarchy/tests/test_metrics.py +1 -1
  76. sknetwork/linalg/__init__.py +1 -1
  77. sknetwork/linalg/diteration.cp39-win_amd64.pyd +0 -0
  78. sknetwork/linalg/diteration.cpp +13474 -9454
  79. sknetwork/linalg/diteration.pyx +0 -2
  80. sknetwork/linalg/eig_solver.py +1 -1
  81. sknetwork/linalg/{normalization.py → normalizer.py} +18 -15
  82. sknetwork/linalg/operators.py +1 -1
  83. sknetwork/linalg/ppr_solver.py +1 -1
  84. sknetwork/linalg/push.cp39-win_amd64.pyd +0 -0
  85. sknetwork/linalg/push.cpp +22993 -18807
  86. sknetwork/linalg/push.pyx +0 -2
  87. sknetwork/linalg/svd_solver.py +1 -1
  88. sknetwork/linalg/tests/test_normalization.py +3 -7
  89. sknetwork/linalg/tests/test_operators.py +4 -8
  90. sknetwork/linalg/tests/test_ppr.py +1 -1
  91. sknetwork/linkpred/base.py +13 -2
  92. sknetwork/linkpred/nn.py +6 -6
  93. sknetwork/log.py +19 -0
  94. sknetwork/path/__init__.py +4 -3
  95. sknetwork/path/dag.py +54 -0
  96. sknetwork/path/distances.py +98 -0
  97. sknetwork/path/search.py +13 -47
  98. sknetwork/path/shortest_path.py +37 -162
  99. sknetwork/path/tests/test_dag.py +37 -0
  100. sknetwork/path/tests/test_distances.py +62 -0
  101. sknetwork/path/tests/test_search.py +26 -11
  102. sknetwork/path/tests/test_shortest_path.py +31 -36
  103. sknetwork/ranking/__init__.py +0 -1
  104. sknetwork/ranking/base.py +13 -8
  105. sknetwork/ranking/betweenness.cp39-win_amd64.pyd +0 -0
  106. sknetwork/ranking/betweenness.cpp +5709 -3017
  107. sknetwork/ranking/betweenness.pyx +0 -2
  108. sknetwork/ranking/closeness.py +7 -10
  109. sknetwork/ranking/pagerank.py +14 -14
  110. sknetwork/ranking/postprocess.py +12 -3
  111. sknetwork/ranking/tests/test_API.py +2 -4
  112. sknetwork/ranking/tests/test_betweenness.py +3 -3
  113. sknetwork/ranking/tests/test_closeness.py +3 -7
  114. sknetwork/ranking/tests/test_pagerank.py +11 -5
  115. sknetwork/ranking/tests/test_postprocess.py +5 -0
  116. sknetwork/regression/base.py +19 -2
  117. sknetwork/regression/diffusion.py +24 -10
  118. sknetwork/regression/tests/test_diffusion.py +8 -0
  119. sknetwork/test_base.py +35 -0
  120. sknetwork/test_log.py +15 -0
  121. sknetwork/topology/__init__.py +7 -8
  122. sknetwork/topology/cliques.cp39-win_amd64.pyd +0 -0
  123. sknetwork/topology/{kcliques.cpp → cliques.cpp} +23412 -20276
  124. sknetwork/topology/cliques.pyx +149 -0
  125. sknetwork/topology/core.cp39-win_amd64.pyd +0 -0
  126. sknetwork/topology/{kcore.cpp → core.cpp} +21732 -18867
  127. sknetwork/topology/core.pyx +90 -0
  128. sknetwork/topology/cycles.py +243 -0
  129. sknetwork/topology/minheap.cp39-win_amd64.pyd +0 -0
  130. sknetwork/{utils → topology}/minheap.cpp +19452 -15368
  131. sknetwork/{utils → topology}/minheap.pxd +1 -3
  132. sknetwork/{utils → topology}/minheap.pyx +1 -3
  133. sknetwork/topology/structure.py +3 -43
  134. sknetwork/topology/tests/test_cliques.py +11 -11
  135. sknetwork/topology/tests/test_core.py +19 -0
  136. sknetwork/topology/tests/test_cycles.py +65 -0
  137. sknetwork/topology/tests/test_structure.py +2 -16
  138. sknetwork/topology/tests/test_triangles.py +11 -15
  139. sknetwork/topology/tests/test_wl.py +72 -0
  140. sknetwork/topology/triangles.cp39-win_amd64.pyd +0 -0
  141. sknetwork/topology/triangles.cpp +5056 -2696
  142. sknetwork/topology/triangles.pyx +74 -89
  143. sknetwork/topology/weisfeiler_lehman.py +56 -86
  144. sknetwork/topology/weisfeiler_lehman_core.cp39-win_amd64.pyd +0 -0
  145. sknetwork/topology/weisfeiler_lehman_core.cpp +14727 -10622
  146. sknetwork/topology/weisfeiler_lehman_core.pyx +0 -2
  147. sknetwork/utils/__init__.py +1 -31
  148. sknetwork/utils/check.py +2 -2
  149. sknetwork/utils/format.py +5 -3
  150. sknetwork/utils/membership.py +2 -2
  151. sknetwork/utils/tests/test_check.py +3 -3
  152. sknetwork/utils/tests/test_format.py +3 -1
  153. sknetwork/utils/values.py +1 -1
  154. sknetwork/visualization/__init__.py +2 -2
  155. sknetwork/visualization/dendrograms.py +55 -7
  156. sknetwork/visualization/graphs.py +292 -72
  157. sknetwork/visualization/tests/test_dendrograms.py +9 -9
  158. sknetwork/visualization/tests/test_graphs.py +71 -62
  159. scikit_network-0.30.0.dist-info/RECORD +0 -227
  160. sknetwork/embedding/louvain_hierarchy.py +0 -142
  161. sknetwork/embedding/tests/test_louvain_hierarchy.py +0 -19
  162. sknetwork/path/metrics.py +0 -148
  163. sknetwork/path/tests/test_metrics.py +0 -29
  164. sknetwork/ranking/harmonic.py +0 -82
  165. sknetwork/topology/dag.py +0 -74
  166. sknetwork/topology/dag_core.cp39-win_amd64.pyd +0 -0
  167. sknetwork/topology/dag_core.cpp +0 -23350
  168. sknetwork/topology/dag_core.pyx +0 -38
  169. sknetwork/topology/kcliques.cp39-win_amd64.pyd +0 -0
  170. sknetwork/topology/kcliques.pyx +0 -193
  171. sknetwork/topology/kcore.cp39-win_amd64.pyd +0 -0
  172. sknetwork/topology/kcore.pyx +0 -120
  173. sknetwork/topology/tests/test_cores.py +0 -21
  174. sknetwork/topology/tests/test_dag.py +0 -26
  175. sknetwork/topology/tests/test_wl_coloring.py +0 -49
  176. sknetwork/topology/tests/test_wl_kernel.py +0 -31
  177. sknetwork/utils/base.py +0 -35
  178. sknetwork/utils/minheap.cp39-win_amd64.pyd +0 -0
  179. sknetwork/utils/simplex.py +0 -140
  180. sknetwork/utils/tests/test_base.py +0 -28
  181. sknetwork/utils/tests/test_bunch.py +0 -16
  182. sknetwork/utils/tests/test_projection_simplex.py +0 -33
  183. sknetwork/utils/tests/test_verbose.py +0 -15
  184. sknetwork/utils/verbose.py +0 -37
  185. {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/LICENSE +0 -0
  186. {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/top_level.txt +0 -0
  187. /sknetwork/{utils → data}/timeout.py +0 -0
sknetwork/linalg/push.pyx CHANGED
@@ -1,7 +1,5 @@
1
1
  # distutils: language = c++
2
2
  # cython: language_level=3
3
- # cython: linetrace=True
4
- # distutils: define_macros=CYTHON_TRACE_NOGIL=1
5
3
  """
6
4
  Created on Mars 2021
7
5
  @author: Wenzhuo Zhao <wenzhuo.zhao@etu.sorbonne-universite.fr>
@@ -13,7 +13,7 @@ import numpy as np
13
13
  from scipy import sparse
14
14
  from scipy.sparse.linalg import svds
15
15
 
16
- from sknetwork.utils.base import Algorithm
16
+ from sknetwork.base import Algorithm
17
17
 
18
18
 
19
19
  class SVDSolver(Algorithm, ABC):
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  """
4
- Created on April 2020
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, CoNeighbor
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(NotImplementedError):
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 on Apr 2020
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, normalize
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(), test_graph_disconnect()]:
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(), test_graph_disconnect()]:
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
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
- """Tests for Louvain"""
3
+ """Tests for d-iteration"""
4
4
  import unittest
5
5
 
6
6
  import numpy as np
@@ -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.utils.base import Algorithm
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 fit_predict(self, *args, **kwargs) -> np.ndarray:
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.normalization import normalize
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'
@@ -1,4 +1,5 @@
1
1
  """Path module"""
2
- from sknetwork.path.metrics import get_diameter, get_eccentricity, get_radius
3
- from sknetwork.path.search import breadth_first_search, depth_first_search
4
- from sknetwork.path.shortest_path import get_distances, get_shortest_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 on Jul 24, 2019
4
+ Created in May 2023
5
5
  """
6
6
  import numpy as np
7
7
  from scipy import sparse
8
8
 
9
- from sknetwork.utils.check import is_symmetric
9
+ from sknetwork.path.distances import get_distances
10
10
 
11
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)
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
- The adjacency matrix of the graph
18
+ Adjacency matrix of the graph.
21
19
  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
20
+ Source node.
53
21
 
54
22
  Returns
55
23
  -------
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``.
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
- directed = not is_symmetric(adjacency)
65
- return sparse.csgraph.depth_first_order(adjacency, source, directed, return_predecessors)
28
+ distances = get_distances(adjacency, source)
29
+ indices = np.argsort(distances)
30
+ n = np.sum(distances < 0)
31
+ return indices[n:]
@@ -1,186 +1,61 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  """
4
- Created on November 12, 2019
5
- @author: Quentin Lutz <qlutz@enst.fr>
4
+ Created in May 2023
5
+ @author: Thomas Bonald <bonald@enst.fr>
6
6
  """
7
- from functools import partial
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.utils.check import check_n_jobs, is_symmetric
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 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)
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
- 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.
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
- 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``).
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
- >>> 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
-
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
- 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)
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
- dists, preds = get_distances(adjacency.T, source, method, True, unweighted, n_jobs)
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