scikit-network 0.28.3__cp39-cp39-macosx_12_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of scikit-network might be problematic. Click here for more details.

Files changed (240) hide show
  1. scikit_network-0.28.3.dist-info/AUTHORS.rst +41 -0
  2. scikit_network-0.28.3.dist-info/LICENSE +34 -0
  3. scikit_network-0.28.3.dist-info/METADATA +457 -0
  4. scikit_network-0.28.3.dist-info/RECORD +240 -0
  5. scikit_network-0.28.3.dist-info/WHEEL +5 -0
  6. scikit_network-0.28.3.dist-info/top_level.txt +1 -0
  7. sknetwork/__init__.py +21 -0
  8. sknetwork/classification/__init__.py +8 -0
  9. sknetwork/classification/base.py +84 -0
  10. sknetwork/classification/base_rank.py +143 -0
  11. sknetwork/classification/diffusion.py +134 -0
  12. sknetwork/classification/knn.py +162 -0
  13. sknetwork/classification/metrics.py +205 -0
  14. sknetwork/classification/pagerank.py +66 -0
  15. sknetwork/classification/propagation.py +152 -0
  16. sknetwork/classification/tests/__init__.py +1 -0
  17. sknetwork/classification/tests/test_API.py +35 -0
  18. sknetwork/classification/tests/test_diffusion.py +37 -0
  19. sknetwork/classification/tests/test_knn.py +24 -0
  20. sknetwork/classification/tests/test_metrics.py +53 -0
  21. sknetwork/classification/tests/test_pagerank.py +20 -0
  22. sknetwork/classification/tests/test_propagation.py +24 -0
  23. sknetwork/classification/vote.cpython-39-darwin.so +0 -0
  24. sknetwork/classification/vote.pyx +58 -0
  25. sknetwork/clustering/__init__.py +7 -0
  26. sknetwork/clustering/base.py +102 -0
  27. sknetwork/clustering/kmeans.py +142 -0
  28. sknetwork/clustering/louvain.py +255 -0
  29. sknetwork/clustering/louvain_core.cpython-39-darwin.so +0 -0
  30. sknetwork/clustering/louvain_core.pyx +134 -0
  31. sknetwork/clustering/metrics.py +91 -0
  32. sknetwork/clustering/postprocess.py +66 -0
  33. sknetwork/clustering/propagation_clustering.py +108 -0
  34. sknetwork/clustering/tests/__init__.py +1 -0
  35. sknetwork/clustering/tests/test_API.py +37 -0
  36. sknetwork/clustering/tests/test_kmeans.py +47 -0
  37. sknetwork/clustering/tests/test_louvain.py +104 -0
  38. sknetwork/clustering/tests/test_metrics.py +50 -0
  39. sknetwork/clustering/tests/test_post_processing.py +23 -0
  40. sknetwork/clustering/tests/test_postprocess.py +39 -0
  41. sknetwork/data/__init__.py +5 -0
  42. sknetwork/data/load.py +408 -0
  43. sknetwork/data/models.py +459 -0
  44. sknetwork/data/parse.py +621 -0
  45. sknetwork/data/test_graphs.py +84 -0
  46. sknetwork/data/tests/__init__.py +1 -0
  47. sknetwork/data/tests/test_API.py +30 -0
  48. sknetwork/data/tests/test_load.py +95 -0
  49. sknetwork/data/tests/test_models.py +52 -0
  50. sknetwork/data/tests/test_parse.py +253 -0
  51. sknetwork/data/tests/test_test_graphs.py +30 -0
  52. sknetwork/data/tests/test_toy_graphs.py +68 -0
  53. sknetwork/data/toy_graphs.py +619 -0
  54. sknetwork/embedding/__init__.py +10 -0
  55. sknetwork/embedding/base.py +90 -0
  56. sknetwork/embedding/force_atlas.py +197 -0
  57. sknetwork/embedding/louvain_embedding.py +174 -0
  58. sknetwork/embedding/louvain_hierarchy.py +142 -0
  59. sknetwork/embedding/metrics.py +66 -0
  60. sknetwork/embedding/random_projection.py +133 -0
  61. sknetwork/embedding/spectral.py +214 -0
  62. sknetwork/embedding/spring.py +198 -0
  63. sknetwork/embedding/svd.py +363 -0
  64. sknetwork/embedding/tests/__init__.py +1 -0
  65. sknetwork/embedding/tests/test_API.py +73 -0
  66. sknetwork/embedding/tests/test_force_atlas.py +35 -0
  67. sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
  68. sknetwork/embedding/tests/test_louvain_hierarchy.py +19 -0
  69. sknetwork/embedding/tests/test_metrics.py +29 -0
  70. sknetwork/embedding/tests/test_random_projection.py +28 -0
  71. sknetwork/embedding/tests/test_spectral.py +84 -0
  72. sknetwork/embedding/tests/test_spring.py +50 -0
  73. sknetwork/embedding/tests/test_svd.py +37 -0
  74. sknetwork/flow/__init__.py +3 -0
  75. sknetwork/flow/flow.py +73 -0
  76. sknetwork/flow/tests/__init__.py +1 -0
  77. sknetwork/flow/tests/test_flow.py +17 -0
  78. sknetwork/flow/tests/test_utils.py +69 -0
  79. sknetwork/flow/utils.py +91 -0
  80. sknetwork/gnn/__init__.py +10 -0
  81. sknetwork/gnn/activation.py +117 -0
  82. sknetwork/gnn/base.py +155 -0
  83. sknetwork/gnn/base_activation.py +89 -0
  84. sknetwork/gnn/base_layer.py +109 -0
  85. sknetwork/gnn/gnn_classifier.py +381 -0
  86. sknetwork/gnn/layer.py +153 -0
  87. sknetwork/gnn/layers.py +127 -0
  88. sknetwork/gnn/loss.py +180 -0
  89. sknetwork/gnn/neighbor_sampler.py +65 -0
  90. sknetwork/gnn/optimizer.py +163 -0
  91. sknetwork/gnn/tests/__init__.py +1 -0
  92. sknetwork/gnn/tests/test_activation.py +56 -0
  93. sknetwork/gnn/tests/test_base.py +79 -0
  94. sknetwork/gnn/tests/test_base_layer.py +37 -0
  95. sknetwork/gnn/tests/test_gnn_classifier.py +192 -0
  96. sknetwork/gnn/tests/test_layers.py +80 -0
  97. sknetwork/gnn/tests/test_loss.py +33 -0
  98. sknetwork/gnn/tests/test_neigh_sampler.py +23 -0
  99. sknetwork/gnn/tests/test_optimizer.py +43 -0
  100. sknetwork/gnn/tests/test_utils.py +93 -0
  101. sknetwork/gnn/utils.py +219 -0
  102. sknetwork/hierarchy/__init__.py +7 -0
  103. sknetwork/hierarchy/base.py +69 -0
  104. sknetwork/hierarchy/louvain_hierarchy.py +264 -0
  105. sknetwork/hierarchy/metrics.py +234 -0
  106. sknetwork/hierarchy/paris.cpython-39-darwin.so +0 -0
  107. sknetwork/hierarchy/paris.pyx +317 -0
  108. sknetwork/hierarchy/postprocess.py +350 -0
  109. sknetwork/hierarchy/tests/__init__.py +1 -0
  110. sknetwork/hierarchy/tests/test_API.py +25 -0
  111. sknetwork/hierarchy/tests/test_algos.py +29 -0
  112. sknetwork/hierarchy/tests/test_metrics.py +62 -0
  113. sknetwork/hierarchy/tests/test_postprocess.py +57 -0
  114. sknetwork/hierarchy/tests/test_ward.py +25 -0
  115. sknetwork/hierarchy/ward.py +94 -0
  116. sknetwork/linalg/__init__.py +9 -0
  117. sknetwork/linalg/basics.py +37 -0
  118. sknetwork/linalg/diteration.cpython-39-darwin.so +0 -0
  119. sknetwork/linalg/diteration.pyx +49 -0
  120. sknetwork/linalg/eig_solver.py +93 -0
  121. sknetwork/linalg/laplacian.py +15 -0
  122. sknetwork/linalg/normalization.py +66 -0
  123. sknetwork/linalg/operators.py +225 -0
  124. sknetwork/linalg/polynome.py +76 -0
  125. sknetwork/linalg/ppr_solver.py +170 -0
  126. sknetwork/linalg/push.cpython-39-darwin.so +0 -0
  127. sknetwork/linalg/push.pyx +73 -0
  128. sknetwork/linalg/sparse_lowrank.py +142 -0
  129. sknetwork/linalg/svd_solver.py +91 -0
  130. sknetwork/linalg/tests/__init__.py +1 -0
  131. sknetwork/linalg/tests/test_eig.py +44 -0
  132. sknetwork/linalg/tests/test_laplacian.py +18 -0
  133. sknetwork/linalg/tests/test_normalization.py +38 -0
  134. sknetwork/linalg/tests/test_operators.py +70 -0
  135. sknetwork/linalg/tests/test_polynome.py +38 -0
  136. sknetwork/linalg/tests/test_ppr.py +50 -0
  137. sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
  138. sknetwork/linalg/tests/test_svd.py +38 -0
  139. sknetwork/linkpred/__init__.py +4 -0
  140. sknetwork/linkpred/base.py +80 -0
  141. sknetwork/linkpred/first_order.py +508 -0
  142. sknetwork/linkpred/first_order_core.cpython-39-darwin.so +0 -0
  143. sknetwork/linkpred/first_order_core.pyx +315 -0
  144. sknetwork/linkpred/postprocessing.py +98 -0
  145. sknetwork/linkpred/tests/__init__.py +1 -0
  146. sknetwork/linkpred/tests/test_API.py +49 -0
  147. sknetwork/linkpred/tests/test_postprocessing.py +21 -0
  148. sknetwork/path/__init__.py +4 -0
  149. sknetwork/path/metrics.py +148 -0
  150. sknetwork/path/search.py +65 -0
  151. sknetwork/path/shortest_path.py +186 -0
  152. sknetwork/path/tests/__init__.py +1 -0
  153. sknetwork/path/tests/test_metrics.py +29 -0
  154. sknetwork/path/tests/test_search.py +25 -0
  155. sknetwork/path/tests/test_shortest_path.py +45 -0
  156. sknetwork/ranking/__init__.py +9 -0
  157. sknetwork/ranking/base.py +56 -0
  158. sknetwork/ranking/betweenness.cpython-39-darwin.so +0 -0
  159. sknetwork/ranking/betweenness.pyx +99 -0
  160. sknetwork/ranking/closeness.py +95 -0
  161. sknetwork/ranking/harmonic.py +82 -0
  162. sknetwork/ranking/hits.py +94 -0
  163. sknetwork/ranking/katz.py +81 -0
  164. sknetwork/ranking/pagerank.py +107 -0
  165. sknetwork/ranking/postprocess.py +25 -0
  166. sknetwork/ranking/tests/__init__.py +1 -0
  167. sknetwork/ranking/tests/test_API.py +34 -0
  168. sknetwork/ranking/tests/test_betweenness.py +38 -0
  169. sknetwork/ranking/tests/test_closeness.py +34 -0
  170. sknetwork/ranking/tests/test_hits.py +20 -0
  171. sknetwork/ranking/tests/test_pagerank.py +69 -0
  172. sknetwork/regression/__init__.py +4 -0
  173. sknetwork/regression/base.py +56 -0
  174. sknetwork/regression/diffusion.py +190 -0
  175. sknetwork/regression/tests/__init__.py +1 -0
  176. sknetwork/regression/tests/test_API.py +34 -0
  177. sknetwork/regression/tests/test_diffusion.py +48 -0
  178. sknetwork/sknetwork.py +3 -0
  179. sknetwork/topology/__init__.py +9 -0
  180. sknetwork/topology/dag.py +74 -0
  181. sknetwork/topology/dag_core.cpython-39-darwin.so +0 -0
  182. sknetwork/topology/dag_core.pyx +38 -0
  183. sknetwork/topology/kcliques.cpython-39-darwin.so +0 -0
  184. sknetwork/topology/kcliques.pyx +193 -0
  185. sknetwork/topology/kcore.cpython-39-darwin.so +0 -0
  186. sknetwork/topology/kcore.pyx +120 -0
  187. sknetwork/topology/structure.py +234 -0
  188. sknetwork/topology/tests/__init__.py +1 -0
  189. sknetwork/topology/tests/test_cliques.py +28 -0
  190. sknetwork/topology/tests/test_cores.py +21 -0
  191. sknetwork/topology/tests/test_dag.py +26 -0
  192. sknetwork/topology/tests/test_structure.py +99 -0
  193. sknetwork/topology/tests/test_triangles.py +42 -0
  194. sknetwork/topology/tests/test_wl_coloring.py +49 -0
  195. sknetwork/topology/tests/test_wl_kernel.py +31 -0
  196. sknetwork/topology/triangles.cpython-39-darwin.so +0 -0
  197. sknetwork/topology/triangles.pyx +166 -0
  198. sknetwork/topology/weisfeiler_lehman.py +163 -0
  199. sknetwork/topology/weisfeiler_lehman_core.cpython-39-darwin.so +0 -0
  200. sknetwork/topology/weisfeiler_lehman_core.pyx +116 -0
  201. sknetwork/utils/__init__.py +40 -0
  202. sknetwork/utils/base.py +35 -0
  203. sknetwork/utils/check.py +354 -0
  204. sknetwork/utils/co_neighbor.py +71 -0
  205. sknetwork/utils/format.py +219 -0
  206. sknetwork/utils/kmeans.py +89 -0
  207. sknetwork/utils/knn.py +166 -0
  208. sknetwork/utils/knn1d.cpython-39-darwin.so +0 -0
  209. sknetwork/utils/knn1d.pyx +80 -0
  210. sknetwork/utils/membership.py +82 -0
  211. sknetwork/utils/minheap.cpython-39-darwin.so +0 -0
  212. sknetwork/utils/minheap.pxd +22 -0
  213. sknetwork/utils/minheap.pyx +111 -0
  214. sknetwork/utils/neighbors.py +115 -0
  215. sknetwork/utils/seeds.py +75 -0
  216. sknetwork/utils/simplex.py +140 -0
  217. sknetwork/utils/tests/__init__.py +1 -0
  218. sknetwork/utils/tests/test_base.py +28 -0
  219. sknetwork/utils/tests/test_bunch.py +16 -0
  220. sknetwork/utils/tests/test_check.py +190 -0
  221. sknetwork/utils/tests/test_co_neighbor.py +43 -0
  222. sknetwork/utils/tests/test_format.py +61 -0
  223. sknetwork/utils/tests/test_kmeans.py +21 -0
  224. sknetwork/utils/tests/test_knn.py +32 -0
  225. sknetwork/utils/tests/test_membership.py +24 -0
  226. sknetwork/utils/tests/test_neighbors.py +41 -0
  227. sknetwork/utils/tests/test_projection_simplex.py +33 -0
  228. sknetwork/utils/tests/test_seeds.py +67 -0
  229. sknetwork/utils/tests/test_verbose.py +15 -0
  230. sknetwork/utils/tests/test_ward.py +20 -0
  231. sknetwork/utils/timeout.py +38 -0
  232. sknetwork/utils/verbose.py +37 -0
  233. sknetwork/utils/ward.py +60 -0
  234. sknetwork/visualization/__init__.py +4 -0
  235. sknetwork/visualization/colors.py +34 -0
  236. sknetwork/visualization/dendrograms.py +229 -0
  237. sknetwork/visualization/graphs.py +819 -0
  238. sknetwork/visualization/tests/__init__.py +1 -0
  239. sknetwork/visualization/tests/test_dendrograms.py +53 -0
  240. sknetwork/visualization/tests/test_graphs.py +167 -0
