scikit-network 0.33.4__cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. scikit_network-0.33.4.dist-info/METADATA +122 -0
  2. scikit_network-0.33.4.dist-info/RECORD +229 -0
  3. scikit_network-0.33.4.dist-info/WHEEL +6 -0
  4. scikit_network-0.33.4.dist-info/licenses/AUTHORS.rst +43 -0
  5. scikit_network-0.33.4.dist-info/licenses/LICENSE +34 -0
  6. scikit_network-0.33.4.dist-info/top_level.txt +1 -0
  7. scikit_network.libs/libgomp-a34b3233.so.1.0.0 +0 -0
  8. sknetwork/__init__.py +21 -0
  9. sknetwork/base.py +67 -0
  10. sknetwork/classification/__init__.py +8 -0
  11. sknetwork/classification/base.py +138 -0
  12. sknetwork/classification/base_rank.py +129 -0
  13. sknetwork/classification/diffusion.py +127 -0
  14. sknetwork/classification/knn.py +131 -0
  15. sknetwork/classification/metrics.py +205 -0
  16. sknetwork/classification/pagerank.py +58 -0
  17. sknetwork/classification/propagation.py +144 -0
  18. sknetwork/classification/tests/__init__.py +1 -0
  19. sknetwork/classification/tests/test_API.py +30 -0
  20. sknetwork/classification/tests/test_diffusion.py +77 -0
  21. sknetwork/classification/tests/test_knn.py +23 -0
  22. sknetwork/classification/tests/test_metrics.py +53 -0
  23. sknetwork/classification/tests/test_pagerank.py +20 -0
  24. sknetwork/classification/tests/test_propagation.py +24 -0
  25. sknetwork/classification/vote.cpp +27593 -0
  26. sknetwork/classification/vote.cpython-312-x86_64-linux-gnu.so +0 -0
  27. sknetwork/classification/vote.pyx +56 -0
  28. sknetwork/clustering/__init__.py +8 -0
  29. sknetwork/clustering/base.py +168 -0
  30. sknetwork/clustering/kcenters.py +251 -0
  31. sknetwork/clustering/leiden.py +238 -0
  32. sknetwork/clustering/leiden_core.cpp +31928 -0
  33. sknetwork/clustering/leiden_core.cpython-312-x86_64-linux-gnu.so +0 -0
  34. sknetwork/clustering/leiden_core.pyx +124 -0
  35. sknetwork/clustering/louvain.py +282 -0
  36. sknetwork/clustering/louvain_core.cpp +31573 -0
  37. sknetwork/clustering/louvain_core.cpython-312-x86_64-linux-gnu.so +0 -0
  38. sknetwork/clustering/louvain_core.pyx +124 -0
  39. sknetwork/clustering/metrics.py +91 -0
  40. sknetwork/clustering/postprocess.py +66 -0
  41. sknetwork/clustering/propagation_clustering.py +100 -0
  42. sknetwork/clustering/tests/__init__.py +1 -0
  43. sknetwork/clustering/tests/test_API.py +38 -0
  44. sknetwork/clustering/tests/test_kcenters.py +60 -0
  45. sknetwork/clustering/tests/test_leiden.py +34 -0
  46. sknetwork/clustering/tests/test_louvain.py +135 -0
  47. sknetwork/clustering/tests/test_metrics.py +50 -0
  48. sknetwork/clustering/tests/test_postprocess.py +39 -0
  49. sknetwork/data/__init__.py +6 -0
  50. sknetwork/data/base.py +33 -0
  51. sknetwork/data/load.py +292 -0
  52. sknetwork/data/models.py +459 -0
  53. sknetwork/data/parse.py +644 -0
  54. sknetwork/data/test_graphs.py +93 -0
  55. sknetwork/data/tests/__init__.py +1 -0
  56. sknetwork/data/tests/test_API.py +30 -0
  57. sknetwork/data/tests/test_base.py +14 -0
  58. sknetwork/data/tests/test_load.py +61 -0
  59. sknetwork/data/tests/test_models.py +52 -0
  60. sknetwork/data/tests/test_parse.py +250 -0
  61. sknetwork/data/tests/test_test_graphs.py +29 -0
  62. sknetwork/data/tests/test_toy_graphs.py +68 -0
  63. sknetwork/data/timeout.py +38 -0
  64. sknetwork/data/toy_graphs.py +611 -0
  65. sknetwork/embedding/__init__.py +8 -0
  66. sknetwork/embedding/base.py +90 -0
  67. sknetwork/embedding/force_atlas.py +198 -0
  68. sknetwork/embedding/louvain_embedding.py +142 -0
  69. sknetwork/embedding/random_projection.py +131 -0
  70. sknetwork/embedding/spectral.py +137 -0
  71. sknetwork/embedding/spring.py +198 -0
  72. sknetwork/embedding/svd.py +351 -0
  73. sknetwork/embedding/tests/__init__.py +1 -0
  74. sknetwork/embedding/tests/test_API.py +49 -0
  75. sknetwork/embedding/tests/test_force_atlas.py +35 -0
  76. sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
  77. sknetwork/embedding/tests/test_random_projection.py +28 -0
  78. sknetwork/embedding/tests/test_spectral.py +81 -0
  79. sknetwork/embedding/tests/test_spring.py +50 -0
  80. sknetwork/embedding/tests/test_svd.py +43 -0
  81. sknetwork/gnn/__init__.py +10 -0
  82. sknetwork/gnn/activation.py +117 -0
  83. sknetwork/gnn/base.py +181 -0
  84. sknetwork/gnn/base_activation.py +90 -0
  85. sknetwork/gnn/base_layer.py +109 -0
  86. sknetwork/gnn/gnn_classifier.py +305 -0
  87. sknetwork/gnn/layer.py +153 -0
  88. sknetwork/gnn/loss.py +180 -0
  89. sknetwork/gnn/neighbor_sampler.py +65 -0
  90. sknetwork/gnn/optimizer.py +164 -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 +75 -0
  94. sknetwork/gnn/tests/test_base_layer.py +37 -0
  95. sknetwork/gnn/tests/test_gnn_classifier.py +130 -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 +41 -0
  101. sknetwork/gnn/utils.py +127 -0
  102. sknetwork/hierarchy/__init__.py +6 -0
  103. sknetwork/hierarchy/base.py +90 -0
  104. sknetwork/hierarchy/louvain_hierarchy.py +260 -0
  105. sknetwork/hierarchy/metrics.py +234 -0
  106. sknetwork/hierarchy/paris.cpp +37877 -0
  107. sknetwork/hierarchy/paris.cpython-312-x86_64-linux-gnu.so +0 -0
  108. sknetwork/hierarchy/paris.pyx +310 -0
  109. sknetwork/hierarchy/postprocess.py +350 -0
  110. sknetwork/hierarchy/tests/__init__.py +1 -0
  111. sknetwork/hierarchy/tests/test_API.py +24 -0
  112. sknetwork/hierarchy/tests/test_algos.py +34 -0
  113. sknetwork/hierarchy/tests/test_metrics.py +62 -0
  114. sknetwork/hierarchy/tests/test_postprocess.py +57 -0
  115. sknetwork/linalg/__init__.py +9 -0
  116. sknetwork/linalg/basics.py +37 -0
  117. sknetwork/linalg/diteration.cpp +27409 -0
  118. sknetwork/linalg/diteration.cpython-312-x86_64-linux-gnu.so +0 -0
  119. sknetwork/linalg/diteration.pyx +47 -0
  120. sknetwork/linalg/eig_solver.py +93 -0
  121. sknetwork/linalg/laplacian.py +15 -0
  122. sknetwork/linalg/normalizer.py +86 -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.cpp +31081 -0
  127. sknetwork/linalg/push.cpython-312-x86_64-linux-gnu.so +0 -0
  128. sknetwork/linalg/push.pyx +71 -0
  129. sknetwork/linalg/sparse_lowrank.py +142 -0
  130. sknetwork/linalg/svd_solver.py +91 -0
  131. sknetwork/linalg/tests/__init__.py +1 -0
  132. sknetwork/linalg/tests/test_eig.py +44 -0
  133. sknetwork/linalg/tests/test_laplacian.py +18 -0
  134. sknetwork/linalg/tests/test_normalization.py +34 -0
  135. sknetwork/linalg/tests/test_operators.py +66 -0
  136. sknetwork/linalg/tests/test_polynome.py +38 -0
  137. sknetwork/linalg/tests/test_ppr.py +50 -0
  138. sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
  139. sknetwork/linalg/tests/test_svd.py +38 -0
  140. sknetwork/linkpred/__init__.py +2 -0
  141. sknetwork/linkpred/base.py +46 -0
  142. sknetwork/linkpred/nn.py +126 -0
  143. sknetwork/linkpred/tests/__init__.py +1 -0
  144. sknetwork/linkpred/tests/test_nn.py +26 -0
  145. sknetwork/log.py +19 -0
  146. sknetwork/path/__init__.py +5 -0
  147. sknetwork/path/dag.py +54 -0
  148. sknetwork/path/distances.py +98 -0
  149. sknetwork/path/search.py +31 -0
  150. sknetwork/path/shortest_path.py +61 -0
  151. sknetwork/path/tests/__init__.py +1 -0
  152. sknetwork/path/tests/test_dag.py +37 -0
  153. sknetwork/path/tests/test_distances.py +62 -0
  154. sknetwork/path/tests/test_search.py +40 -0
  155. sknetwork/path/tests/test_shortest_path.py +40 -0
  156. sknetwork/ranking/__init__.py +8 -0
  157. sknetwork/ranking/base.py +57 -0
  158. sknetwork/ranking/betweenness.cpp +9716 -0
  159. sknetwork/ranking/betweenness.cpython-312-x86_64-linux-gnu.so +0 -0
  160. sknetwork/ranking/betweenness.pyx +97 -0
  161. sknetwork/ranking/closeness.py +92 -0
  162. sknetwork/ranking/hits.py +90 -0
  163. sknetwork/ranking/katz.py +79 -0
  164. sknetwork/ranking/pagerank.py +106 -0
  165. sknetwork/ranking/postprocess.py +37 -0
  166. sknetwork/ranking/tests/__init__.py +1 -0
  167. sknetwork/ranking/tests/test_API.py +32 -0
  168. sknetwork/ranking/tests/test_betweenness.py +38 -0
  169. sknetwork/ranking/tests/test_closeness.py +30 -0
  170. sknetwork/ranking/tests/test_hits.py +20 -0
  171. sknetwork/ranking/tests/test_pagerank.py +62 -0
  172. sknetwork/ranking/tests/test_postprocess.py +26 -0
  173. sknetwork/regression/__init__.py +4 -0
  174. sknetwork/regression/base.py +57 -0
  175. sknetwork/regression/diffusion.py +204 -0
  176. sknetwork/regression/tests/__init__.py +1 -0
  177. sknetwork/regression/tests/test_API.py +32 -0
  178. sknetwork/regression/tests/test_diffusion.py +56 -0
  179. sknetwork/sknetwork.py +3 -0
  180. sknetwork/test_base.py +35 -0
  181. sknetwork/test_log.py +15 -0
  182. sknetwork/topology/__init__.py +8 -0
  183. sknetwork/topology/cliques.cpp +32574 -0
  184. sknetwork/topology/cliques.cpython-312-x86_64-linux-gnu.so +0 -0
  185. sknetwork/topology/cliques.pyx +149 -0
  186. sknetwork/topology/core.cpp +30660 -0
  187. sknetwork/topology/core.cpython-312-x86_64-linux-gnu.so +0 -0
  188. sknetwork/topology/core.pyx +90 -0
  189. sknetwork/topology/cycles.py +243 -0
  190. sknetwork/topology/minheap.cpp +27341 -0
  191. sknetwork/topology/minheap.cpython-312-x86_64-linux-gnu.so +0 -0
  192. sknetwork/topology/minheap.pxd +20 -0
  193. sknetwork/topology/minheap.pyx +109 -0
  194. sknetwork/topology/structure.py +194 -0
  195. sknetwork/topology/tests/__init__.py +1 -0
  196. sknetwork/topology/tests/test_cliques.py +28 -0
  197. sknetwork/topology/tests/test_core.py +19 -0
  198. sknetwork/topology/tests/test_cycles.py +65 -0
  199. sknetwork/topology/tests/test_structure.py +85 -0
  200. sknetwork/topology/tests/test_triangles.py +38 -0
  201. sknetwork/topology/tests/test_wl.py +72 -0
  202. sknetwork/topology/triangles.cpp +8903 -0
  203. sknetwork/topology/triangles.cpython-312-x86_64-linux-gnu.so +0 -0
  204. sknetwork/topology/triangles.pyx +151 -0
  205. sknetwork/topology/weisfeiler_lehman.py +133 -0
  206. sknetwork/topology/weisfeiler_lehman_core.cpp +27644 -0
  207. sknetwork/topology/weisfeiler_lehman_core.cpython-312-x86_64-linux-gnu.so +0 -0
  208. sknetwork/topology/weisfeiler_lehman_core.pyx +114 -0
  209. sknetwork/utils/__init__.py +7 -0
  210. sknetwork/utils/check.py +355 -0
  211. sknetwork/utils/format.py +221 -0
  212. sknetwork/utils/membership.py +82 -0
  213. sknetwork/utils/neighbors.py +115 -0
  214. sknetwork/utils/tests/__init__.py +1 -0
  215. sknetwork/utils/tests/test_check.py +190 -0
  216. sknetwork/utils/tests/test_format.py +63 -0
  217. sknetwork/utils/tests/test_membership.py +24 -0
  218. sknetwork/utils/tests/test_neighbors.py +41 -0
  219. sknetwork/utils/tests/test_tfidf.py +18 -0
  220. sknetwork/utils/tests/test_values.py +66 -0
  221. sknetwork/utils/tfidf.py +37 -0
  222. sknetwork/utils/values.py +76 -0
  223. sknetwork/visualization/__init__.py +4 -0
  224. sknetwork/visualization/colors.py +34 -0
  225. sknetwork/visualization/dendrograms.py +277 -0
  226. sknetwork/visualization/graphs.py +1039 -0
  227. sknetwork/visualization/tests/__init__.py +1 -0
  228. sknetwork/visualization/tests/test_dendrograms.py +53 -0
  229. sknetwork/visualization/tests/test_graphs.py +176 -0
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created in November 2019
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ """
7
+ from abc import ABC
8
+ from typing import Optional, Union
9
+
10
+ import numpy as np
11
+ from scipy import sparse
12
+
13
+ from sknetwork.topology.structure import is_connected
14
+ from sknetwork.base import Algorithm
15
+
16
+
17
+ class BaseEmbedding(Algorithm, ABC):
18
+ """Base class for embedding algorithms.
19
+
20
+ Attributes
21
+ ----------
22
+ embedding\_ : np.ndarray, shape = (n_nodes, n_components)
23
+ Embedding of the nodes.
24
+ """
25
+ def __init__(self):
26
+ self._init_vars()
27
+
28
+ def transform(self) -> np.ndarray:
29
+ """Return the embedding.
30
+
31
+ Returns
32
+ -------
33
+ embedding : np.ndarray
34
+ Embedding.
35
+ """
36
+ return self.embedding_
37
+
38
+ def fit_transform(self, *args, **kwargs) -> np.ndarray:
39
+ """Fit to data and return the embedding. Same parameters as the ``fit`` method.
40
+
41
+ Returns
42
+ -------
43
+ embedding : np.ndarray
44
+ Embedding.
45
+ """
46
+ self.fit(*args, **kwargs)
47
+ return self.embedding_
48
+
49
+ def predict(self, columns: bool = False) -> np.ndarray:
50
+ """Return the embedding of nodes.
51
+
52
+ Parameters
53
+ ----------
54
+ columns : bool
55
+ If ``True``, return the prediction for columns.
56
+
57
+ Returns
58
+ -------
59
+ embedding : np.ndarray
60
+ Embedding of the nodes.
61
+ """
62
+ if columns:
63
+ return self.embedding_col_
64
+ return self.embedding_
65
+
66
+ @staticmethod
67
+ def _get_regularization(regularization: float, adjacency: sparse.csr_matrix) -> float:
68
+ """Set proper regularization depending on graph connectivity."""
69
+ if regularization < 0:
70
+ if is_connected(adjacency, connection='strong'):
71
+ regularization = 0
72
+ else:
73
+ regularization = np.abs(regularization)
74
+ return regularization
75
+
76
+ def _init_vars(self):
77
+ self.embedding_ = None
78
+ self.embedding_row_ = None
79
+ self.embedding_col_ = None
80
+
81
+ def _check_fitted(self):
82
+ return self.embedding_ is not None
83
+
84
+ def _split_vars(self, shape):
85
+ """Split labels_ into labels_row_ and labels_col_"""
86
+ n_row = shape[0]
87
+ self.embedding_row_ = self.embedding_[:n_row]
88
+ self.embedding_col_ = self.embedding_[n_row:]
89
+ self.embedding_ = self.embedding_row_
90
+ return self
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Created on Jun 2020
5
+ @author: Victor Manach <victor.manach@telecom-paris.fr>
6
+ @author: Rémi Jaylet <remi.jaylet@telecom-paris.fr>
7
+ """
8
+ from typing import Optional, Union
9
+
10
+ import numpy as np
11
+ from scipy import sparse
12
+ from scipy.spatial import cKDTree
13
+
14
+ from sknetwork.embedding.base import BaseEmbedding
15
+ from sknetwork.utils.check import check_format, is_symmetric, check_square
16
+ from sknetwork.utils.format import directed2undirected
17
+
18
+
19
+ class ForceAtlas(BaseEmbedding):
20
+ """Force Atlas layout for displaying graphs.
21
+
22
+ Parameters
23
+ ----------
24
+ n_components : int
25
+ Dimension of the graph layout.
26
+ n_iter : int
27
+ Number of iterations to update positions.
28
+ If ``None``, use the value of self.n_iter.
29
+ approx_radius : float
30
+ If a positive value is provided, only the nodes within this distance a given node are used to compute
31
+ the repulsive force.
32
+ lin_log : bool
33
+ If ``True``, use lin-log mode.
34
+ gravity_factor : float
35
+ Gravity force scaling constant.
36
+ repulsive_factor : float
37
+ Repulsive force scaling constant.
38
+ tolerance : float
39
+ Tolerance defined in the swinging constant.
40
+ speed : float
41
+ Speed constant.
42
+ speed_max : float
43
+ Constant used to impose constrain on speed.
44
+
45
+ Attributes
46
+ ----------
47
+ embedding\_ : np.ndarray, shape = (n_nodes, n_components)
48
+ Embedding of the nodes.
49
+
50
+ Example
51
+ -------
52
+ >>> from sknetwork.embedding.force_atlas import ForceAtlas
53
+ >>> from sknetwork.data import karate_club
54
+ >>> force_atlas = ForceAtlas()
55
+ >>> adjacency = karate_club()
56
+ >>> embedding = force_atlas.fit_transform(adjacency)
57
+ >>> embedding.shape
58
+ (34, 2)
59
+
60
+ References
61
+ ----------
62
+ Jacomy M., Venturini T., Heymann S., Bastian M. (2014).
63
+ `ForceAtlas2, a Continuous Graph Layout Algorithm for Handy Network Visualization Designed for the Gephi Software.
64
+ <https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0098679>`_
65
+ Plos One.
66
+ """
67
+ def __init__(self, n_components: int = 2, n_iter: int = 50, approx_radius: float = -1, lin_log: bool = False,
68
+ gravity_factor: float = 0.01, repulsive_factor: float = 0.1, tolerance: float = 0.1,
69
+ speed: float = 0.1, speed_max: float = 10):
70
+ super(ForceAtlas, self).__init__()
71
+ self.n_components = n_components
72
+ self.n_iter = n_iter
73
+ self.approx_radius = approx_radius
74
+ self.lin_log = lin_log
75
+ self.gravity_factor = gravity_factor
76
+ self.repulsive_factor = repulsive_factor
77
+ self.tolerance = tolerance
78
+ self.speed = speed
79
+ self.speed_max = speed_max
80
+ self.embedding_ = None
81
+
82
+ def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray], pos_init: Optional[np.ndarray] = None,
83
+ n_iter: Optional[int] = None) -> 'ForceAtlas':
84
+ """Compute layout.
85
+
86
+ Parameters
87
+ ----------
88
+ adjacency :
89
+ Adjacency matrix of the graph, treated as undirected.
90
+ pos_init :
91
+ Position to start with. Random if not provided.
92
+ n_iter : int
93
+ Number of iterations to update positions.
94
+ If ``None``, use the value of self.n_iter.
95
+
96
+ Returns
97
+ -------
98
+ self: :class:`ForceAtlas`
99
+ """
100
+ # verify the format of the adjacency matrix
101
+ adjacency = check_format(adjacency)
102
+ check_square(adjacency)
103
+ if not is_symmetric(adjacency):
104
+ adjacency = directed2undirected(adjacency)
105
+ n = adjacency.shape[0]
106
+
107
+ # setting of the tolerance according to the size of the graph
108
+ if n < 5000:
109
+ tolerance = 0.1
110
+ elif 5000 <= n < 50000: # pragma: no cover
111
+ tolerance = 1
112
+ else: # pragma: no cover
113
+ tolerance = 10
114
+
115
+ if n_iter is None:
116
+ n_iter = self.n_iter
117
+
118
+ # initial position of the nodes of the graph
119
+ if pos_init is None:
120
+ position: np.ndarray = np.random.randn(n, self.n_components)
121
+ else:
122
+ if pos_init.shape != (n, self.n_components):
123
+ raise ValueError('The initial position does not have valid dimensions.')
124
+ else:
125
+ position = pos_init
126
+ # compute the vector with the degree of each node
127
+ degree: np.ndarray = adjacency.dot(np.ones(adjacency.shape[1])) + 1
128
+
129
+ # initialization of variation of position of nodes
130
+ resultants = np.zeros(n)
131
+ delta: np.ndarray = np.zeros((n, self.n_components))
132
+ swing_vector: np.ndarray = np.zeros(n)
133
+ global_speed = 1
134
+
135
+ for iteration in range(n_iter):
136
+ delta *= 0
137
+ global_swing = 0
138
+ global_traction = 0
139
+
140
+ if self.approx_radius > 0:
141
+ tree = cKDTree(position)
142
+ else:
143
+ tree = None
144
+
145
+ for i in range(n):
146
+
147
+ # attraction
148
+ indices = adjacency.indices[adjacency.indptr[i]:adjacency.indptr[i + 1]]
149
+ attraction = position[i] - position[indices]
150
+
151
+ if self.lin_log:
152
+ attraction = np.sign(attraction) * np.log(1 + np.abs(10 * attraction))
153
+ attraction = attraction.sum(axis=0)
154
+
155
+ # repulsion
156
+ if tree is None:
157
+ neighbors = np.arange(n)
158
+ else:
159
+ neighbors = tree.query_ball_point(position[i], self.approx_radius, p=2)
160
+
161
+ grad: np.ndarray = (position[i] - position[neighbors]) # shape (n_neigh, n_components)
162
+ distance: np.ndarray = np.linalg.norm(grad, axis=1) # shape (n_neigh,)
163
+ distance = np.where(distance < 0.01, 0.01, distance)
164
+ repulsion = grad * (degree[neighbors] / distance)[:, np.newaxis]
165
+
166
+ repulsion *= self.repulsive_factor * degree[i]
167
+ repulsion = repulsion.sum(axis=0)
168
+
169
+ # gravity
170
+ gravity = self.gravity_factor * degree[i] * grad
171
+ gravity = gravity.sum(axis=0)
172
+
173
+ # forces resultant applied on node i for traction, swing and speed computation
174
+ force = repulsion - attraction - gravity
175
+ resultant_new: float = np.linalg.norm(force)
176
+ resultant_old: float = resultants[i]
177
+
178
+ swing_node: float = np.abs(resultant_new - resultant_old) # force variation applied on node i
179
+ swing_vector[i] = swing_node
180
+ global_swing += (degree[i] + 1) * swing_node
181
+
182
+ traction: float = np.abs(resultant_new + resultant_old) / 2 # traction force applied on node i
183
+ global_traction += (degree[i] + 1) * traction
184
+
185
+ node_speed = self.speed * global_speed / (1 + global_speed * np.sqrt(swing_node))
186
+ if node_speed > self.speed_max / resultant_new: # pragma: no cover
187
+ node_speed = self.speed_max / resultant_new
188
+
189
+ delta[i]: np.ndarray = node_speed * force
190
+ resultants[i] = resultant_new
191
+ global_speed = tolerance * global_traction / global_swing
192
+
193
+ position += delta # calculating displacement and final position of points after iteration
194
+ if (swing_vector < 1).all():
195
+ break # if the swing of all nodes is zero, then convergence is reached.
196
+
197
+ self.embedding_ = position
198
+ return self
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Created in September 2020
5
+ @author: Quentin Lutz <qlutz@enst.fr>
6
+ @author: Thomas Bonald <bonald@enst.fr>
7
+ """
8
+ from typing import Optional, Union
9
+
10
+ import numpy as np
11
+ from scipy import sparse
12
+
13
+ from sknetwork.clustering.louvain import Louvain
14
+ from sknetwork.embedding.base import BaseEmbedding
15
+ from sknetwork.linalg.normalizer import normalize
16
+ from sknetwork.utils.check import check_random_state, check_adjacency_vector, check_nonnegative, is_square
17
+ from sknetwork.utils.membership import get_membership
18
+
19
+
20
+ def reindex_labels(labels: np.ndarray, labels_secondary: Optional[np.ndarray] = None, which: str = 'remove'):
21
+ """Reindex labels, removing or merging labels of count 1."""
22
+ labels_unique, counts = np.unique(labels, return_counts=True)
23
+ n_labels = max(labels_unique) + 1
24
+ labels_keep = labels_unique[counts > 1]
25
+ if which == 'remove':
26
+ label_index = -np.ones(n_labels, dtype='int')
27
+ label_index[labels_keep] = np.arange(len(labels_keep))
28
+ elif which == 'merge':
29
+ label_index = len(labels_keep) * np.ones(n_labels, dtype='int')
30
+ label_index[labels_keep] = np.arange(len(labels_keep))
31
+ else:
32
+ label_index = np.arange(n_labels)
33
+ labels = label_index[labels]
34
+ if labels_secondary is not None:
35
+ labels_unique = np.unique(labels_secondary)
36
+ n_labels = max(labels_unique) + 1
37
+ label_index = -np.ones(n_labels, dtype='int')
38
+ label_index[labels_keep] = np.arange(len(labels_keep))
39
+ labels_secondary = label_index[labels_secondary]
40
+ return labels, labels_secondary
41
+
42
+
43
+ class LouvainEmbedding(BaseEmbedding):
44
+ """Embedding of graphs induced by Louvain clustering. Each component of the embedding corresponds
45
+ to a cluster obtained by Louvain.
46
+
47
+ Parameters
48
+ ----------
49
+ resolution : float
50
+ Resolution parameter.
51
+ modularity : str
52
+ Which objective function to maximize. Can be ``'Dugue'``, ``'Newman'`` or ``'Potts'``.
53
+ tol_optimization :
54
+ Minimum increase in the objective function to enter a new optimization pass.
55
+ tol_aggregation :
56
+ Minimum increase in the objective function to enter a new aggregation pass.
57
+ n_aggregations :
58
+ Maximum number of aggregations.
59
+ A negative value is interpreted as no limit.
60
+ shuffle_nodes :
61
+ Enables node shuffling before optimization.
62
+ random_state :
63
+ Random number generator or random seed. If ``None``, numpy.random is used.
64
+ isolated_nodes : str
65
+ What to do with isolated column nodes. Can be ``'remove'`` (default), ``'merge'`` or ``'keep'``.
66
+
67
+ Attributes
68
+ ----------
69
+ embedding\_ : np.ndarray, shape = (n_nodes, n_components)
70
+ Embedding of the nodes.
71
+ labels\_ : np.ndarray, shape = (n_nodes,)
72
+ Labels of the nodes, used for the embedding.
73
+
74
+ Example
75
+ -------
76
+ >>> from sknetwork.embedding import LouvainEmbedding
77
+ >>> from sknetwork.data import house
78
+ >>> louvain = LouvainEmbedding()
79
+ >>> adjacency = house()
80
+ >>> embedding = louvain.fit_transform(adjacency)
81
+ >>> embedding.shape
82
+ (5, 2)
83
+ """
84
+ def __init__(self, resolution: float = 1, modularity: str = 'Dugue', tol_optimization: float = 1e-3,
85
+ tol_aggregation: float = 1e-3, n_aggregations: int = -1, shuffle_nodes: bool = False,
86
+ random_state: Optional[Union[np.random.RandomState, int]] = None, isolated_nodes: str = 'remove'):
87
+ super(LouvainEmbedding, self).__init__()
88
+ self.resolution = resolution
89
+ self.modularity = modularity.lower()
90
+ self.tol_optimization = tol_optimization
91
+ self.tol_aggregation = tol_aggregation
92
+ self.n_aggregations = n_aggregations
93
+ self.shuffle_nodes = shuffle_nodes
94
+ self.random_state = check_random_state(random_state)
95
+ self.isolated_nodes = isolated_nodes
96
+
97
+ self.labels_ = None
98
+ self.embedding_ = None
99
+ self.embedding_row_ = None
100
+ self.embedding_col_ = None
101
+
102
+ def fit(self, input_matrix: sparse.csr_matrix, force_bipartite: bool = False):
103
+ """Embedding of graphs from the clustering obtained with Louvain.
104
+
105
+ Parameters
106
+ ----------
107
+ input_matrix :
108
+ Adjacency matrix or biadjacency matrix of the graph.
109
+ force_bipartite : bool (default = ``False``)
110
+ If ``True``, force the input matrix to be considered as a biadjacency matrix.
111
+ Returns
112
+ -------
113
+ self: :class:`BiLouvainEmbedding`
114
+ """
115
+ louvain = Louvain(resolution=self.resolution, modularity=self.modularity,
116
+ tol_optimization=self.tol_optimization, tol_aggregation=self.tol_aggregation,
117
+ n_aggregations=self.n_aggregations, shuffle_nodes=self.shuffle_nodes, sort_clusters=False,
118
+ return_probs=True, return_aggregate=True, random_state=self.random_state)
119
+ louvain.fit(input_matrix, force_bipartite=force_bipartite)
120
+
121
+ # isolated nodes
122
+ if is_square(input_matrix):
123
+ labels = louvain.labels_
124
+ labels_secondary = None
125
+ else:
126
+ labels = louvain.labels_col_
127
+ labels_secondary = louvain.labels_row_
128
+
129
+ self.labels_, labels_row = reindex_labels(labels, labels_secondary, self.isolated_nodes)
130
+
131
+ # embedding
132
+ probs = normalize(input_matrix)
133
+ embedding_ = probs.dot(get_membership(self.labels_))
134
+ self.embedding_ = embedding_.toarray()
135
+
136
+ if labels_row is not None:
137
+ probs = normalize(input_matrix.T)
138
+ embedding_col = probs.dot(get_membership(labels_row))
139
+ self.embedding_row_ = self.embedding_
140
+ self.embedding_col_ = embedding_col.toarray()
141
+
142
+ return self
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Created in January 2021
5
+ @author: Thomas Bonald <bonald@enst.fr>
6
+ """
7
+ from abc import ABC
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 Regularizer, Normalizer, normalize
15
+ from sknetwork.utils.check import check_format, check_random_state
16
+ from sknetwork.utils.format import get_adjacency
17
+
18
+
19
+ class RandomProjection(BaseEmbedding, ABC):
20
+ """Embedding of graphs based the random projection of the adjacency matrix:
21
+
22
+ :math:`(I + \\alpha A +... + (\\alpha A)^K)G`
23
+
24
+ where :math:`A` is the adjacency matrix, :math:`G` is a random Gaussian matrix,
25
+ :math:`\\alpha` is some smoothing factor and :math:`K` some non-negative integer.
26
+
27
+ Parameters
28
+ ----------
29
+ n_components : int (default = 2)
30
+ Dimension of the embedding space.
31
+ alpha : float (default = 0.5)
32
+ Smoothing parameter.
33
+ n_iter : int (default = 3)
34
+ Number of power iterations of the adjacency matrix.
35
+ random_walk : bool (default = ``False``)
36
+ If ``True``, use the transition matrix of the random walk, :math:`P = D^{-1}A`, instead of the adjacency matrix.
37
+ regularization : float (default = ``-1``)
38
+ Regularization factor :math:`\\alpha` so that the matrix is :math:`A + \\alpha \\frac{11^T}{n}`.
39
+ If negative, regularization is applied only if the graph is disconnected (and then equal to the absolute value
40
+ of the parameter).
41
+ normalized : bool (default = ``True``)
42
+ If ``True``, normalize the embedding so that each vector has norm 1 in the embedding space, i.e.,
43
+ each vector lies on the unit sphere.
44
+ random_state : int, optional
45
+ Seed used by the random number generator.
46
+
47
+ Attributes
48
+ ----------
49
+ embedding\_ : array, shape = (n_nodes, n_components)
50
+ Embedding of the nodes.
51
+
52
+ Example
53
+ -------
54
+ >>> from sknetwork.embedding import RandomProjection
55
+ >>> from sknetwork.data import karate_club
56
+ >>> projection = RandomProjection()
57
+ >>> adjacency = karate_club()
58
+ >>> embedding = projection.fit_transform(adjacency)
59
+ >>> embedding.shape
60
+ (34, 2)
61
+
62
+ References
63
+ ----------
64
+ Zhang, Z., Cui, P., Li, H., Wang, X., & Zhu, W. (2018).
65
+ Billion-scale network embedding with iterative random projection, ICDM.
66
+ """
67
+ def __init__(self, n_components: int = 2, alpha: float = 0.5, n_iter: int = 3, random_walk: bool = False,
68
+ regularization: float = -1, normalized: bool = True, random_state: int = None):
69
+ super(RandomProjection, self).__init__()
70
+
71
+ self.embedding_ = None
72
+ self.n_components = n_components
73
+ self.alpha = alpha
74
+ self.n_iter = n_iter
75
+ self.random_walk = random_walk
76
+ self.regularization = regularization
77
+ self.normalized = normalized
78
+ self.random_state = random_state
79
+ self.bipartite = None
80
+ self.regularized = None
81
+
82
+ def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray], force_bipartite: bool = False) \
83
+ -> 'RandomProjection':
84
+ """Compute the graph embedding.
85
+
86
+ Parameters
87
+ ----------
88
+ input_matrix : sparse.csr_matrix, np.ndarray
89
+ Adjacency matrix or biadjacency matrix of the graph.
90
+ force_bipartite : bool (default = ``False``)
91
+ If ``True``, force the input matrix to be considered as a biadjacency matrix.
92
+ Returns
93
+ -------
94
+ self: :class:`RandomProjection`
95
+ """
96
+ # input
97
+ input_matrix = check_format(input_matrix)
98
+ adjacency, self.bipartite = get_adjacency(input_matrix, force_bipartite=force_bipartite)
99
+ n = adjacency.shape[0]
100
+
101
+ # regularization
102
+ regularization = self._get_regularization(self.regularization, adjacency)
103
+ self.regularized = regularization > 0
104
+
105
+ # multiplier
106
+ if self.random_walk:
107
+ multiplier = Normalizer(adjacency, regularization)
108
+ else:
109
+ multiplier = Regularizer(adjacency, regularization)
110
+
111
+ # random matrix
112
+ random_generator = check_random_state(self.random_state)
113
+ random_matrix = random_generator.normal(size=(n, self.n_components))
114
+ random_matrix, _ = np.linalg.qr(random_matrix)
115
+
116
+ # random projection
117
+ factor = random_matrix
118
+ embedding = factor.copy()
119
+ for t in range(self.n_iter):
120
+ factor = self.alpha * multiplier.dot(factor)
121
+ embedding += factor
122
+
123
+ # normalization
124
+ if self.normalized:
125
+ embedding = normalize(embedding, p=2)
126
+
127
+ # output
128
+ self.embedding_ = embedding
129
+ if self.bipartite:
130
+ self._split_vars(input_matrix.shape)
131
+ return self
@@ -0,0 +1,137 @@
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\_ : np.ndarray, shape = (n_nodes, n_components)
43
+ Embedding of the nodes.
44
+ eigenvalues\_ : np.ndarray, shape = (n_components)
45
+ Eigenvalues.
46
+ eigenvectors\_ : np.ndarray, shape = (n_nodes, n_components)
47
+ Eigenvectors.
48
+
49
+ Example
50
+ -------
51
+ >>> from sknetwork.embedding import Spectral
52
+ >>> from sknetwork.data import karate_club
53
+ >>> spectral = Spectral(n_components=3)
54
+ >>> adjacency = karate_club()
55
+ >>> embedding = spectral.fit_transform(adjacency)
56
+ >>> embedding.shape
57
+ (34, 3)
58
+
59
+ References
60
+ ----------
61
+ Belkin, M. & Niyogi, P. (2003). Laplacian Eigenmaps for Dimensionality Reduction and Data Representation,
62
+ Neural computation.
63
+ """
64
+ def __init__(self, n_components: int = 2, decomposition: str = 'rw', regularization: float = -1,
65
+ normalized: bool = True):
66
+ super(Spectral, self).__init__()
67
+
68
+ self.embedding_ = None
69
+ self.n_components = n_components
70
+ self.decomposition = decomposition
71
+ self.regularization = regularization
72
+ self.normalized = normalized
73
+ self.bipartite = None
74
+ self.regularized = None
75
+ self.eigenvalues_ = None
76
+ self.eigenvectors_ = None
77
+
78
+ def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray], force_bipartite: bool = False) -> 'Spectral':
79
+ """Compute the graph embedding.
80
+
81
+ If the input matrix :math:`B` is not square (e.g., biadjacency matrix of a bipartite graph) or not symmetric
82
+ (e.g., adjacency matrix of a directed graph), use the adjacency matrix
83
+
84
+ :math:`A = \\begin{bmatrix} 0 & B \\\\ B^T & 0 \\end{bmatrix}`
85
+
86
+ and return the embedding for both rows and columns of the input matrix :math:`B`.
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
+
95
+ Returns
96
+ -------
97
+ self: :class:`Spectral`
98
+ """
99
+ # input
100
+ input_matrix = check_format(input_matrix)
101
+ adjacency, self.bipartite = get_adjacency(input_matrix, allow_directed=False, force_bipartite=force_bipartite)
102
+ n = adjacency.shape[0]
103
+
104
+ # regularization
105
+ regularization = self._get_regularization(self.regularization, adjacency)
106
+ self.regularized = regularization > 0
107
+
108
+ # laplacian
109
+ normalized_laplacian = self.decomposition == 'rw'
110
+ laplacian = Laplacian(adjacency, regularization, normalized_laplacian)
111
+
112
+ # spectral decomposition
113
+ n_components = check_n_components(self.n_components, n - 2) + 1
114
+ solver = LanczosEig(which='SM')
115
+ solver.fit(matrix=laplacian, n_components=n_components)
116
+ index = np.argsort(solver.eigenvalues_)[1:] # increasing order, skip first
117
+
118
+ eigenvalues = solver.eigenvalues_[index]
119
+ eigenvectors = solver.eigenvectors_[:, index]
120
+
121
+ if normalized_laplacian:
122
+ eigenvectors = laplacian.norm_diag.dot(eigenvectors)
123
+ eigenvalues = 1 - eigenvalues
124
+
125
+ # embedding
126
+ embedding = eigenvectors.copy()
127
+ if self.normalized:
128
+ embedding = normalize(embedding, p=2)
129
+
130
+ # output
131
+ self.embedding_ = embedding
132
+ self.eigenvalues_ = eigenvalues
133
+ self.eigenvectors_ = eigenvectors
134
+ if self.bipartite:
135
+ self._split_vars(input_matrix.shape)
136
+
137
+ return self