scikit-network 0.33.3__cp313-cp313-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 (228) hide show
  1. scikit_network-0.33.3.dist-info/METADATA +122 -0
  2. scikit_network-0.33.3.dist-info/RECORD +228 -0
  3. scikit_network-0.33.3.dist-info/WHEEL +5 -0
  4. scikit_network-0.33.3.dist-info/licenses/AUTHORS.rst +43 -0
  5. scikit_network-0.33.3.dist-info/licenses/LICENSE +34 -0
  6. scikit_network-0.33.3.dist-info/top_level.txt +1 -0
  7. sknetwork/__init__.py +21 -0
  8. sknetwork/base.py +67 -0
  9. sknetwork/classification/__init__.py +8 -0
  10. sknetwork/classification/base.py +142 -0
  11. sknetwork/classification/base_rank.py +133 -0
  12. sknetwork/classification/diffusion.py +134 -0
  13. sknetwork/classification/knn.py +139 -0
  14. sknetwork/classification/metrics.py +205 -0
  15. sknetwork/classification/pagerank.py +66 -0
  16. sknetwork/classification/propagation.py +152 -0
  17. sknetwork/classification/tests/__init__.py +1 -0
  18. sknetwork/classification/tests/test_API.py +30 -0
  19. sknetwork/classification/tests/test_diffusion.py +77 -0
  20. sknetwork/classification/tests/test_knn.py +23 -0
  21. sknetwork/classification/tests/test_metrics.py +53 -0
  22. sknetwork/classification/tests/test_pagerank.py +20 -0
  23. sknetwork/classification/tests/test_propagation.py +24 -0
  24. sknetwork/classification/vote.cp313-win_amd64.pyd +0 -0
  25. sknetwork/classification/vote.cpp +27584 -0
  26. sknetwork/classification/vote.pyx +56 -0
  27. sknetwork/clustering/__init__.py +8 -0
  28. sknetwork/clustering/base.py +172 -0
  29. sknetwork/clustering/kcenters.py +253 -0
  30. sknetwork/clustering/leiden.py +242 -0
  31. sknetwork/clustering/leiden_core.cp313-win_amd64.pyd +0 -0
  32. sknetwork/clustering/leiden_core.cpp +31575 -0
  33. sknetwork/clustering/leiden_core.pyx +124 -0
  34. sknetwork/clustering/louvain.py +286 -0
  35. sknetwork/clustering/louvain_core.cp313-win_amd64.pyd +0 -0
  36. sknetwork/clustering/louvain_core.cpp +31220 -0
  37. sknetwork/clustering/louvain_core.pyx +124 -0
  38. sknetwork/clustering/metrics.py +91 -0
  39. sknetwork/clustering/postprocess.py +66 -0
  40. sknetwork/clustering/propagation_clustering.py +104 -0
  41. sknetwork/clustering/tests/__init__.py +1 -0
  42. sknetwork/clustering/tests/test_API.py +38 -0
  43. sknetwork/clustering/tests/test_kcenters.py +60 -0
  44. sknetwork/clustering/tests/test_leiden.py +34 -0
  45. sknetwork/clustering/tests/test_louvain.py +135 -0
  46. sknetwork/clustering/tests/test_metrics.py +50 -0
  47. sknetwork/clustering/tests/test_postprocess.py +39 -0
  48. sknetwork/data/__init__.py +6 -0
  49. sknetwork/data/base.py +33 -0
  50. sknetwork/data/load.py +406 -0
  51. sknetwork/data/models.py +459 -0
  52. sknetwork/data/parse.py +644 -0
  53. sknetwork/data/test_graphs.py +84 -0
  54. sknetwork/data/tests/__init__.py +1 -0
  55. sknetwork/data/tests/test_API.py +30 -0
  56. sknetwork/data/tests/test_base.py +14 -0
  57. sknetwork/data/tests/test_load.py +95 -0
  58. sknetwork/data/tests/test_models.py +52 -0
  59. sknetwork/data/tests/test_parse.py +250 -0
  60. sknetwork/data/tests/test_test_graphs.py +29 -0
  61. sknetwork/data/tests/test_toy_graphs.py +68 -0
  62. sknetwork/data/timeout.py +38 -0
  63. sknetwork/data/toy_graphs.py +611 -0
  64. sknetwork/embedding/__init__.py +8 -0
  65. sknetwork/embedding/base.py +94 -0
  66. sknetwork/embedding/force_atlas.py +198 -0
  67. sknetwork/embedding/louvain_embedding.py +148 -0
  68. sknetwork/embedding/random_projection.py +135 -0
  69. sknetwork/embedding/spectral.py +141 -0
  70. sknetwork/embedding/spring.py +198 -0
  71. sknetwork/embedding/svd.py +359 -0
  72. sknetwork/embedding/tests/__init__.py +1 -0
  73. sknetwork/embedding/tests/test_API.py +49 -0
  74. sknetwork/embedding/tests/test_force_atlas.py +35 -0
  75. sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
  76. sknetwork/embedding/tests/test_random_projection.py +28 -0
  77. sknetwork/embedding/tests/test_spectral.py +81 -0
  78. sknetwork/embedding/tests/test_spring.py +50 -0
  79. sknetwork/embedding/tests/test_svd.py +43 -0
  80. sknetwork/gnn/__init__.py +10 -0
  81. sknetwork/gnn/activation.py +117 -0
  82. sknetwork/gnn/base.py +181 -0
  83. sknetwork/gnn/base_activation.py +90 -0
  84. sknetwork/gnn/base_layer.py +109 -0
  85. sknetwork/gnn/gnn_classifier.py +305 -0
  86. sknetwork/gnn/layer.py +153 -0
  87. sknetwork/gnn/loss.py +180 -0
  88. sknetwork/gnn/neighbor_sampler.py +65 -0
  89. sknetwork/gnn/optimizer.py +164 -0
  90. sknetwork/gnn/tests/__init__.py +1 -0
  91. sknetwork/gnn/tests/test_activation.py +56 -0
  92. sknetwork/gnn/tests/test_base.py +75 -0
  93. sknetwork/gnn/tests/test_base_layer.py +37 -0
  94. sknetwork/gnn/tests/test_gnn_classifier.py +130 -0
  95. sknetwork/gnn/tests/test_layers.py +80 -0
  96. sknetwork/gnn/tests/test_loss.py +33 -0
  97. sknetwork/gnn/tests/test_neigh_sampler.py +23 -0
  98. sknetwork/gnn/tests/test_optimizer.py +43 -0
  99. sknetwork/gnn/tests/test_utils.py +41 -0
  100. sknetwork/gnn/utils.py +127 -0
  101. sknetwork/hierarchy/__init__.py +6 -0
  102. sknetwork/hierarchy/base.py +96 -0
  103. sknetwork/hierarchy/louvain_hierarchy.py +272 -0
  104. sknetwork/hierarchy/metrics.py +234 -0
  105. sknetwork/hierarchy/paris.cp313-win_amd64.pyd +0 -0
  106. sknetwork/hierarchy/paris.cpp +37868 -0
  107. sknetwork/hierarchy/paris.pyx +316 -0
  108. sknetwork/hierarchy/postprocess.py +350 -0
  109. sknetwork/hierarchy/tests/__init__.py +1 -0
  110. sknetwork/hierarchy/tests/test_API.py +24 -0
  111. sknetwork/hierarchy/tests/test_algos.py +34 -0
  112. sknetwork/hierarchy/tests/test_metrics.py +62 -0
  113. sknetwork/hierarchy/tests/test_postprocess.py +57 -0
  114. sknetwork/linalg/__init__.py +9 -0
  115. sknetwork/linalg/basics.py +37 -0
  116. sknetwork/linalg/diteration.cp313-win_amd64.pyd +0 -0
  117. sknetwork/linalg/diteration.cpp +27400 -0
  118. sknetwork/linalg/diteration.pyx +47 -0
  119. sknetwork/linalg/eig_solver.py +93 -0
  120. sknetwork/linalg/laplacian.py +15 -0
  121. sknetwork/linalg/normalizer.py +86 -0
  122. sknetwork/linalg/operators.py +225 -0
  123. sknetwork/linalg/polynome.py +76 -0
  124. sknetwork/linalg/ppr_solver.py +170 -0
  125. sknetwork/linalg/push.cp313-win_amd64.pyd +0 -0
  126. sknetwork/linalg/push.cpp +31072 -0
  127. sknetwork/linalg/push.pyx +71 -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 +34 -0
  134. sknetwork/linalg/tests/test_operators.py +66 -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 +2 -0
  140. sknetwork/linkpred/base.py +46 -0
  141. sknetwork/linkpred/nn.py +126 -0
  142. sknetwork/linkpred/tests/__init__.py +1 -0
  143. sknetwork/linkpred/tests/test_nn.py +27 -0
  144. sknetwork/log.py +19 -0
  145. sknetwork/path/__init__.py +5 -0
  146. sknetwork/path/dag.py +54 -0
  147. sknetwork/path/distances.py +98 -0
  148. sknetwork/path/search.py +31 -0
  149. sknetwork/path/shortest_path.py +61 -0
  150. sknetwork/path/tests/__init__.py +1 -0
  151. sknetwork/path/tests/test_dag.py +37 -0
  152. sknetwork/path/tests/test_distances.py +62 -0
  153. sknetwork/path/tests/test_search.py +40 -0
  154. sknetwork/path/tests/test_shortest_path.py +40 -0
  155. sknetwork/ranking/__init__.py +8 -0
  156. sknetwork/ranking/base.py +61 -0
  157. sknetwork/ranking/betweenness.cp313-win_amd64.pyd +0 -0
  158. sknetwork/ranking/betweenness.cpp +9707 -0
  159. sknetwork/ranking/betweenness.pyx +97 -0
  160. sknetwork/ranking/closeness.py +92 -0
  161. sknetwork/ranking/hits.py +94 -0
  162. sknetwork/ranking/katz.py +83 -0
  163. sknetwork/ranking/pagerank.py +110 -0
  164. sknetwork/ranking/postprocess.py +37 -0
  165. sknetwork/ranking/tests/__init__.py +1 -0
  166. sknetwork/ranking/tests/test_API.py +32 -0
  167. sknetwork/ranking/tests/test_betweenness.py +38 -0
  168. sknetwork/ranking/tests/test_closeness.py +30 -0
  169. sknetwork/ranking/tests/test_hits.py +20 -0
  170. sknetwork/ranking/tests/test_pagerank.py +62 -0
  171. sknetwork/ranking/tests/test_postprocess.py +26 -0
  172. sknetwork/regression/__init__.py +4 -0
  173. sknetwork/regression/base.py +61 -0
  174. sknetwork/regression/diffusion.py +210 -0
  175. sknetwork/regression/tests/__init__.py +1 -0
  176. sknetwork/regression/tests/test_API.py +32 -0
  177. sknetwork/regression/tests/test_diffusion.py +56 -0
  178. sknetwork/sknetwork.py +3 -0
  179. sknetwork/test_base.py +35 -0
  180. sknetwork/test_log.py +15 -0
  181. sknetwork/topology/__init__.py +8 -0
  182. sknetwork/topology/cliques.cp313-win_amd64.pyd +0 -0
  183. sknetwork/topology/cliques.cpp +32565 -0
  184. sknetwork/topology/cliques.pyx +149 -0
  185. sknetwork/topology/core.cp313-win_amd64.pyd +0 -0
  186. sknetwork/topology/core.cpp +30651 -0
  187. sknetwork/topology/core.pyx +90 -0
  188. sknetwork/topology/cycles.py +243 -0
  189. sknetwork/topology/minheap.cp313-win_amd64.pyd +0 -0
  190. sknetwork/topology/minheap.cpp +27332 -0
  191. sknetwork/topology/minheap.pxd +20 -0
  192. sknetwork/topology/minheap.pyx +109 -0
  193. sknetwork/topology/structure.py +194 -0
  194. sknetwork/topology/tests/__init__.py +1 -0
  195. sknetwork/topology/tests/test_cliques.py +28 -0
  196. sknetwork/topology/tests/test_core.py +19 -0
  197. sknetwork/topology/tests/test_cycles.py +65 -0
  198. sknetwork/topology/tests/test_structure.py +85 -0
  199. sknetwork/topology/tests/test_triangles.py +38 -0
  200. sknetwork/topology/tests/test_wl.py +72 -0
  201. sknetwork/topology/triangles.cp313-win_amd64.pyd +0 -0
  202. sknetwork/topology/triangles.cpp +8894 -0
  203. sknetwork/topology/triangles.pyx +151 -0
  204. sknetwork/topology/weisfeiler_lehman.py +133 -0
  205. sknetwork/topology/weisfeiler_lehman_core.cp313-win_amd64.pyd +0 -0
  206. sknetwork/topology/weisfeiler_lehman_core.cpp +27635 -0
  207. sknetwork/topology/weisfeiler_lehman_core.pyx +114 -0
  208. sknetwork/utils/__init__.py +7 -0
  209. sknetwork/utils/check.py +355 -0
  210. sknetwork/utils/format.py +221 -0
  211. sknetwork/utils/membership.py +82 -0
  212. sknetwork/utils/neighbors.py +115 -0
  213. sknetwork/utils/tests/__init__.py +1 -0
  214. sknetwork/utils/tests/test_check.py +190 -0
  215. sknetwork/utils/tests/test_format.py +63 -0
  216. sknetwork/utils/tests/test_membership.py +24 -0
  217. sknetwork/utils/tests/test_neighbors.py +41 -0
  218. sknetwork/utils/tests/test_tfidf.py +18 -0
  219. sknetwork/utils/tests/test_values.py +66 -0
  220. sknetwork/utils/tfidf.py +37 -0
  221. sknetwork/utils/values.py +76 -0
  222. sknetwork/visualization/__init__.py +4 -0
  223. sknetwork/visualization/colors.py +34 -0
  224. sknetwork/visualization/dendrograms.py +277 -0
  225. sknetwork/visualization/graphs.py +1039 -0
  226. sknetwork/visualization/tests/__init__.py +1 -0
  227. sknetwork/visualization/tests/test_dendrograms.py +53 -0
  228. sknetwork/visualization/tests/test_graphs.py +176 -0