@@ -0,0 +1,120 @@
1
+ # distutils: language = c++
2
+ # cython: language_level=3
3
+ # cython: linetrace=True
4
+ # distutils: define_macros=CYTHON_TRACE_NOGIL=1
5
+ """
6
+ Created on Jun 3, 2020
7
+ @author: Julien Simonnet <julien.simonnet@etu.upmc.fr>
8
+ @author: Yohann Robert <yohann.robert@etu.upmc.fr>
9
+ """
10
+ cimport cython
11
+
12
+ import numpy as np
13
+ cimport numpy as np
14
+
15
+ from scipy import sparse
16
+
17
+ from sknetwork.utils.base import Algorithm
18
+ from sknetwork.utils.minheap cimport MinHeap
19
+
20
+
21
+ @cython.boundscheck(False)
22
+ @cython.wraparound(False)
23
+ cdef fit_core(int[:] indptr, int[:] indices):
24
+ """Compute the core value of each node.
25
+
26
+ Parameters
27
+ ----------
28
+ indptr :
29
+ CSR format index array of the normalized adjacency matrix.
30
+ indices :
31
+ CSR format index pointer array of the normalized adjacency matrix.
32
+
33
+ Returns
34
+ -------
35
+ labels :
36
+ Core value of each node.
37
+ """
38
+ cdef int n = indptr.shape[0] - 1
39
+ cdef int core_value = 0 # current/max core value of the graph
40
+ cdef int min_node # current node of minimum degree
41
+ cdef int i, j, k
42
+ cdef int[:] degrees = np.asarray(indptr)[1:] - np.asarray(indptr)[:n]
43
+ cdef np.ndarray[int, ndim=1] labels = np.empty((n,), dtype=np.int32)
44
+ cdef MinHeap mh = MinHeap.__new__(MinHeap, n) # minimum heap with an update system
45
+
46
+ # inserts all nodes in the heap
47
+ for i in range(n):
48
+ mh.insert_key(i, degrees)
49
+
50
+ i = n - 1 # index of the rear of the list/array
51
+ while not mh.empty(): # until the heap is emptied
52
+ min_node = mh.pop_min(degrees)
53
+ core_value = max(core_value, degrees[min_node])
54
+
55
+ # decreases the degree of each neighbors of min_node to simulate its deletion
56
+ for k in range(indptr[min_node], indptr[min_node+1]):
57
+ j = indices[k]
58
+ degrees[j] -= 1
59
+ mh.decrease_key(j, degrees) # updates the heap to take into account the new degrees
60
+
61
+ labels[min_node] = core_value
62
+ i -= 1
63
+
64
+ return np.asarray(labels)
65
+
66
+
67
+ class CoreDecomposition(Algorithm):
68
+ """K-core decomposition algorithm.
69
+
70
+ * Graphs
71
+
72
+ Attributes
73
+ ----------
74
+ labels_ : np.ndarray
75
+ Core value of each node.
76
+ core_value_ : int
77
+ Maximum core value of the graph
78
+
79
+ Example
80
+ -------
81
+ >>> from sknetwork.topology import CoreDecomposition
82
+ >>> from sknetwork.data import karate_club
83
+ >>> kcore = CoreDecomposition()
84
+ >>> adjacency = karate_club()
85
+ >>> kcore.fit(adjacency)
86
+ >>> kcore.core_value_
87
+ 4
88
+ """
89
+ def __init__(self):
90
+ super(CoreDecomposition, self).__init__()
91
+ self.labels_ = None
92
+ self.core_value_ = None
93
+
94
+ def fit(self, adjacency: sparse.csr_matrix) -> 'CoreDecomposition':
95
+ """K-core decomposition.
96
+
97
+ Parameters
98
+ ----------
99
+ adjacency :
100
+ Adjacency matrix of the graph.
101
+
102
+ Returns
103
+ -------
104
+ self: :class:`CoreDecomposition`
105
+ """
106
+ labels = fit_core(adjacency.indptr, adjacency.indices)
107
+ self.labels_ = labels
108
+ self.core_value_ = labels.max()
109
+ return self
110
+
111
+ def fit_transform(self, adjacency: sparse.csr_matrix):
112
+ """Fit algorithm to the data and return the core value of each node. Same parameters as the ``fit`` method.
113
+
114
+ Returns
115
+ -------
116
+ labels :
117
+ Core value of the nodes.
118
+ """
119
+ self.fit(adjacency)
120
+ return self.labels_
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on July 24, 2019
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ @author: Quentin Lutz <qlutz@enst.fr>
7
+ @author: Thomas Bonald <tbonald@enst.fr>
8
+ """
9
+ from typing import Tuple, Optional, Union
10
+
11
+ import numpy as np
12
+ from scipy import sparse
13
+
14
+ from sknetwork.utils.check import is_symmetric, check_format
15
+ from sknetwork.utils.format import get_adjacency
16
+
17
+
18
+ def get_connected_components(input_matrix: sparse.csr_matrix, connection: str = 'weak', force_bipartite: bool = False) \
19
+ -> np.ndarray:
20
+ """Extract the connected components of a graph.
21
+
22
+ Parameters
23
+ ----------
24
+ input_matrix :
25
+ Input matrix (either the adjacency matrix or the biadjacency matrix of the graph).
26
+ connection :
27
+ Must be ``'weak'`` (default) or ``'strong'``. The type of connection to use for directed graphs.
28
+ force_bipartite : bool
29
+ If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
30
+
31
+ Returns
32
+ -------
33
+ labels :
34
+ Connected component of each node.
35
+ For bipartite graphs, rows and columns are concatenated (rows first).
36
+
37
+ Example
38
+ -------
39
+ >>> from sknetwork.topology import get_connected_components
40
+ >>> from sknetwork.data import house
41
+ >>> get_connected_components(house())
42
+ array([0, 0, 0, 0, 0], dtype=int32)
43
+ """
44
+ input_matrix = check_format(input_matrix)
45
+ if len(input_matrix.data) == 0:
46
+ raise ValueError('The graph is empty (no edge).')
47
+ adjacency, _ = get_adjacency(input_matrix, force_bipartite=force_bipartite)
48
+ labels = sparse.csgraph.connected_components(adjacency, connection=connection, return_labels=True)[1]
49
+ return labels
50
+
51
+
52
+ def is_connected(input_matrix: sparse.csr_matrix, connection: str = 'weak', force_bipartite: bool = False) -> bool:
53
+ """Check whether the graph is connected.
54
+
55
+ Parameters
56
+ ----------
57
+ input_matrix :
58
+ Input matrix (either the adjacency matrix or the biadjacency matrix of the graph).
59
+ connection :
60
+ Must be ``'weak'`` (default) or ``'strong'``. The type of connection to use for directed graphs.
61
+ force_bipartite : bool
62
+ If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
63
+
64
+ Example
65
+ -------
66
+ >>> from sknetwork.topology import is_connected
67
+ >>> from sknetwork.data import house
68
+ >>> is_connected(house())
69
+ True
70
+ """
71
+ return len(set(get_connected_components(input_matrix, connection, force_bipartite))) == 1
72
+
73
+
74
+ def get_largest_connected_component(input_matrix: sparse.csr_matrix, connection: str = "weak",
75
+ force_bipartite: bool = False, return_index: bool = False) \
76
+ -> Union[sparse.csr_matrix, Tuple[sparse.csr_matrix, np.ndarray]]:
77
+ """Extract the largest connected component of a graph. Bipartite graphs are treated as undirected.
78
+
79
+ Parameters
80
+ ----------
81
+ input_matrix :
82
+ Adjacency matrix or biadjacency matrix of the graph.
83
+ connection :
84
+ Must be ``'weak'`` (default) or ``'strong'``. The type of connection to use for directed graphs.
85
+ force_bipartite : bool
86
+ If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
87
+ return_index : bool
88
+ Whether to return the index of the nodes of the largest connected component in the original graph.
89
+
90
+ Returns
91
+ -------
92
+ output_matrix : sparse.csr_matrix
93
+ Adjacency matrix or biadjacency matrix of the largest connected component.
94
+ index : array
95
+ Indices of the nodes in the original graph.
96
+ For bipartite graphs, rows and columns are concatenated (rows first).
97
+
98
+ Example
99
+ -------
100
+ >>> from sknetwork.topology import get_largest_connected_component
101
+ >>> from sknetwork.data import house
102
+ >>> get_largest_connected_component(house()).shape
103
+ (5, 5)
104
+ """
105
+ input_matrix = check_format(input_matrix)
106
+ adjacency, bipartite = get_adjacency(input_matrix, force_bipartite=force_bipartite)
107
+ labels = get_connected_components(adjacency, connection=connection)
108
+ unique_labels, counts = np.unique(labels, return_counts=True)
109
+ largest_component_label = unique_labels[np.argmax(counts)]
110
+
111
+ if bipartite:
112
+ n_row, n_col = input_matrix.shape
113
+ index_row = np.argwhere(labels[:n_row] == largest_component_label).ravel()
114
+ index_col = np.argwhere(labels[n_row:] == largest_component_label).ravel()
115
+ index = np.hstack((index_row, index_col))
116
+ output_matrix = input_matrix[index_row, :]
117
+ output_matrix = (output_matrix.tocsc()[:, index_col]).tocsr()
118
+ else:
119
+ index = np.argwhere(labels == largest_component_label).ravel()
120
+ output_matrix = input_matrix[index, :]
121
+ output_matrix = (output_matrix.tocsc()[:, index]).tocsr()
122
+ if return_index:
123
+ return output_matrix, index
124
+ else:
125
+ return output_matrix
126
+
127
+
128
+ def is_bipartite(adjacency: sparse.csr_matrix, return_biadjacency: bool = False) \
129
+ -> Union[bool, Tuple[bool, Optional[sparse.csr_matrix], Optional[np.ndarray], Optional[np.ndarray]]]:
130
+ """Check whether a graph is bipartite.
131
+
132
+ Parameters
133
+ ----------
134
+ adjacency :
135
+ Adjacency matrix of the graph (symmetric).
136
+ return_biadjacency :
137
+ If ``True``, return a biadjacency matrix of the graph if bipartite.
138
+
139
+ Returns
140
+ -------
141
+ is_bipartite : bool
142
+ A boolean denoting if the graph is bipartite.
143
+ biadjacency : sparse.csr_matrix
144
+ A biadjacency matrix of the graph if bipartite (optional).
145
+ rows : np.ndarray
146
+ Index of rows in the original graph (optional).
147
+ cols : np.ndarray
148
+ Index of columns in the original graph (optional).
149
+
150
+ Example
151
+ -------
152
+ >>> from sknetwork.topology import is_bipartite
153
+ >>> from sknetwork.data import cyclic_graph
154
+ >>> is_bipartite(cyclic_graph(4))
155
+ True
156
+ >>> is_bipartite(cyclic_graph(3))
157
+ False
158
+ """
159
+ if not is_symmetric(adjacency):
160
+ raise ValueError('The graph must be undirected.')
161
+ if adjacency.diagonal().any():
162
+ if return_biadjacency:
163
+ return False, None, None, None
164
+ else:
165
+ return False
166
+ n = adjacency.indptr.shape[0] - 1
167
+ coloring = np.full(n, -1, dtype=int)
168
+ exists_remaining = n
169
+ while exists_remaining:
170
+ src = np.argwhere(coloring == -1)[0, 0]
171
+ next_nodes = [src]
172
+ coloring[src] = 0
173
+ exists_remaining -= 1
174
+ while next_nodes:
175
+ node = next_nodes.pop()
176
+ for neighbor in adjacency.indices[adjacency.indptr[node]:adjacency.indptr[node + 1]]:
177
+ if coloring[neighbor] == -1:
178
+ coloring[neighbor] = 1 - coloring[node]
179
+ next_nodes.append(neighbor)
180
+ exists_remaining -= 1
181
+ elif coloring[neighbor] == coloring[node]:
182
+ if return_biadjacency:
183
+ return False, None, None, None
184
+ else:
185
+ return False
186
+ if return_biadjacency:
187
+ rows = np.argwhere(coloring == 0).ravel()
188
+ cols = np.argwhere(coloring == 1).ravel()
189
+ return True, adjacency[rows, :][:, cols], rows, cols
190
+ else:
191
+ return True
192
+
193
+
194
+ def is_acyclic(adjacency: sparse.csr_matrix, directed: Optional[bool] = None) -> bool:
195
+ """Check whether a graph has no cycle.
196
+
197
+ Parameters
198
+ ----------
199
+ adjacency:
200
+ Adjacency matrix of the graph.
201
+ directed:
202
+ Whether to consider the graph as directed (inferred if not specified).
203
+ Returns
204
+ -------
205
+ is_acyclic : bool
206
+ A boolean with value True if the graph has no cycle and False otherwise.
207
+
208
+ Example
209
+ -------
210
+ >>> from sknetwork.topology import is_acyclic
211
+ >>> from sknetwork.data import star, grid
212
+ >>> is_acyclic(star())
213
+ True
214
+ >>> is_acyclic(grid())
215
+ False
216
+ """
217
+ if directed is False:
218
+ # the graph must be undirected
219
+ if not is_symmetric(adjacency):
220
+ raise ValueError("The adjacency matrix is not symmetric. The parameter 'directed' must be True.")
221
+ elif directed is None:
222
+ # if not specified, infer from the graph
223
+ directed = not is_symmetric(adjacency)
224
+ has_loops = (adjacency.diagonal() > 0).any()
225
+ if has_loops:
226
+ return False
227
+ else:
228
+ n_cc = sparse.csgraph.connected_components(adjacency, directed, connection='strong', return_labels=False)
229
+ n_nodes = adjacency.shape[0]
230
+ if directed:
231
+ return n_cc == n_nodes
232
+ else:
233
+ n_edges = adjacency.nnz // 2
234
+ return n_cc == n_nodes - n_edges
@@ -0,0 +1 @@
1
+ """tests for topology"""
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tests for k-cliques count"""
4
+ import unittest
5
+
6
+ from scipy.special import comb
7
+
8
+ from sknetwork.data.test_graphs import *
9
+ from sknetwork.topology import Cliques
10
+
11
+
12
+ class TestCliqueListing(unittest.TestCase):
13
+
14
+ def test_empty(self):
15
+ adjacency = test_graph_empty()
16
+ self.assertEqual(Cliques(2).fit_transform(adjacency), 0)
17
+ cliques = Cliques(1)
18
+ self.assertRaises(ValueError, cliques.fit_transform, adjacency)
19
+
20
+ def test_disconnected(self):
21
+ adjacency = test_graph_disconnect()
22
+ self.assertEqual(Cliques(3).fit_transform(adjacency), 1)
23
+
24
+ def test_cliques(self):
25
+ adjacency = test_graph_clique()
26
+ n = adjacency.shape[0]
27
+ self.assertEqual(Cliques(3).fit_transform(adjacency), comb(n, 3, exact=True))
28
+ self.assertEqual(Cliques(4).fit_transform(adjacency), comb(n, 4, exact=True))
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tests for k-core decimposition"""
4
+ import unittest
5
+
6
+ from sknetwork.data.test_graphs import *
7
+ from sknetwork.topology import CoreDecomposition
8
+
9
+
10
+ class TestCoreDecomposition(unittest.TestCase):
11
+
12
+ def test_empty(self):
13
+ adjacency = test_graph_empty()
14
+ kcore = CoreDecomposition().fit(adjacency)
15
+ self.assertEqual(kcore.core_value_, 0)
16
+
17
+ def test_cliques(self):
18
+ adjacency = test_graph_clique()
19
+ n = adjacency.shape[0]
20
+ kcore = CoreDecomposition().fit(adjacency)
21
+ self.assertEqual(kcore.core_value_, n - 1)
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """"tests for dag.py"""
4
+ import unittest
5
+
6
+ import numpy as np
7
+
8
+ from sknetwork.data import house
9
+ from sknetwork.topology import DAG
10
+
11
+
12
+ class TestDAG(unittest.TestCase):
13
+
14
+ def test_options(self):
15
+ adjacency = house()
16
+ dag = DAG()
17
+ dag.fit(adjacency)
18
+ self.assertEqual(dag.indptr_.shape[0], adjacency.shape[0]+1)
19
+ self.assertEqual(dag.indices_.shape[0], 6)
20
+
21
+ with self.assertRaises(ValueError):
22
+ dag.fit(adjacency, sorted_nodes=np.arange(3))
23
+
24
+ with self.assertRaises(ValueError):
25
+ dag = DAG(ordering='toto')
26
+ dag.fit(adjacency)
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """"tests for structure.py"""
4
+ import unittest
5
+
6
+ import numpy as np
7
+ from scipy import sparse
8
+
9
+ from sknetwork.data import star_wars, cyclic_digraph, linear_digraph, linear_graph
10
+ from sknetwork.topology import get_connected_components, get_largest_connected_component
11
+ from sknetwork.topology import is_connected, is_bipartite, is_acyclic
12
+ from sknetwork.utils.format import bipartite2undirected, directed2undirected
13
+
14
+
15
+ class TestStructure(unittest.TestCase):
16
+
17
+ def test_cc(self):
18
+ adjacency = linear_digraph(3)
19
+ labels = get_connected_components(adjacency, connection='weak')
20
+ self.assertEqual(len(set(labels)), 1)
21
+ adjacency = linear_digraph(3)
22
+ labels = get_connected_components(adjacency, connection='weak', force_bipartite=True)
23
+ self.assertEqual(len(set(labels)), 4)
24
+ biadjacency = sparse.csr_matrix(([1, 1, 1, 1], [[1, 2, 3, 3], [1, 2, 2, 3]]))
25
+ labels = get_connected_components(biadjacency)
26
+ self.assertEqual(len(set(labels)), 3)
27
+
28
+ def test_connected(self):
29
+ adjacency = cyclic_digraph(3)
30
+ self.assertEqual(is_connected(adjacency), True)
31
+ adjacency = linear_digraph(3)
32
+ self.assertEqual(is_connected(adjacency, connection='strong'), False)
33
+ biadjacency = star_wars()
34
+ self.assertEqual(is_connected(biadjacency), True)
35
+
36
+ def test_largest_cc(self):
37
+ adjacency = cyclic_digraph(3)
38
+ adjacency += adjacency.T
39
+ largest_cc, index = get_largest_connected_component(adjacency, return_index=True)
40
+
41
+ self.assertAlmostEqual(np.linalg.norm(largest_cc.toarray() - np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]])), 0)
42
+ self.assertEqual(np.linalg.norm(index - np.array([0, 1, 2])), 0)
43
+
44
+ biadjacency = star_wars(metadata=False)
45
+ largest_cc, index = get_largest_connected_component(biadjacency, return_index=True)
46
+ self.assertAlmostEqual(np.linalg.norm(largest_cc.toarray() - np.array([[1, 0, 1],
47
+ [1, 0, 0],
48
+ [1, 1, 1],
49
+ [0, 1, 1]])), 0)
50
+ self.assertEqual(np.linalg.norm(index - np.array([0, 1, 2, 3, 0, 1, 2])), 0)
51
+
52
+ biadjacency = sparse.csr_matrix(([1, 1, 1, 1], [[1, 2, 3, 3], [1, 2, 2, 3]]))
53
+ largest_cc, index = get_largest_connected_component(biadjacency, force_bipartite=True, return_index=True)
54
+ self.assertEqual(largest_cc.shape[0], 2)
55
+ self.assertEqual(len(index), 4)
56
+
57
+ self.assertTrue(isinstance(get_largest_connected_component(adjacency, return_index=False), sparse.csr_matrix))
58
+
59
+ def test_is_bipartite(self):
60
+ biadjacency = star_wars(metadata=False)
61
+ adjacency = bipartite2undirected(biadjacency)
62
+ self.assertTrue(is_bipartite(adjacency))
63
+
64
+ bipartite, biadjacency_pred, _, _ = is_bipartite(adjacency, return_biadjacency=True)
65
+ self.assertEqual(bipartite, True)
66
+ self.assertEqual(np.all(biadjacency.data == biadjacency_pred.data), True)
67
+
68
+ adjacency = sparse.identity(2, format='csr')
69
+ bipartite, biadjacency, _, _ = is_bipartite(adjacency, return_biadjacency=True)
70
+ self.assertEqual(bipartite, False)
71
+ self.assertIsNone(biadjacency)
72
+
73
+ adjacency = directed2undirected(cyclic_digraph(3))
74
+ bipartite, biadjacency, _, _ = is_bipartite(adjacency, return_biadjacency=True)
75
+ self.assertEqual(bipartite, False)
76
+ self.assertIsNone(biadjacency)
77
+
78
+ with self.assertRaises(ValueError):
79
+ is_bipartite(cyclic_digraph(3))
80
+
81
+ self.assertFalse(is_bipartite(sparse.eye(3)))
82
+
83
+ adjacency = directed2undirected(cyclic_digraph(3))
84
+ bipartite = is_bipartite(adjacency, return_biadjacency=False)
85
+ self.assertEqual(bipartite, False)
86
+
87
+ def test_is_acyclic(self):
88
+ adjacency_with_self_loops = sparse.identity(2, format='csr')
89
+ self.assertFalse(is_acyclic(adjacency_with_self_loops))
90
+ self.assertFalse(is_acyclic(adjacency_with_self_loops, directed=True))
91
+ directed_cycle = cyclic_digraph(3)
92
+ self.assertFalse(is_acyclic(directed_cycle))
93
+ with self.assertRaises(ValueError):
94
+ is_acyclic(directed_cycle, directed=False)
95
+ undirected_line = linear_graph(2)
96
+ self.assertTrue(is_acyclic(undirected_line))
97
+ self.assertFalse(is_acyclic(undirected_line, directed=True))
98
+ acyclic_graph = linear_digraph(2)
99
+ self.assertTrue(is_acyclic(acyclic_graph))
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tests for label propagation"""
4
+
5
+ import unittest
6
+
7
+ from scipy.special import comb
8
+
9
+ from sknetwork.data import karate_club
10
+ from sknetwork.data.parse import from_edge_list
11
+ from sknetwork.data.test_graphs import *
12
+ from sknetwork.topology import Triangles
13
+
14
+
15
+ class TestTriangleListing(unittest.TestCase):
16
+
17
+ def test_empty(self):
18
+ adjacency = test_graph_empty()
19
+ self.assertEqual(Triangles().fit_transform(adjacency), 0)
20
+
21
+ def test_disconnected(self):
22
+ adjacency = test_graph_disconnect()
23
+ self.assertEqual(Triangles().fit_transform(adjacency), 1)
24
+
25
+ def test_cliques(self):
26
+ adjacency = test_graph_clique()
27
+ n = adjacency.shape[0]
28
+ nb = Triangles().fit_transform(adjacency)
29
+ self.assertEqual(nb, comb(n, 3, exact=True))
30
+
31
+ def test_clustering_coefficient(self):
32
+ edges = [(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)]
33
+ adjacency = from_edge_list(edges, directed=False, matrix_only=True)
34
+
35
+ triangles = Triangles().fit(adjacency)
36
+ self.assertEqual(0.75, triangles.clustering_coef_)
37
+
38
+ def test_options(self):
39
+ adjacency = karate_club()
40
+
41
+ self.assertEqual(Triangles(parallelize=False).fit_transform(adjacency), 45)
42
+ self.assertEqual(Triangles(parallelize=True).fit_transform(adjacency), 45)
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tests for Weisfeiler-Lehman coloring"""
4
+ import unittest
5
+
6
+ from sknetwork.data import house, bow_tie
7
+ from sknetwork.data.test_graphs import *
8
+ from sknetwork.topology import WeisfeilerLehman
9
+
10
+
11
+ class TestWLColoring(unittest.TestCase):
12
+
13
+ def test_empty(self):
14
+ adjacency = test_graph_empty()
15
+ labels = WeisfeilerLehman().fit_transform(adjacency)
16
+ self.assertTrue((labels == np.zeros(10)).all())
17
+
18
+ def test_cliques(self):
19
+ adjacency = test_graph_clique()
20
+ labels = WeisfeilerLehman().fit_transform(adjacency)
21
+ self.assertTrue((labels == np.zeros(10)).all())
22
+
23
+ def test_house(self):
24
+ adjacency = house()
25
+ labels = WeisfeilerLehman().fit_transform(adjacency)
26
+ self.assertTrue((labels == np.array([0, 2, 1, 1, 2])).all())
27
+
28
+ def test_bow_tie(self):
29
+ adjacency = bow_tie()
30
+ labels = WeisfeilerLehman().fit_transform(adjacency)
31
+ self.assertTrue((labels == np.array([1, 0, 0, 0, 0])).all())
32
+
33
+ def test_iso(self):
34
+ adjacency = house()
35
+ n = adjacency.indptr.shape[0] - 1
36
+ reorder = list(range(n))
37
+ np.random.shuffle(reorder)
38
+ adjacency2 = adjacency[reorder][:, reorder]
39
+ l1 = WeisfeilerLehman().fit_transform(adjacency)
40
+ l2 = WeisfeilerLehman().fit_transform(adjacency2)
41
+ l1.sort()
42
+ l2.sort()
43
+ self.assertTrue((l1 == l2).all())
44
+
45
+ def test_early_stop(self):
46
+ adjacency = house()
47
+ wl = WeisfeilerLehman(max_iter=1)
48
+ labels = wl.fit_transform(adjacency)
49
+ self.assertTrue((labels == np.array([0, 1, 0, 0, 1])).all())
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tests for Weisfeiler-Lehman kernels"""
4
+ import unittest
5
+
6
+ import numpy as np
7
+
8
+ from sknetwork.data import house, bow_tie, linear_graph
9
+ from sknetwork.topology import are_isomorphic
10
+
11
+
12
+ class TestWLKernel(unittest.TestCase):
13
+
14
+ def test_isomorphism(self):
15
+ ref = house()
16
+ n = ref.shape[0]
17
+
18
+ adjacency = house()
19
+ reorder = list(range(n))
20
+ np.random.shuffle(reorder)
21
+ adjacency = adjacency[reorder][:, reorder]
22
+ self.assertTrue(are_isomorphic(ref, adjacency))
23
+
24
+ adjacency = bow_tie()
25
+ self.assertFalse(are_isomorphic(ref, adjacency))
26
+
27
+ adjacency = linear_graph(n)
28
+ self.assertFalse(are_isomorphic(ref, adjacency))
29
+
30
+ adjacency = linear_graph(n + 1)
31
+ self.assertFalse(are_isomorphic(ref, adjacency))