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,133 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Created on January, 15 2021
5
+ @author: Thomas Bonald <bonald@enst.fr>
6
+ """
7
+ from typing import Union
8
+
9
+ import numpy as np
10
+ from scipy import sparse
11
+
12
+ from sknetwork.embedding.base import BaseEmbedding
13
+ from sknetwork.linalg import Regularizer, Normalizer, normalize
14
+ from sknetwork.utils.check import check_format, check_random_state
15
+ from sknetwork.utils.format import get_adjacency
16
+
17
+
18
+ class RandomProjection(BaseEmbedding):
19
+ """Embedding of graphs based the random projection of the adjacency matrix:
20
+
21
+ :math:`(I + \\alpha A +... + (\\alpha A)^K)G`
22
+
23
+ where :math:`A` is the adjacency matrix, :math:`G` is a random Gaussian matrix,
24
+ :math:`\\alpha` is some smoothing factor and :math:`K` some non-negative integer.
25
+
26
+ Parameters
27
+ ----------
28
+ n_components : int (default = 2)
29
+ Dimension of the embedding space.
30
+ alpha : float (default = 0.5)
31
+ Smoothing parameter.
32
+ n_iter : int (default = 3)
33
+ Number of power iterations of the adjacency matrix.
34
+ random_walk : bool (default = ``False``)
35
+ If ``True``, use the transition matrix of the random walk, :math:`P = D^{-1}A`, instead of the adjacency matrix.
36
+ regularization : float (default = ``-1``)
37
+ Regularization factor :math:`\\alpha` so that the matrix is :math:`A + \\alpha \\frac{11^T}{n}`.
38
+ If negative, regularization is applied only if the graph is disconnected (and then equal to the absolute value
39
+ of the parameter).
40
+ normalized : bool (default = ``True``)
41
+ If ``True``, normalize the embedding so that each vector has norm 1 in the embedding space, i.e.,
42
+ each vector lies on the unit sphere.
43
+ random_state : int, optional
44
+ Seed used by the random number generator.
45
+
46
+ Attributes
47
+ ----------
48
+ embedding_ : array, shape = (n, n_components)
49
+ Embedding of the nodes.
50
+ embedding_row_ : array, shape = (n_row, n_components)
51
+ Embedding of the rows, for bipartite graphs.
52
+ embedding_col_ : array, shape = (n_col, n_components)
53
+ Embedding of the columns, for bipartite graphs.
54
+
55
+ Example
56
+ -------
57
+ >>> from sknetwork.embedding import RandomProjection
58
+ >>> from sknetwork.data import karate_club
59
+ >>> projection = RandomProjection()
60
+ >>> adjacency = karate_club()
61
+ >>> embedding = projection.fit_transform(adjacency)
62
+ >>> embedding.shape
63
+ (34, 2)
64
+
65
+ References
66
+ ----------
67
+ Zhang, Z., Cui, P., Li, H., Wang, X., & Zhu, W. (2018).
68
+ Billion-scale network embedding with iterative random projection, ICDM.
69
+ """
70
+ def __init__(self, n_components: int = 2, alpha: float = 0.5, n_iter: int = 3, random_walk: bool = False,
71
+ regularization: float = -1, normalized: bool = True, random_state: int = None):
72
+ super(RandomProjection, self).__init__()
73
+
74
+ self.n_components = n_components
75
+ self.alpha = alpha
76
+ self.n_iter = n_iter
77
+ self.random_walk = random_walk
78
+ self.regularization = regularization
79
+ self.normalized = normalized
80
+ self.random_state = random_state
81
+ self.bipartite = None
82
+ self.regularized = None
83
+
84
+ def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray], force_bipartite: bool = False) \
85
+ -> 'RandomProjection':
86
+ """Compute the graph embedding.
87
+
88
+ Parameters
89
+ ----------
90
+ input_matrix :
91
+ Adjacency matrix or biadjacency matrix of the graph.
92
+ force_bipartite : bool (default = ``False``)
93
+ If ``True``, force the input matrix to be considered as a biadjacency matrix.
94
+ Returns
95
+ -------
96
+ self: :class:`RandomProjection`
97
+ """
98
+ # input
99
+ input_matrix = check_format(input_matrix)
100
+ adjacency, self.bipartite = get_adjacency(input_matrix, force_bipartite=force_bipartite)
101
+ n = adjacency.shape[0]
102
+
103
+ # regularization
104
+ regularization = self._get_regularization(self.regularization, adjacency)
105
+ self.regularized = regularization > 0
106
+
107
+ # multiplier
108
+ if self.random_walk:
109
+ multiplier = Normalizer(adjacency, regularization)
110
+ else:
111
+ multiplier = Regularizer(adjacency, regularization)
112
+
113
+ # random matrix
114
+ random_generator = check_random_state(self.random_state)
115
+ random_matrix = random_generator.normal(size=(n, self.n_components))
116
+ random_matrix, _ = np.linalg.qr(random_matrix)
117
+
118
+ # random projection
119
+ factor = random_matrix
120
+ embedding = factor.copy()
121
+ for t in range(self.n_iter):
122
+ factor = self.alpha * multiplier.dot(factor)
123
+ embedding += factor
124
+
125
+ # normalization
126
+ if self.normalized:
127
+ embedding = normalize(embedding, p=2)
128
+
129
+ # output
130
+ self.embedding_ = embedding
131
+ if self.bipartite:
132
+ self._split_vars(input_matrix.shape)
133
+ return self
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Created on September 2018
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ @author: Thomas Bonald <bonald@enst.fr>
7
+ """
8
+ from typing import Union
9
+
10
+ import numpy as np
11
+ from scipy import sparse
12
+
13
+ from sknetwork.embedding.base import BaseEmbedding
14
+ from sknetwork.linalg import LanczosEig, Laplacian, Normalizer, normalize
15
+ from sknetwork.utils.format import get_adjacency
16
+ from sknetwork.utils.check import check_format, check_adjacency_vector, check_nonnegative, check_n_components
17
+
18
+
19
+ class Spectral(BaseEmbedding):
20
+ """Spectral embedding of graphs, based the spectral decomposition of the Laplacian matrix :math:`L = D - A`
21
+ or the transition matrix of the random walk :math:`P = D^{-1}A` (default), where :math:`D` is the
22
+ diagonal matrix of degrees.
23
+
24
+ Eigenvectors are considered in increasing order (for the Laplacian matrix :math:`L`) or decreasing order
25
+ (for the transition matrix of the random walk :math:`P`) of eigenvalues, skipping the first.
26
+
27
+ Parameters
28
+ ----------
29
+ n_components : int (default = ``2``)
30
+ Dimension of the embedding space.
31
+ decomposition : str (``laplacian`` or ``rw``, default = ``rw``)
32
+ Matrix used for the spectral decomposition.
33
+ regularization : float (default = ``-1``)
34
+ Regularization factor :math:`\\alpha` so that the adjacency matrix is :math:`A + \\alpha \\frac{11^T}{n}`.
35
+ If negative, regularization is applied only if the graph is disconnected; the regularization factor
36
+ :math:`\\alpha` is then set to the absolute value of the parameter.
37
+ normalized : bool (default = ``True``)
38
+ If ``True``, normalized the embedding so that each vector has norm 1 in the embedding space, i.e.,
39
+ each vector lies on the unit sphere.
40
+ Attributes
41
+ ----------
42
+ embedding_ : array, shape = (n, n_components)
43
+ Embedding of the nodes.
44
+ embedding_row_ : array, shape = (n_row, n_components)
45
+ Embedding of the rows, for bipartite graphs.
46
+ embedding_col_ : array, shape = (n_col, n_components)
47
+ Embedding of the columns, for bipartite graphs.
48
+ eigenvalues_ : array, shape = (n_components)
49
+ Eigenvalues.
50
+ eigenvectors_ : array, shape = (n, n_components)
51
+ Eigenvectors.
52
+
53
+ Example
54
+ -------
55
+ >>> from sknetwork.embedding import Spectral
56
+ >>> from sknetwork.data import karate_club
57
+ >>> spectral = Spectral(n_components=3)
58
+ >>> adjacency = karate_club()
59
+ >>> embedding = spectral.fit_transform(adjacency)
60
+ >>> embedding.shape
61
+ (34, 3)
62
+
63
+ References
64
+ ----------
65
+ Belkin, M. & Niyogi, P. (2003). Laplacian Eigenmaps for Dimensionality Reduction and Data Representation,
66
+ Neural computation.
67
+ """
68
+ def __init__(self, n_components: int = 2, decomposition: str = 'rw', regularization: float = -1,
69
+ normalized: bool = True):
70
+ super(Spectral, self).__init__()
71
+
72
+ self.embedding_ = None
73
+ self.n_components = n_components
74
+ self.decomposition = decomposition
75
+ self.regularization = regularization
76
+ self.normalized = normalized
77
+ self.bipartite = None
78
+ self.regularized = None
79
+ self.eigenvalues_ = None
80
+ self.eigenvectors_ = None
81
+
82
+ def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray], force_bipartite: bool = False) -> 'Spectral':
83
+ """Compute the graph embedding.
84
+
85
+ If the input matrix :math:`B` is not square (e.g., biadjacency matrix of a bipartite graph) or not symmetric
86
+ (e.g., adjacency matrix of a directed graph), use the adjacency matrix
87
+
88
+ :math:`A = \\begin{bmatrix} 0 & B \\\\ B^T & 0 \\end{bmatrix}`
89
+
90
+ and return the embedding for both rows and columns of the input matrix :math:`B`.
91
+
92
+ Parameters
93
+ ----------
94
+ input_matrix :
95
+ Adjacency matrix or biadjacency matrix of the graph.
96
+ force_bipartite : bool (default = ``False``)
97
+ If ``True``, force the input matrix to be considered as a biadjacency matrix.
98
+
99
+ Returns
100
+ -------
101
+ self: :class:`Spectral`
102
+ """
103
+ # input
104
+ input_matrix = check_format(input_matrix)
105
+ adjacency, self.bipartite = get_adjacency(input_matrix, allow_directed=False, force_bipartite=force_bipartite)
106
+ n = adjacency.shape[0]
107
+
108
+ # regularization
109
+ regularization = self._get_regularization(self.regularization, adjacency)
110
+ self.regularized = regularization > 0
111
+
112
+ # laplacian
113
+ normalized_laplacian = self.decomposition == 'rw'
114
+ laplacian = Laplacian(adjacency, regularization, normalized_laplacian)
115
+
116
+ # spectral decomposition
117
+ n_components = check_n_components(self.n_components, n - 2) + 1
118
+ solver = LanczosEig(which='SM')
119
+ solver.fit(matrix=laplacian, n_components=n_components)
120
+ index = np.argsort(solver.eigenvalues_)[1:] # increasing order, skip first
121
+
122
+ eigenvalues = solver.eigenvalues_[index]
123
+ eigenvectors = solver.eigenvectors_[:, index]
124
+
125
+ if normalized_laplacian:
126
+ eigenvectors = laplacian.norm_diag.dot(eigenvectors)
127
+ eigenvalues = 1 - eigenvalues
128
+
129
+ # embedding
130
+ embedding = eigenvectors.copy()
131
+ if self.normalized:
132
+ embedding = normalize(embedding, p=2)
133
+
134
+ # output
135
+ self.embedding_ = embedding
136
+ self.eigenvalues_ = eigenvalues
137
+ self.eigenvectors_ = eigenvectors
138
+ if self.bipartite:
139
+ self._split_vars(input_matrix.shape)
140
+
141
+ return self
142
+
143
+ def predict(self, adjacency_vectors: Union[sparse.csr_matrix, np.ndarray]) -> np.ndarray:
144
+ """Predict the embedding of new nodes, when possible (otherwise return 0).
145
+
146
+ Each new node is defined by its adjacency row vector.
147
+
148
+ Parameters
149
+ ----------
150
+ adjacency_vectors :
151
+ Adjacency vectors of nodes.
152
+ Array of shape (n_col,) (single vector) or (n_vectors, n_col)
153
+
154
+ Returns
155
+ -------
156
+ embedding_vectors : np.ndarray
157
+ Embedding of the nodes.
158
+
159
+ Example
160
+ -------
161
+ >>> from sknetwork.embedding import Spectral
162
+ >>> from sknetwork.data import karate_club
163
+ >>> spectral = Spectral(n_components=3)
164
+ >>> adjacency = karate_club()
165
+ >>> adjacency_vector = np.arange(34) < 5
166
+ >>> _ = spectral.fit(adjacency)
167
+ >>> len(spectral.predict(adjacency_vector))
168
+ 3
169
+ """
170
+ self._check_fitted()
171
+
172
+ # input
173
+ if self.bipartite:
174
+ n = len(self.embedding_col_)
175
+ else:
176
+ n = len(self.embedding_)
177
+ adjacency_vectors = check_adjacency_vector(adjacency_vectors, n)
178
+ check_nonnegative(adjacency_vectors)
179
+
180
+ if self.bipartite:
181
+ shape = (adjacency_vectors.shape[0], self.embedding_row_.shape[0])
182
+ adjacency_vectors = sparse.csr_matrix(adjacency_vectors)
183
+ adjacency_vectors = sparse.hstack([sparse.csr_matrix(shape), adjacency_vectors], format='csr')
184
+ eigenvectors = self.eigenvectors_
185
+ eigenvalues = self.eigenvalues_
186
+
187
+ # regularization
188
+ if self.regularized:
189
+ regularization = np.abs(self.regularization)
190
+ else:
191
+ regularization = 0
192
+ normalizer = Normalizer(adjacency_vectors, regularization)
193
+
194
+ # prediction
195
+ embedding_vectors = normalizer.dot(eigenvectors)
196
+ normalized_laplacian = self.decomposition == 'rw'
197
+ if normalized_laplacian:
198
+ norm_vect = eigenvalues.copy()
199
+ norm_vect[norm_vect == 0] = 1
200
+ embedding_vectors /= norm_vect
201
+ else:
202
+ norm_matrix = sparse.csr_matrix(1 - np.outer(normalizer.norm_diag.data, eigenvalues))
203
+ norm_matrix.data = 1 / norm_matrix.data
204
+ embedding_vectors *= norm_matrix.toarray()
205
+
206
+ # normalization
207
+ if self.normalized:
208
+ embedding_vectors = normalize(embedding_vectors, p=2)
209
+
210
+ # shape
211
+ if len(embedding_vectors) == 1:
212
+ embedding_vectors = embedding_vectors.ravel()
213
+
214
+ return embedding_vectors
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Created on Apr 2020
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ """
7
+ from typing import Optional, Union
8
+
9
+ import numpy as np
10
+ from scipy import sparse
11
+ from scipy.spatial import cKDTree
12
+
13
+ from sknetwork.embedding.base import BaseEmbedding
14
+ from sknetwork.embedding.spectral import Spectral
15
+ from sknetwork.linalg import normalize
16
+ from sknetwork.utils.check import check_adjacency_vector, check_format, check_square, is_symmetric
17
+ from sknetwork.utils.format import directed2undirected
18
+
19
+
20
+ class Spring(BaseEmbedding):
21
+ """Spring layout for displaying small graphs.
22
+
23
+ Parameters
24
+ ----------
25
+ n_components : int
26
+ Dimension of the graph layout.
27
+ strength : float
28
+ Intensity of the force that moves the nodes.
29
+ n_iter : int
30
+ Number of iterations to update positions.
31
+ tol : float
32
+ Minimum relative change in positions to continue updating.
33
+ approx_radius : float
34
+ If a positive value is provided, only the nodes within this distance a given node are used to compute
35
+ the repulsive force.
36
+ position_init : str
37
+ How to initialize the layout. If 'spectral', use Spectral embedding in dimension 2,
38
+ otherwise, use random initialization.
39
+
40
+ Attributes
41
+ ----------
42
+ embedding_ : np.ndarray
43
+ Layout.
44
+
45
+ Example
46
+ -------
47
+ >>> from sknetwork.embedding import Spring
48
+ >>> from sknetwork.data import karate_club
49
+ >>> spring = Spring()
50
+ >>> adjacency = karate_club()
51
+ >>> embedding = spring.fit_transform(adjacency)
52
+ >>> embedding.shape
53
+ (34, 2)
54
+
55
+ Notes
56
+ -----
57
+ Simple implementation designed to display small graphs.
58
+
59
+ References
60
+ ----------
61
+ Fruchterman, T. M. J., Reingold, E. M. (1991).
62
+ `Graph Drawing by Force-Directed Placement.
63
+ <https://onlinelibrary.wiley.com/doi/pdf/10.1002/spe.4380211102>`_
64
+ Software – Practice & Experience.
65
+ """
66
+ def __init__(self, n_components: int = 2, strength: float = None, n_iter: int = 50, tol: float = 1e-4,
67
+ approx_radius: float = -1, position_init: str = 'random'):
68
+ super(Spring, self).__init__()
69
+ self.n_components = n_components
70
+ self.strength = strength
71
+ self.n_iter = n_iter
72
+ self.tol = tol
73
+ self.approx_radius = approx_radius
74
+ if position_init not in ['random', 'spectral']:
75
+ raise ValueError('Unknown initial position, try "spectral" or "random".')
76
+ else:
77
+ self.position_init = position_init
78
+
79
+ def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray], position_init: Optional[np.ndarray] = None,
80
+ n_iter: Optional[int] = None) -> 'Spring':
81
+ """Compute layout.
82
+
83
+ Parameters
84
+ ----------
85
+ adjacency :
86
+ Adjacency matrix of the graph, treated as undirected.
87
+ position_init : np.ndarray
88
+ Custom initial positions of the nodes. Shape must be (n, 2).
89
+ If ``None``, use the value of self.pos_init.
90
+ n_iter : int
91
+ Number of iterations to update positions.
92
+ If ``None``, use the value of self.n_iter.
93
+
94
+ Returns
95
+ -------
96
+ self: :class:`Spring`
97
+ """
98
+ adjacency = check_format(adjacency)
99
+ check_square(adjacency)
100
+ if not is_symmetric(adjacency):
101
+ adjacency = directed2undirected(adjacency)
102
+ n = adjacency.shape[0]
103
+
104
+ position = np.zeros((n, self.n_components))
105
+ if position_init is None:
106
+ if self.position_init == 'random':
107
+ position = np.random.randn(n, self.n_components)
108
+ elif self.position_init == 'spectral':
109
+ position = Spectral(n_components=self.n_components).fit_transform(adjacency)
110
+ elif isinstance(position_init, np.ndarray):
111
+ if position_init.shape == (n, self.n_components):
112
+ position = position_init.copy()
113
+ else:
114
+ raise ValueError('Initial position has invalid shape.')
115
+ else:
116
+ raise TypeError('Initial position must be a numpy array.')
117
+
118
+ if n_iter is None:
119
+ n_iter = self.n_iter
120
+
121
+ if self.strength is None:
122
+ strength = np.sqrt((1 / n))
123
+ else:
124
+ strength = self.strength
125
+
126
+ pos_max = position.max(axis=0)
127
+ pos_min = position.min(axis=0)
128
+ step_max: float = 0.1 * (pos_max - pos_min).max()
129
+ step: float = step_max / (n_iter + 1)
130
+ tree = None
131
+
132
+ delta = np.zeros((n, self.n_components))
133
+ for iteration in range(n_iter):
134
+ delta *= 0
135
+ if self.approx_radius > 0:
136
+ tree = cKDTree(position)
137
+
138
+ for i in range(n):
139
+ # attraction
140
+ indices = adjacency.indices[adjacency.indptr[i]:adjacency.indptr[i+1]]
141
+ attraction = adjacency.data[adjacency.indptr[i]:adjacency.indptr[i+1]] / strength
142
+
143
+ grad = position[i] - position[indices]
144
+ attraction *= np.linalg.norm(grad, axis=1)
145
+ attraction = (grad * attraction[:, np.newaxis]).sum(axis=0)
146
+
147
+ # repulsion
148
+ if tree is None:
149
+ grad: np.ndarray = (position[i] - position) # shape (n, n_components)
150
+ distance: np.ndarray = np.linalg.norm(grad, axis=1) # shape (n,)
151
+ else:
152
+ neighbors = tree.query_ball_point(position[i], self.approx_radius)
153
+ grad: np.ndarray = (position[i] - position[neighbors]) # shape (n_neigh, n_components)
154
+ distance: np.ndarray = np.linalg.norm(grad, axis=1) # shape (n_neigh,)
155
+
156
+ distance = np.where(distance < 0.01, 0.01, distance)
157
+ repulsion = (grad * (strength / distance)[:, np.newaxis] ** 2).sum(axis=0)
158
+
159
+ # total force
160
+ delta[i]: np.ndarray = repulsion - attraction
161
+
162
+ length = np.linalg.norm(delta, axis=0)
163
+ length = np.where(length < 0.01, 0.1, length)
164
+ delta = delta * step_max / length
165
+ position += delta
166
+ step_max -= step
167
+ err: float = np.linalg.norm(delta) / n
168
+ if err < self.tol:
169
+ break
170
+
171
+ self.embedding_ = position
172
+ return self
173
+
174
+ def predict(self, adjacency_vectors: Union[sparse.csr_matrix, np.ndarray]) -> np.ndarray:
175
+ """Predict the embedding of new rows, defined by their adjacency vectors.
176
+
177
+ Parameters
178
+ ----------
179
+ adjacency_vectors :
180
+ Adjacency vectors of nodes.
181
+ Array of shape (n_col,) (single vector) or (n_vectors, n_col)
182
+
183
+ Returns
184
+ -------
185
+ embedding_vectors : np.ndarray
186
+ Embedding of the nodes.
187
+ """
188
+ self._check_fitted()
189
+ embedding = self.embedding_
190
+ n = embedding.shape[0]
191
+
192
+ adjacency_vectors = check_adjacency_vector(adjacency_vectors, n)
193
+ embedding_vectors = normalize(adjacency_vectors).dot(embedding)
194
+
195
+ if embedding_vectors.shape[0] == 1:
196
+ embedding_vectors = embedding_vectors.ravel()
197
+
198
+ return embedding_vectors