@@ -0,0 +1,20 @@
1
+ # distutils: language = c++
2
+ # cython: language_level=3
3
+ """
4
+ Created in June 2020
5
+ @author: Julien Simonnet <julien.simonnet@etu.upmc.fr>
6
+ @author: Yohann Robert <yohann.robert@etu.upmc.fr>
7
+ """
8
+ from libcpp.vector cimport vector
9
+
10
+ cdef class MinHeap:
11
+
12
+ cdef vector[int] val, pos
13
+ cdef int size
14
+
15
+ cdef int pop_min(self, int[:] scores)
16
+ cdef bint empty(self)
17
+ cdef void swap(self, int x, int y)
18
+ cdef void insert_key(self, int k, int[:] scores)
19
+ cdef void decrease_key(self, int i, int[:] scores)
20
+ cdef void min_heapify(self, int i, int[:] scores)
@@ -0,0 +1,109 @@
1
+ # distutils: language = c++
2
+ # cython: language_level=3
3
+ """
4
+ Created in June 2020
5
+ @author: Julien Simonnet <julien.simonnet@etu.upmc.fr>
6
+ @author: Yohann Robert <yohann.robert@etu.upmc.fr>
7
+ """
8
+ cimport cython
9
+
10
+
11
+ cdef inline int parent(int i):
12
+ """Index of the parent node of i in the tree."""
13
+ return (i - 1) // 2
14
+
15
+ cdef inline int left(int i):
16
+ """Index of the left child of i in the tree."""
17
+ return 2 * i + 1
18
+
19
+ cdef inline int right(int i):
20
+ """Index of the right child of i in the tree."""
21
+ return 2 * i + 2
22
+
23
+
24
+ @cython.boundscheck(False)
25
+ @cython.wraparound(False)
26
+ cdef class MinHeap:
27
+ """Min heap data structure."""
28
+ def __cinit__(self, int n):
29
+ self.val.reserve(n) # reserves the necessary space in the vector
30
+ self.pos.reserve(n) # reserves the necessary space in the other vector
31
+ self.size = 0
32
+
33
+ cdef bint empty(self):
34
+ """Check if the heap is empty."""
35
+ return self.size == 0
36
+
37
+ cdef inline void swap(self, int x, int y):
38
+ """Exchange two elements in the heap."""
39
+ cdef int tmp
40
+ tmp = self.val[x]
41
+ self.val[x] = self.val[y]
42
+ self.val[y] = tmp
43
+
44
+ # updates the position of the corresponding elements
45
+ self.pos[self.val[x]] = x
46
+ self.pos[self.val[y]] = y
47
+
48
+ # Inserts a new key k
49
+ cdef void insert_key(self, int k, int[:] scores):
50
+ """Insert new element into the heap"""
51
+ # First insert the new key at the end
52
+ self.val[self.size] = k
53
+ self.pos[k] = self.size
54
+ cdef int i = self.size
55
+ self.size += 1
56
+
57
+ cdef int p = parent(i)
58
+ while (p >= 0) and (scores[self.val[p]] > scores[self.val[i]]) :
59
+ self.swap(i, p)
60
+ i = p
61
+ p = parent(i)
62
+
63
+
64
+ cdef void decrease_key(self, int i, int[:] scores):
65
+ """Decrease value of key at index 'i' to new_val.
66
+ It is assumed that the new value is smaller than the old one.
67
+ """
68
+ cdef int pos, p
69
+ pos = self.pos[i]
70
+ if pos < self.size:
71
+ p = parent(pos)
72
+
73
+ while (pos != 0) and (scores[self.val[p]] > scores[self.val[pos]]):
74
+ self.swap(pos, p)
75
+ pos = p
76
+ p = parent(pos)
77
+
78
+ cdef int pop_min(self, int[:] scores):
79
+ """Remove and return the minimum element (or root) from the heap."""
80
+ if self.size == 1:
81
+ self.size = 0
82
+ return self.val[0]
83
+
84
+ # Store the minimum value, and remove it from heap
85
+ cdef int root = self.val[0]
86
+ self.val[0] = self.val[self.size-1]
87
+ self.pos[self.val[0]] = 0
88
+ self.size -= 1
89
+ self.min_heapify(0, scores)
90
+
91
+ return root
92
+
93
+ cdef void min_heapify(self, int i, int[:] scores):
94
+ """A recursive method to heapify a subtree with the root at given index
95
+ This function assumes that the subtrees are already heapified.
96
+ """
97
+ cdef int l, r, smallest
98
+ l = left(i)
99
+ r = right(i)
100
+ smallest = i
101
+ if (l < self.size) and (scores[self.val[l]] < scores[self.val[i]]):
102
+ smallest = l
103
+
104
+ if (r < self.size) and (scores[self.val[r]] < scores[self.val[smallest]]):
105
+ smallest = r
106
+
107
+ if smallest != i:
108
+ self.swap(i, smallest)
109
+ self.min_heapify(smallest, scores)
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created in July 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, List
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
+ from sknetwork.path import get_distances
17
+
18
+
19
+ def get_connected_components(input_matrix: sparse.csr_matrix, connection: str = 'weak', force_bipartite: bool = False) \
20
+ -> np.ndarray:
21
+ """Extract the connected components of a graph.
22
+
23
+ Parameters
24
+ ----------
25
+ input_matrix :
26
+ Input matrix (either the adjacency matrix or the biadjacency matrix of the graph).
27
+ connection :
28
+ Must be ``'weak'`` (default) or ``'strong'``. The type of connection to use for directed graphs.
29
+ force_bipartite : bool
30
+ If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
31
+
32
+ Returns
33
+ -------
34
+ labels :
35
+ Connected component of each node.
36
+ For bipartite graphs, rows and columns are concatenated (rows first).
37
+
38
+ Example
39
+ -------
40
+ >>> from sknetwork.topology import get_connected_components
41
+ >>> from sknetwork.data import house
42
+ >>> get_connected_components(house())
43
+ array([0, 0, 0, 0, 0], dtype=int32)
44
+ """
45
+ input_matrix = check_format(input_matrix)
46
+ if len(input_matrix.data) == 0:
47
+ raise ValueError('The graph is empty (no edge).')
48
+ adjacency, _ = get_adjacency(input_matrix, force_bipartite=force_bipartite)
49
+ labels = sparse.csgraph.connected_components(adjacency, connection=connection, return_labels=True)[1]
50
+ return labels
51
+
52
+
53
+ def is_connected(input_matrix: sparse.csr_matrix, connection: str = 'weak', force_bipartite: bool = False) -> bool:
54
+ """Check whether the graph is connected.
55
+
56
+ Parameters
57
+ ----------
58
+ input_matrix :
59
+ Input matrix (either the adjacency matrix or the biadjacency matrix of the graph).
60
+ connection :
61
+ Must be ``'weak'`` (default) or ``'strong'``. The type of connection to use for directed graphs.
62
+ force_bipartite : bool
63
+ If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
64
+
65
+ Example
66
+ -------
67
+ >>> from sknetwork.topology import is_connected
68
+ >>> from sknetwork.data import house
69
+ >>> is_connected(house())
70
+ True
71
+ """
72
+ return len(set(get_connected_components(input_matrix, connection, force_bipartite))) == 1
73
+
74
+
75
+ def get_largest_connected_component(input_matrix: sparse.csr_matrix, connection: str = "weak",
76
+ force_bipartite: bool = False, return_index: bool = False) \
77
+ -> Union[sparse.csr_matrix, Tuple[sparse.csr_matrix, np.ndarray]]:
78
+ """Extract the largest connected component of a graph. Bipartite graphs are treated as undirected.
79
+
80
+ Parameters
81
+ ----------
82
+ input_matrix :
83
+ Adjacency matrix or biadjacency matrix of the graph.
84
+ connection :
85
+ Must be ``'weak'`` (default) or ``'strong'``. The type of connection to use for directed graphs.
86
+ force_bipartite : bool
87
+ If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
88
+ return_index : bool
89
+ Whether to return the index of the nodes of the largest connected component in the original graph.
90
+
91
+ Returns
92
+ -------
93
+ output_matrix : sparse.csr_matrix
94
+ Adjacency matrix or biadjacency matrix of the largest connected component.
95
+ index : array
96
+ Indices of the nodes in the original graph.
97
+ For bipartite graphs, rows and columns are concatenated (rows first).
98
+
99
+ Example
100
+ -------
101
+ >>> from sknetwork.topology import get_largest_connected_component
102
+ >>> from sknetwork.data import house
103
+ >>> get_largest_connected_component(house()).shape
104
+ (5, 5)
105
+ """
106
+ input_matrix = check_format(input_matrix)
107
+ adjacency, bipartite = get_adjacency(input_matrix, force_bipartite=force_bipartite)
108
+ labels = get_connected_components(adjacency, connection=connection)
109
+ unique_labels, counts = np.unique(labels, return_counts=True)
110
+ largest_component_label = unique_labels[np.argmax(counts)]
111
+
112
+ if bipartite:
113
+ n_row, n_col = input_matrix.shape
114
+ index_row = np.argwhere(labels[:n_row] == largest_component_label).ravel()
115
+ index_col = np.argwhere(labels[n_row:] == largest_component_label).ravel()
116
+ index = np.hstack((index_row, index_col))
117
+ output_matrix = input_matrix[index_row, :]
118
+ output_matrix = (output_matrix.tocsc()[:, index_col]).tocsr()
119
+ else:
120
+ index = np.argwhere(labels == largest_component_label).ravel()
121
+ output_matrix = input_matrix[index, :]
122
+ output_matrix = (output_matrix.tocsc()[:, index]).tocsr()
123
+ if return_index:
124
+ return output_matrix, index
125
+ else:
126
+ return output_matrix
127
+
128
+
129
+ def is_bipartite(adjacency: sparse.csr_matrix, return_biadjacency: bool = False) \
130
+ -> Union[bool, Tuple[bool, Optional[sparse.csr_matrix], Optional[np.ndarray], Optional[np.ndarray]]]:
131
+ """Check whether a graph is bipartite.
132
+
133
+ Parameters
134
+ ----------
135
+ adjacency :
136
+ Adjacency matrix of the graph (symmetric).
137
+ return_biadjacency :
138
+ If ``True``, return a biadjacency matrix of the graph if bipartite.
139
+
140
+ Returns
141
+ -------
142
+ is_bipartite : bool
143
+ A boolean denoting if the graph is bipartite.
144
+ biadjacency : sparse.csr_matrix
145
+ A biadjacency matrix of the graph if bipartite (optional).
146
+ rows : np.ndarray
147
+ Index of rows in the original graph (optional).
148
+ cols : np.ndarray
149
+ Index of columns in the original graph (optional).
150
+
151
+ Example
152
+ -------
153
+ >>> from sknetwork.topology import is_bipartite
154
+ >>> from sknetwork.data import cyclic_graph
155
+ >>> is_bipartite(cyclic_graph(4))
156
+ True
157
+ >>> is_bipartite(cyclic_graph(3))
158
+ False
159
+ """
160
+ if not is_symmetric(adjacency):
161
+ raise ValueError('The graph must be undirected.')
162
+ if adjacency.diagonal().any():
163
+ if return_biadjacency:
164
+ return False, None, None, None
165
+ else:
166
+ return False
167
+ n = adjacency.indptr.shape[0] - 1
168
+ coloring = np.full(n, -1, dtype=int)
169
+ exists_remaining = n
170
+ while exists_remaining:
171
+ src = np.argwhere(coloring == -1)[0, 0]
172
+ next_nodes = [src]
173
+ coloring[src] = 0
174
+ exists_remaining -= 1
175
+ while next_nodes:
176
+ node = next_nodes.pop()
177
+ for neighbor in adjacency.indices[adjacency.indptr[node]:adjacency.indptr[node + 1]]:
178
+ if coloring[neighbor] == -1:
179
+ coloring[neighbor] = 1 - coloring[node]
180
+ next_nodes.append(neighbor)
181
+ exists_remaining -= 1
182
+ elif coloring[neighbor] == coloring[node]:
183
+ if return_biadjacency:
184
+ return False, None, None, None
185
+ else:
186
+ return False
187
+ if return_biadjacency:
188
+ rows = np.argwhere(coloring == 0).ravel()
189
+ cols = np.argwhere(coloring == 1).ravel()
190
+ return True, adjacency[rows, :][:, cols], rows, cols
191
+ else:
192
+ return True
193
+
194
+
@@ -0,0 +1 @@
1
+ """tests for topology"""
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tests for cliques"""
4
+ import unittest
5
+
6
+ from scipy.special import comb
7
+
8
+ from sknetwork.data.test_graphs import *
9
+ from sknetwork.topology.cliques import count_cliques
10
+
11
+
12
+ class TestClique(unittest.TestCase):
13
+
14
+ def test_empty(self):
15
+ adjacency = test_graph_empty()
16
+ self.assertEqual(count_cliques(adjacency), 0)
17
+ with self.assertRaises(ValueError):
18
+ count_cliques(adjacency, 1)
19
+
20
+ def test_disconnected(self):
21
+ adjacency = test_disconnected_graph()
22
+ self.assertEqual(count_cliques(adjacency), 1)
23
+
24
+ def test_cliques(self):
25
+ adjacency = test_clique()
26
+ n = adjacency.shape[0]
27
+ self.assertEqual(count_cliques(adjacency), comb(n, 3, exact=True))
28
+ self.assertEqual(count_cliques(adjacency, 4), comb(n, 4, exact=True))
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tests for k-core decomposition"""
4
+ import unittest
5
+
6
+ from sknetwork.data.test_graphs import *
7
+ from sknetwork.topology.core import get_core_decomposition
8
+
9
+
10
+ class TestCoreDecomposition(unittest.TestCase):
11
+
12
+ def test_empty(self):
13
+ adjacency = test_graph_empty()
14
+ self.assertEqual(max(get_core_decomposition(adjacency)), 0)
15
+
16
+ def test_cliques(self):
17
+ adjacency = test_clique()
18
+ n = adjacency.shape[0]
19
+ self.assertEqual(max(get_core_decomposition(adjacency)), n - 1)
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """"tests for cycles.py"""
4
+ import unittest
5
+
6
+ import numpy as np
7
+ from scipy import sparse
8
+
9
+ from sknetwork.data import star_wars, house, cyclic_digraph, cyclic_graph, linear_digraph, linear_graph
10
+ from sknetwork.topology import is_connected, is_acyclic, get_cycles, break_cycles
11
+ from sknetwork.utils.format import bipartite2undirected, directed2undirected
12
+
13
+
14
+ class TestCycle(unittest.TestCase):
15
+
16
+ def test_is_acyclic(self):
17
+ adjacency_with_self_loops = sparse.identity(2, format='csr')
18
+ self.assertFalse(is_acyclic(adjacency_with_self_loops))
19
+ self.assertFalse(is_acyclic(adjacency_with_self_loops, directed=True))
20
+ directed_cycle = cyclic_digraph(3)
21
+ self.assertFalse(is_acyclic(directed_cycle))
22
+ with self.assertRaises(ValueError):
23
+ is_acyclic(directed_cycle, directed=False)
24
+ undirected_line = linear_graph(2)
25
+ self.assertTrue(is_acyclic(undirected_line))
26
+ self.assertFalse(is_acyclic(undirected_line, directed=True))
27
+ acyclic_graph = linear_digraph(2)
28
+ self.assertTrue(is_acyclic(acyclic_graph))
29
+
30
+ def test_get_cycles(self):
31
+ adjacency_with_self_loops = sparse.identity(2, format='csr')
32
+ node_cycles = get_cycles(adjacency_with_self_loops, directed=True)
33
+ self.assertEqual(node_cycles, [[0], [1]])
34
+
35
+ cycle_adjacency = cyclic_digraph(4)
36
+ node_cycles = get_cycles(cycle_adjacency, directed=True)
37
+ self.assertEqual(sorted(node_cycles[0]), [0, 1, 2, 3])
38
+ adjacency_with_subcycles = cycle_adjacency + sparse.csr_matrix(([1], ([1], [3])), shape=cycle_adjacency.shape)
39
+ node_cycles = get_cycles(adjacency_with_subcycles, directed=True)
40
+ self.assertEqual(node_cycles, [[0, 1, 3], [0, 1, 2, 3]])
41
+
42
+ undirected_cycle = cyclic_graph(4)
43
+ node_cycles = get_cycles(undirected_cycle, directed=False)
44
+ self.assertEqual(sorted(node_cycles[0]), [0, 1, 2, 3])
45
+
46
+ disconnected_cycles = sparse.csr_matrix(([1, 1, 1], ([1, 2, 3], [2, 3, 1])), shape=(4, 4))
47
+ node_cycles = get_cycles(disconnected_cycles, directed=True)
48
+ self.assertEqual(sorted(node_cycles[0]), [1, 2, 3])
49
+
50
+ def test_break_cycles(self):
51
+ cycle_adjacency = cyclic_digraph(4)
52
+ acyclic_graph = break_cycles(cycle_adjacency, root=0, directed=True)
53
+ self.assertTrue(is_acyclic(acyclic_graph))
54
+ adjacency_with_subcycles = cycle_adjacency + sparse.csr_matrix(([1], ([1], [0])), shape=cycle_adjacency.shape)
55
+ acyclic_graph = break_cycles(adjacency_with_subcycles, root=0, directed=True)
56
+ self.assertTrue(is_acyclic(acyclic_graph))
57
+
58
+ undirected_cycle = house(metadata=False)
59
+ acyclic_graph = break_cycles(undirected_cycle, root=0, directed=False)
60
+ self.assertTrue(is_acyclic(acyclic_graph))
61
+
62
+ disconnected_cycles = sparse.csr_matrix(([1, 1, 1, 1, 1], ([0, 1, 2, 3, 4], [1, 0, 3, 4, 2])), shape=(5, 5))
63
+ self.assertFalse(is_connected(disconnected_cycles))
64
+ acyclic_graph = break_cycles(disconnected_cycles, root=[0, 2], directed=True)
65
+ self.assertTrue(is_acyclic(acyclic_graph))
@@ -0,0 +1,85 @@
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, house, cyclic_digraph, cyclic_graph, 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
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)
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tests for triangle counting"""
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.triangles import count_triangles, get_clustering_coefficient
13
+
14
+
15
+ class TestTriangle(unittest.TestCase):
16
+
17
+ def test_empty(self):
18
+ adjacency = test_graph_empty()
19
+ self.assertEqual(count_triangles(adjacency), 0)
20
+
21
+ def test_disconnected(self):
22
+ adjacency = test_disconnected_graph()
23
+ self.assertEqual(count_triangles(adjacency), 1)
24
+
25
+ def test_cliques(self):
26
+ adjacency = test_clique()
27
+ n = adjacency.shape[0]
28
+ self.assertEqual(count_triangles(adjacency), comb(n, 3, exact=True))
29
+
30
+ def test_clustering_coefficient(self):
31
+ edges = [(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)]
32
+ adjacency = from_edge_list(edges, directed=False, matrix_only=True)
33
+ self.assertEqual(0.75, get_clustering_coefficient(adjacency))
34
+
35
+ def test_options(self):
36
+ adjacency = karate_club()
37
+ self.assertEqual(count_triangles(adjacency, parallelize=False), 45)
38
+ self.assertEqual(count_triangles(adjacency, parallelize=True), 45)
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tests for Weisfeiler-Lehman"""
4
+ import unittest
5
+
6
+ import numpy as np
7
+
8
+ from sknetwork.data import house, bow_tie, linear_graph
9
+ from sknetwork.data.test_graphs import *
10
+ from sknetwork.topology import color_weisfeiler_lehman, are_isomorphic
11
+
12
+
13
+ class TestWLKernel(unittest.TestCase):
14
+
15
+ def test_isomorphism(self):
16
+ ref = house()
17
+ n = ref.shape[0]
18
+
19
+ adjacency = house()
20
+ reorder = list(range(n))
21
+ np.random.shuffle(reorder)
22
+ adjacency = adjacency[reorder][:, reorder]
23
+ self.assertTrue(are_isomorphic(ref, adjacency))
24
+
25
+ adjacency = bow_tie()
26
+ self.assertFalse(are_isomorphic(ref, adjacency))
27
+
28
+ adjacency = linear_graph(n)
29
+ self.assertFalse(are_isomorphic(ref, adjacency))
30
+
31
+ adjacency = linear_graph(n + 1)
32
+ self.assertFalse(are_isomorphic(ref, adjacency))
33
+
34
+
35
+ class TestWLColoring(unittest.TestCase):
36
+
37
+ def test_empty(self):
38
+ adjacency = test_graph_empty()
39
+ labels = color_weisfeiler_lehman(adjacency)
40
+ self.assertTrue((labels == np.zeros(10)).all())
41
+
42
+ def test_cliques(self):
43
+ adjacency = test_clique()
44
+ labels = color_weisfeiler_lehman(adjacency)
45
+ self.assertTrue((labels == np.zeros(10)).all())
46
+
47
+ def test_house(self):
48
+ adjacency = house()
49
+ labels = color_weisfeiler_lehman(adjacency)
50
+ self.assertTrue((labels == np.array([0, 2, 1, 1, 2])).all())
51
+
52
+ def test_bow_tie(self):
53
+ adjacency = bow_tie()
54
+ labels = color_weisfeiler_lehman(adjacency)
55
+ self.assertTrue((labels == np.array([1, 0, 0, 0, 0])).all())
56
+
57
+ def test_iso(self):
58
+ adjacency = house()
59
+ n = adjacency.indptr.shape[0] - 1
60
+ reorder = list(range(n))
61
+ np.random.shuffle(reorder)
62
+ adjacency2 = adjacency[reorder][:, reorder]
63
+ l1 = color_weisfeiler_lehman(adjacency)
64
+ l2 = color_weisfeiler_lehman(adjacency2)
65
+ l1.sort()
66
+ l2.sort()
67
+ self.assertTrue((l1 == l2).all())
68
+
69
+ def test_early_stop(self):
70
+ adjacency = house()
71
+ labels = color_weisfeiler_lehman(adjacency, max_iter=1)
72
+ self.assertTrue((labels == np.array([0, 1, 0, 0, 1])).all())