scikit-network 0.33.0__cp312-cp312-macosx_10_9_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.

Potentially problematic release.


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

Files changed (216) hide show
  1. scikit_network-0.33.0.dist-info/AUTHORS.rst +43 -0
  2. scikit_network-0.33.0.dist-info/LICENSE +34 -0
  3. scikit_network-0.33.0.dist-info/METADATA +517 -0
  4. scikit_network-0.33.0.dist-info/RECORD +216 -0
  5. scikit_network-0.33.0.dist-info/WHEEL +5 -0
  6. scikit_network-0.33.0.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.cpython-312-darwin.so +0 -0
  25. sknetwork/classification/vote.pyx +56 -0
  26. sknetwork/clustering/__init__.py +8 -0
  27. sknetwork/clustering/base.py +172 -0
  28. sknetwork/clustering/kcenters.py +253 -0
  29. sknetwork/clustering/leiden.py +242 -0
  30. sknetwork/clustering/leiden_core.cpython-312-darwin.so +0 -0
  31. sknetwork/clustering/leiden_core.pyx +124 -0
  32. sknetwork/clustering/louvain.py +286 -0
  33. sknetwork/clustering/louvain_core.cpython-312-darwin.so +0 -0
  34. sknetwork/clustering/louvain_core.pyx +124 -0
  35. sknetwork/clustering/metrics.py +91 -0
  36. sknetwork/clustering/postprocess.py +66 -0
  37. sknetwork/clustering/propagation_clustering.py +104 -0
  38. sknetwork/clustering/tests/__init__.py +1 -0
  39. sknetwork/clustering/tests/test_API.py +38 -0
  40. sknetwork/clustering/tests/test_kcenters.py +60 -0
  41. sknetwork/clustering/tests/test_leiden.py +34 -0
  42. sknetwork/clustering/tests/test_louvain.py +129 -0
  43. sknetwork/clustering/tests/test_metrics.py +50 -0
  44. sknetwork/clustering/tests/test_postprocess.py +39 -0
  45. sknetwork/data/__init__.py +6 -0
  46. sknetwork/data/base.py +33 -0
  47. sknetwork/data/load.py +406 -0
  48. sknetwork/data/models.py +459 -0
  49. sknetwork/data/parse.py +644 -0
  50. sknetwork/data/test_graphs.py +84 -0
  51. sknetwork/data/tests/__init__.py +1 -0
  52. sknetwork/data/tests/test_API.py +30 -0
  53. sknetwork/data/tests/test_base.py +14 -0
  54. sknetwork/data/tests/test_load.py +95 -0
  55. sknetwork/data/tests/test_models.py +52 -0
  56. sknetwork/data/tests/test_parse.py +250 -0
  57. sknetwork/data/tests/test_test_graphs.py +29 -0
  58. sknetwork/data/tests/test_toy_graphs.py +68 -0
  59. sknetwork/data/timeout.py +38 -0
  60. sknetwork/data/toy_graphs.py +611 -0
  61. sknetwork/embedding/__init__.py +8 -0
  62. sknetwork/embedding/base.py +94 -0
  63. sknetwork/embedding/force_atlas.py +198 -0
  64. sknetwork/embedding/louvain_embedding.py +148 -0
  65. sknetwork/embedding/random_projection.py +135 -0
  66. sknetwork/embedding/spectral.py +141 -0
  67. sknetwork/embedding/spring.py +198 -0
  68. sknetwork/embedding/svd.py +359 -0
  69. sknetwork/embedding/tests/__init__.py +1 -0
  70. sknetwork/embedding/tests/test_API.py +49 -0
  71. sknetwork/embedding/tests/test_force_atlas.py +35 -0
  72. sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
  73. sknetwork/embedding/tests/test_random_projection.py +28 -0
  74. sknetwork/embedding/tests/test_spectral.py +81 -0
  75. sknetwork/embedding/tests/test_spring.py +50 -0
  76. sknetwork/embedding/tests/test_svd.py +43 -0
  77. sknetwork/gnn/__init__.py +10 -0
  78. sknetwork/gnn/activation.py +117 -0
  79. sknetwork/gnn/base.py +181 -0
  80. sknetwork/gnn/base_activation.py +89 -0
  81. sknetwork/gnn/base_layer.py +109 -0
  82. sknetwork/gnn/gnn_classifier.py +305 -0
  83. sknetwork/gnn/layer.py +153 -0
  84. sknetwork/gnn/loss.py +180 -0
  85. sknetwork/gnn/neighbor_sampler.py +65 -0
  86. sknetwork/gnn/optimizer.py +164 -0
  87. sknetwork/gnn/tests/__init__.py +1 -0
  88. sknetwork/gnn/tests/test_activation.py +56 -0
  89. sknetwork/gnn/tests/test_base.py +75 -0
  90. sknetwork/gnn/tests/test_base_layer.py +37 -0
  91. sknetwork/gnn/tests/test_gnn_classifier.py +130 -0
  92. sknetwork/gnn/tests/test_layers.py +80 -0
  93. sknetwork/gnn/tests/test_loss.py +33 -0
  94. sknetwork/gnn/tests/test_neigh_sampler.py +23 -0
  95. sknetwork/gnn/tests/test_optimizer.py +43 -0
  96. sknetwork/gnn/tests/test_utils.py +41 -0
  97. sknetwork/gnn/utils.py +127 -0
  98. sknetwork/hierarchy/__init__.py +6 -0
  99. sknetwork/hierarchy/base.py +96 -0
  100. sknetwork/hierarchy/louvain_hierarchy.py +272 -0
  101. sknetwork/hierarchy/metrics.py +234 -0
  102. sknetwork/hierarchy/paris.cpython-312-darwin.so +0 -0
  103. sknetwork/hierarchy/paris.pyx +316 -0
  104. sknetwork/hierarchy/postprocess.py +350 -0
  105. sknetwork/hierarchy/tests/__init__.py +1 -0
  106. sknetwork/hierarchy/tests/test_API.py +24 -0
  107. sknetwork/hierarchy/tests/test_algos.py +34 -0
  108. sknetwork/hierarchy/tests/test_metrics.py +62 -0
  109. sknetwork/hierarchy/tests/test_postprocess.py +57 -0
  110. sknetwork/linalg/__init__.py +9 -0
  111. sknetwork/linalg/basics.py +37 -0
  112. sknetwork/linalg/diteration.cpython-312-darwin.so +0 -0
  113. sknetwork/linalg/diteration.pyx +47 -0
  114. sknetwork/linalg/eig_solver.py +93 -0
  115. sknetwork/linalg/laplacian.py +15 -0
  116. sknetwork/linalg/normalizer.py +86 -0
  117. sknetwork/linalg/operators.py +225 -0
  118. sknetwork/linalg/polynome.py +76 -0
  119. sknetwork/linalg/ppr_solver.py +170 -0
  120. sknetwork/linalg/push.cpython-312-darwin.so +0 -0
  121. sknetwork/linalg/push.pyx +71 -0
  122. sknetwork/linalg/sparse_lowrank.py +142 -0
  123. sknetwork/linalg/svd_solver.py +91 -0
  124. sknetwork/linalg/tests/__init__.py +1 -0
  125. sknetwork/linalg/tests/test_eig.py +44 -0
  126. sknetwork/linalg/tests/test_laplacian.py +18 -0
  127. sknetwork/linalg/tests/test_normalization.py +34 -0
  128. sknetwork/linalg/tests/test_operators.py +66 -0
  129. sknetwork/linalg/tests/test_polynome.py +38 -0
  130. sknetwork/linalg/tests/test_ppr.py +50 -0
  131. sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
  132. sknetwork/linalg/tests/test_svd.py +38 -0
  133. sknetwork/linkpred/__init__.py +2 -0
  134. sknetwork/linkpred/base.py +46 -0
  135. sknetwork/linkpred/nn.py +126 -0
  136. sknetwork/linkpred/tests/__init__.py +1 -0
  137. sknetwork/linkpred/tests/test_nn.py +27 -0
  138. sknetwork/log.py +19 -0
  139. sknetwork/path/__init__.py +5 -0
  140. sknetwork/path/dag.py +54 -0
  141. sknetwork/path/distances.py +98 -0
  142. sknetwork/path/search.py +31 -0
  143. sknetwork/path/shortest_path.py +61 -0
  144. sknetwork/path/tests/__init__.py +1 -0
  145. sknetwork/path/tests/test_dag.py +37 -0
  146. sknetwork/path/tests/test_distances.py +62 -0
  147. sknetwork/path/tests/test_search.py +40 -0
  148. sknetwork/path/tests/test_shortest_path.py +40 -0
  149. sknetwork/ranking/__init__.py +8 -0
  150. sknetwork/ranking/base.py +61 -0
  151. sknetwork/ranking/betweenness.cpython-312-darwin.so +0 -0
  152. sknetwork/ranking/betweenness.pyx +97 -0
  153. sknetwork/ranking/closeness.py +92 -0
  154. sknetwork/ranking/hits.py +94 -0
  155. sknetwork/ranking/katz.py +83 -0
  156. sknetwork/ranking/pagerank.py +110 -0
  157. sknetwork/ranking/postprocess.py +37 -0
  158. sknetwork/ranking/tests/__init__.py +1 -0
  159. sknetwork/ranking/tests/test_API.py +32 -0
  160. sknetwork/ranking/tests/test_betweenness.py +38 -0
  161. sknetwork/ranking/tests/test_closeness.py +30 -0
  162. sknetwork/ranking/tests/test_hits.py +20 -0
  163. sknetwork/ranking/tests/test_pagerank.py +62 -0
  164. sknetwork/ranking/tests/test_postprocess.py +26 -0
  165. sknetwork/regression/__init__.py +4 -0
  166. sknetwork/regression/base.py +61 -0
  167. sknetwork/regression/diffusion.py +210 -0
  168. sknetwork/regression/tests/__init__.py +1 -0
  169. sknetwork/regression/tests/test_API.py +32 -0
  170. sknetwork/regression/tests/test_diffusion.py +56 -0
  171. sknetwork/sknetwork.py +3 -0
  172. sknetwork/test_base.py +35 -0
  173. sknetwork/test_log.py +15 -0
  174. sknetwork/topology/__init__.py +8 -0
  175. sknetwork/topology/cliques.cpython-312-darwin.so +0 -0
  176. sknetwork/topology/cliques.pyx +149 -0
  177. sknetwork/topology/core.cpython-312-darwin.so +0 -0
  178. sknetwork/topology/core.pyx +90 -0
  179. sknetwork/topology/cycles.py +243 -0
  180. sknetwork/topology/minheap.cpython-312-darwin.so +0 -0
  181. sknetwork/topology/minheap.pxd +20 -0
  182. sknetwork/topology/minheap.pyx +109 -0
  183. sknetwork/topology/structure.py +194 -0
  184. sknetwork/topology/tests/__init__.py +1 -0
  185. sknetwork/topology/tests/test_cliques.py +28 -0
  186. sknetwork/topology/tests/test_core.py +19 -0
  187. sknetwork/topology/tests/test_cycles.py +65 -0
  188. sknetwork/topology/tests/test_structure.py +85 -0
  189. sknetwork/topology/tests/test_triangles.py +38 -0
  190. sknetwork/topology/tests/test_wl.py +72 -0
  191. sknetwork/topology/triangles.cpython-312-darwin.so +0 -0
  192. sknetwork/topology/triangles.pyx +151 -0
  193. sknetwork/topology/weisfeiler_lehman.py +133 -0
  194. sknetwork/topology/weisfeiler_lehman_core.cpython-312-darwin.so +0 -0
  195. sknetwork/topology/weisfeiler_lehman_core.pyx +114 -0
  196. sknetwork/utils/__init__.py +7 -0
  197. sknetwork/utils/check.py +355 -0
  198. sknetwork/utils/format.py +221 -0
  199. sknetwork/utils/membership.py +82 -0
  200. sknetwork/utils/neighbors.py +115 -0
  201. sknetwork/utils/tests/__init__.py +1 -0
  202. sknetwork/utils/tests/test_check.py +190 -0
  203. sknetwork/utils/tests/test_format.py +63 -0
  204. sknetwork/utils/tests/test_membership.py +24 -0
  205. sknetwork/utils/tests/test_neighbors.py +41 -0
  206. sknetwork/utils/tests/test_tfidf.py +18 -0
  207. sknetwork/utils/tests/test_values.py +66 -0
  208. sknetwork/utils/tfidf.py +37 -0
  209. sknetwork/utils/values.py +76 -0
  210. sknetwork/visualization/__init__.py +4 -0
  211. sknetwork/visualization/colors.py +34 -0
  212. sknetwork/visualization/dendrograms.py +277 -0
  213. sknetwork/visualization/graphs.py +1039 -0
  214. sknetwork/visualization/tests/__init__.py +1 -0
  215. sknetwork/visualization/tests/test_dendrograms.py +53 -0
  216. sknetwork/visualization/tests/test_graphs.py +176 -0
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created in April 2020
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ """
7
+
8
+ from typing import Union
9
+
10
+ import numpy as np
11
+ from scipy import sparse
12
+ from scipy.sparse.linalg import LinearOperator
13
+
14
+ from sknetwork.utils.check import check_format, check_square
15
+
16
+
17
+ class Polynome(LinearOperator):
18
+ """Polynome of a matrix as a linear operator
19
+
20
+ :math:`P(A) = \\alpha_k A^k + ... + \\alpha_1 A + \\alpha_0`.
21
+
22
+ Parameters
23
+ ----------
24
+ matrix :
25
+ Square matrix
26
+ coeffs : np.ndarray
27
+ Coefficients of the polynome by increasing order of power.
28
+
29
+ Examples
30
+ --------
31
+ >>> from scipy import sparse
32
+ >>> from sknetwork.linalg import Polynome
33
+ >>> matrix = sparse.eye(2, format='csr')
34
+ >>> polynome = Polynome(matrix, np.arange(3))
35
+ >>> x = np.ones(2)
36
+ >>> polynome.dot(x)
37
+ array([3., 3.])
38
+ >>> polynome.T.dot(x)
39
+ array([3., 3.])
40
+
41
+ Notes
42
+ -----
43
+ The polynome is evaluated using the `Ruffini-Horner method
44
+ <https://en.wikipedia.org/wiki/Horner%27s_method>`_.
45
+ """
46
+
47
+ def __init__(self, matrix: Union[sparse.csr_matrix, np.ndarray], coeffs: np.ndarray):
48
+ if coeffs.shape[0] == 0:
49
+ raise ValueError('A polynome requires at least one coefficient.')
50
+ if not isinstance(matrix, LinearOperator):
51
+ matrix = check_format(matrix)
52
+ check_square(matrix)
53
+ shape = matrix.shape
54
+ dtype = matrix.dtype
55
+ super(Polynome, self).__init__(dtype=dtype, shape=shape)
56
+
57
+ self.matrix = matrix
58
+ self.coeffs = coeffs
59
+
60
+ def __neg__(self):
61
+ return Polynome(self.matrix, -self.coeffs)
62
+
63
+ def __mul__(self, other):
64
+ return Polynome(self.matrix, other * self.coeffs)
65
+
66
+ def _matvec(self, matrix: np.ndarray):
67
+ """Right dot product with a dense matrix.
68
+ """
69
+ y = self.coeffs[-1] * matrix
70
+ for a in self.coeffs[::-1][1:]:
71
+ y = self.matrix.dot(y) + a * matrix
72
+ return y
73
+
74
+ def _transpose(self):
75
+ """Transposed operator."""
76
+ return Polynome(self.matrix.T.tocsr(), self.coeffs)
@@ -0,0 +1,170 @@
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 Union
8
+
9
+ import numpy as np
10
+ from scipy import sparse
11
+ from scipy.sparse.linalg import eigs, LinearOperator, bicgstab
12
+
13
+ from sknetwork.linalg.diteration import diffusion
14
+ from sknetwork.linalg.push import push_pagerank
15
+ from sknetwork.linalg.normalizer import normalize
16
+ from sknetwork.linalg.polynome import Polynome
17
+
18
+
19
+ class RandomSurferOperator(LinearOperator):
20
+ """Random surfer as a LinearOperator
21
+
22
+ Parameters
23
+ ----------
24
+ adjacency :
25
+ Adjacency matrix of the graph as a CSR or a LinearOperator.
26
+ damping_factor : float
27
+ Probability to continue the random walk.
28
+ seeds :
29
+ Probability vector for seeds.
30
+
31
+ Attributes
32
+ ----------
33
+ a : sparse.csr_matrix
34
+ Scaled transposed transition matrix.
35
+ b : np.ndarray
36
+ Scaled restart probability vector.
37
+ """
38
+
39
+ def __init__(self, adjacency: Union[sparse.csr_matrix, LinearOperator], seeds: np.ndarray, damping_factor):
40
+ super(RandomSurferOperator, self).__init__(shape=adjacency.shape, dtype=float)
41
+
42
+ n = adjacency.shape[0]
43
+ out_degrees = adjacency.dot(np.ones(n)).astype(bool)
44
+
45
+ if hasattr(adjacency, 'left_sparse_dot'):
46
+ self.a = damping_factor * normalize(adjacency).T
47
+ else:
48
+ self.a = (damping_factor * normalize(adjacency)).T.tocsr()
49
+ self.b = (np.ones(n) - damping_factor * out_degrees) * seeds
50
+
51
+ def _matvec(self, x: np.ndarray):
52
+ return self.a.dot(x) + self.b * x.sum()
53
+
54
+
55
+ def get_pagerank(adjacency: Union[sparse.csr_matrix, LinearOperator], seeds: np.ndarray, damping_factor: float,
56
+ n_iter: int, tol: float = 1e-6, solver: str = 'piteration') -> np.ndarray:
57
+ """Solve the Pagerank problem. Formally,
58
+
59
+ :math:`x = \\alpha Px + (1-\\alpha)y`,
60
+
61
+ where :math:`P = (D^{-1}A)^T` is the transition matrix and :math:`y` is the personalization probability vector.
62
+
63
+ Parameters
64
+ ----------
65
+ adjacency : sparse.csr_matrix
66
+ Adjacency matrix of the graph.
67
+ seeds : np.ndarray
68
+ Personalization array. Must be a valid probability vector.
69
+ damping_factor : float
70
+ Probability to continue the random walk.
71
+ n_iter : int
72
+ Number of iterations for some of the solvers such as ``'piteration'`` or ``'diteration'``.
73
+ tol : float
74
+ Tolerance for the convergence of some solvers such as ``'bicgstab'`` or ``'lanczos'`` or ``'push'``.
75
+ solver : :obj:`str`
76
+ Which solver to use: ``'piteration'``, ``'diteration'``, ``'bicgstab'``, ``'lanczos'``, ``̀'RH'``, ``'push'``.
77
+
78
+ Returns
79
+ -------
80
+ pagerank : np.ndarray
81
+ Probability vector.
82
+
83
+ Examples
84
+ --------
85
+ >>> from sknetwork.data import house
86
+ >>> adjacency = house()
87
+ >>> seeds = np.array([1, 0, 0, 0, 0])
88
+ >>> scores = get_pagerank(adjacency, seeds, damping_factor=0.85, n_iter=10)
89
+ >>> np.round(scores, 2)
90
+ array([0.29, 0.24, 0.12, 0.12, 0.24])
91
+
92
+
93
+ References
94
+ ----------
95
+ * Hong, D. (2012). `Optimized on-line computation of pagerank algorithm.
96
+ <https://arxiv.org/pdf/1202.6158.pdf>`_
97
+ arXiv preprint arXiv:1202.6158.
98
+ * Van der Vorst, H. A. (1992). `Bi-CGSTAB:
99
+ <https://en.wikipedia.org/wiki/Biconjugate_gradient_stabilized_method>`_
100
+ A fast and smoothly converging variant of Bi-CG for the solution of nonsymmetric linear systems.
101
+ SIAM Journal on scientific and Statistical Computing, 13(2), 631-644.
102
+ * Lanczos, C. (1950).
103
+ `An iteration method for the solution of the eigenvalue problem of linear differential and integral operators.
104
+ <http://www.cs.umd.edu/~oleary/lanczos1950.pdf>`_
105
+ Los Angeles, CA: United States Governm. Press Office.
106
+ * Whang, J. , Lenharth, A. , Dhillon, I. , & Pingali, K. . (2015).
107
+ `Scalable Data-Driven PageRank: Algorithms, System Issues, and Lessons Learned`. 9233, 438-450.
108
+ <https://www.cs.utexas.edu/users/inderjit/public_papers/scalable_pagerank_europar15.pdf>
109
+ """
110
+ n = adjacency.shape[0]
111
+
112
+ if solver == 'diteration':
113
+ if not isinstance(adjacency, sparse.csr_matrix):
114
+ raise ValueError('D-iteration is not compatible with linear operators.')
115
+ adjacency = normalize(adjacency, p=1)
116
+ indptr = adjacency.indptr.astype(np.int32)
117
+ indices = adjacency.indices.astype(np.int32)
118
+ data = adjacency.data.astype(np.float32)
119
+ damping_factor = np.float32(damping_factor)
120
+ n_iter = np.int32(n_iter)
121
+ tol = np.float32(tol)
122
+
123
+ scores = np.zeros(n, dtype=np.float32)
124
+ fluid = (1 - damping_factor) * seeds.astype(np.float32)
125
+ diffusion(indptr, indices, data, scores, fluid, damping_factor, n_iter, tol)
126
+
127
+ elif solver == 'push':
128
+ n = adjacency.shape[0]
129
+ damping_factor = np.float32(damping_factor)
130
+ tol = np.float32(tol)
131
+ degrees = adjacency.dot(np.ones(n)).astype(np.int32)
132
+ rev_adjacency = adjacency.transpose().tocsr()
133
+
134
+ indptr = adjacency.indptr.astype(np.int32)
135
+ indices = adjacency.indices.astype(np.int32)
136
+ rev_indptr = rev_adjacency.indptr.astype(np.int32)
137
+ rev_indices = rev_adjacency.indices.astype(np.int32)
138
+
139
+ scores = push_pagerank(n, degrees, indptr, indices,
140
+ rev_indptr, rev_indices,
141
+ seeds.astype(np.float32),
142
+ damping_factor, tol)
143
+
144
+ elif solver == 'RH':
145
+ coeffs = np.ones(n_iter + 1)
146
+ polynome = Polynome(damping_factor * normalize(adjacency, p=1).T.tocsr(), coeffs)
147
+ scores = polynome.dot(seeds)
148
+
149
+ else:
150
+ rso = RandomSurferOperator(adjacency, seeds, damping_factor)
151
+ v0 = rso.b
152
+ if solver == 'bicgstab':
153
+ scores, info = bicgstab(sparse.eye(n, format='csr') - rso.a, rso.b, atol=tol, x0=v0)
154
+ elif solver == 'lanczos':
155
+ # noinspection PyTypeChecker
156
+ _, scores = sparse.linalg.eigs(rso, k=1, tol=tol, v0=v0)
157
+ scores = abs(scores.flatten().real)
158
+ elif solver == 'piteration':
159
+ scores = v0
160
+ for i in range(n_iter):
161
+ scores_ = rso.dot(scores)
162
+ scores_ /= scores_.sum()
163
+ if np.linalg.norm(scores - scores_, ord=1) < tol:
164
+ break
165
+ else:
166
+ scores = scores_
167
+ else:
168
+ raise ValueError('Unknown solver.')
169
+
170
+ return scores / scores.sum()
@@ -0,0 +1,71 @@
1
+ # distutils: language = c++
2
+ # cython: language_level=3
3
+ """
4
+ Created on Mars 2021
5
+ @author: Wenzhuo Zhao <wenzhuo.zhao@etu.sorbonne-universite.fr>
6
+ """
7
+ from libcpp.queue cimport queue
8
+ from cython.parallel cimport prange
9
+ import numpy as np
10
+ cimport numpy as cnp
11
+ cimport cython
12
+
13
+
14
+ @cython.boundscheck(False)
15
+ @cython.wraparound(False)
16
+ def push_pagerank(int n, cnp.ndarray[cnp.int32_t, ndim=1] degrees,
17
+ int[:] indptr, int[:] indices,
18
+ int[:] rev_indptr, int[:] rev_indices,
19
+ cnp.ndarray[cnp.float32_t, ndim=1] seeds,
20
+ cnp.float32_t damping_factor, cnp.float32_t tol):
21
+ """Push-based PageRank"""
22
+ cdef cnp.ndarray[cnp.float32_t, ndim=1] residuals
23
+ cdef int vertex
24
+ cdef int neighbor
25
+ cdef int j1
26
+ cdef int j2
27
+ cdef int j
28
+ cdef int[:] indexes
29
+ cdef int index
30
+ cdef float probability
31
+ cdef queue[int] worklist
32
+ cdef cnp.ndarray[cnp.float32_t, ndim=1] scores
33
+ cdef cnp.float32_t tmp
34
+ cdef float norm
35
+
36
+ residuals = np.zeros(n, dtype=np.float32)
37
+ for vertex in prange(n, nogil=True):
38
+ j1 = rev_indptr[vertex]
39
+ j2 = rev_indptr[vertex + 1]
40
+ # iterate node's in-coming neighbors
41
+ for j in range(j1, j2):
42
+ neighbor = rev_indices[j]
43
+ residuals[vertex] += 1 / degrees[neighbor]
44
+ """add the probability of seeds"""
45
+ residuals[vertex] *= (1 - damping_factor) * \
46
+ damping_factor * (1 + seeds[vertex])
47
+
48
+ # node with high residual value will be processed first
49
+ indexes = np.argsort(-residuals).astype(np.int32)
50
+ for index in indexes:
51
+ worklist.push(index)
52
+ scores = np.full(n, (1 - damping_factor), dtype=np.float32)
53
+
54
+ while not worklist.empty():
55
+ vertex = worklist.front()
56
+ worklist.pop()
57
+ # scores[v]_new
58
+ scores[vertex] += residuals[vertex]
59
+ # iterate node's out-coming neighbors
60
+ j1 = indptr[vertex]
61
+ j2 = indptr[vertex + 1]
62
+ for j in prange(j1, j2, nogil=True):
63
+ neighbor = indices[j]
64
+ tmp = residuals[neighbor]
65
+ residuals[neighbor] += residuals[vertex] * \
66
+ (1 - damping_factor) / degrees[vertex]
67
+ if residuals[neighbor] > tol > tmp:
68
+ worklist.push(neighbor)
69
+ norm = np.linalg.norm(scores, 1)
70
+ scores /= norm
71
+ return scores
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Apr 19 2019
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ """
7
+ from typing import Union, Tuple
8
+
9
+ import numpy as np
10
+ from scipy import sparse
11
+ from scipy.sparse.linalg import LinearOperator
12
+
13
+
14
+ class SparseLR(LinearOperator):
15
+ """Class for matrices with "sparse + low rank" structure.
16
+ Example:
17
+
18
+ :math:`A + xy^T`
19
+
20
+ Parameters
21
+ ----------
22
+ sparse_mat: scipy.spmatrix
23
+ Sparse component. Is converted to csr format automatically.
24
+ low_rank_tuples: list
25
+ Single tuple of arrays of list of tuples, representing the low rank components [(x1, y1), (x2, y2),...].
26
+ Each low rank component is of the form :math:`xy^T`.
27
+
28
+ Examples
29
+ --------
30
+ >>> from scipy import sparse
31
+ >>> from sknetwork.linalg import SparseLR
32
+ >>> adjacency = sparse.eye(2, format='csr')
33
+ >>> slr = SparseLR(adjacency, (np.ones(2), np.ones(2)))
34
+ >>> x = np.ones(2)
35
+ >>> slr.dot(x)
36
+ array([3., 3.])
37
+ >>> slr.sum(axis=0)
38
+ array([3., 3.])
39
+ >>> slr.sum(axis=1)
40
+ array([3., 3.])
41
+ >>> slr.sum()
42
+ 6.0
43
+
44
+ References
45
+ ----------
46
+ De Lara (2019). `The Sparse + Low Rank trick for Matrix Factorization-Based Graph Algorithms.
47
+ <http://www.mlgworkshop.org/2019/papers/MLG2019_paper_1.pdf>`_
48
+ Proceedings of the 15th International Workshop on Mining and Learning with Graphs (MLG).
49
+ """
50
+ def __init__(self, sparse_mat: Union[sparse.csr_matrix, sparse.csc_matrix], low_rank_tuples: Union[list, Tuple],
51
+ dtype=float):
52
+ n_row, n_col = sparse_mat.shape
53
+ self.sparse_mat = sparse_mat.tocsr().astype(dtype)
54
+ super(SparseLR, self).__init__(dtype=dtype, shape=(n_row, n_col))
55
+
56
+ if isinstance(low_rank_tuples, Tuple):
57
+ low_rank_tuples = [low_rank_tuples]
58
+ self.low_rank_tuples = []
59
+ for x, y in low_rank_tuples:
60
+ if x.shape == (n_row,) and y.shape == (n_col,):
61
+ self.low_rank_tuples.append((x.astype(self.dtype), y.astype(self.dtype)))
62
+ else:
63
+ raise ValueError('For each low rank tuple, x (resp. y) should be a vector of length {} (resp. {})'
64
+ .format(n_row, n_col))
65
+
66
+ def __neg__(self):
67
+ return SparseLR(-self.sparse_mat, [(-x, y) for (x, y) in self.low_rank_tuples])
68
+
69
+ def __add__(self, other: 'SparseLR'):
70
+ if type(other) == sparse.csr_matrix:
71
+ return SparseLR(self.sparse_mat + other, self.low_rank_tuples)
72
+ else:
73
+ return SparseLR(self.sparse_mat + other.sparse_mat, self.low_rank_tuples + other.low_rank_tuples)
74
+
75
+ def __sub__(self, other):
76
+ return self.__add__(-other)
77
+
78
+ def __mul__(self, other):
79
+ return SparseLR(other * self.sparse_mat, [(other * x, y) for (x, y) in self.low_rank_tuples])
80
+
81
+ def _matvec(self, matrix: np.ndarray):
82
+ """Right dot product with a dense matrix.
83
+
84
+ Parameters
85
+ ----------
86
+ matrix:
87
+ Matrix.
88
+
89
+ Returns
90
+ -------
91
+ Dot product as a dense array
92
+ """
93
+ prod = self.sparse_mat.dot(matrix)
94
+ if len(matrix.shape) == 1:
95
+ for (x, y) in self.low_rank_tuples:
96
+ prod += x * matrix.dot(y)
97
+ else:
98
+ transposed = matrix.T
99
+ for (x, y) in self.low_rank_tuples:
100
+ prod += x[:, np.newaxis].dot(transposed.dot(y)[:, np.newaxis].T)
101
+ return prod
102
+
103
+ def _transpose(self):
104
+ """Transposed operator."""
105
+ transposed_sparse = sparse.csr_matrix(self.sparse_mat.T)
106
+ transposed_tuples = [(y, x) for (x, y) in self.low_rank_tuples]
107
+ return SparseLR(transposed_sparse, transposed_tuples)
108
+
109
+ def _adjoint(self):
110
+ return self.transpose()
111
+
112
+ def left_sparse_dot(self, matrix: sparse.csr_matrix):
113
+ """Left dot product with a sparse matrix."""
114
+ return SparseLR(matrix.dot(self.sparse_mat), [(matrix.dot(x), y) for (x, y) in self.low_rank_tuples])
115
+
116
+ def right_sparse_dot(self, matrix: sparse.csr_matrix):
117
+ """Right dot product with a sparse matrix."""
118
+ return SparseLR(self.sparse_mat.dot(matrix), [(x, matrix.T.dot(y)) for (x, y) in self.low_rank_tuples])
119
+
120
+ def sum(self, axis=None):
121
+ """Row-wise, column-wise or total sum of operator's coefficients.
122
+
123
+ Parameters
124
+ ----------
125
+ axis :
126
+ If 0, return column-wise sum. If 1, return row-wise sum. Otherwise, return total sum.
127
+ """
128
+ if axis == 0:
129
+ s = self.T.dot(np.ones(self.shape[0]))
130
+ elif axis == 1:
131
+ s = self.dot(np.ones(self.shape[1]))
132
+ else:
133
+ s = self.dot(np.ones(self.shape[1])).sum()
134
+ return s
135
+
136
+ def astype(self, dtype: Union[str, np.dtype]):
137
+ """Change dtype of the object."""
138
+ self.sparse_mat = self.sparse_mat.astype(dtype)
139
+ self.low_rank_tuples = [(x.astype(dtype), y.astype(dtype)) for (x, y) in self.low_rank_tuples]
140
+ self.dtype = np.dtype(dtype)
141
+
142
+ return self
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Created on July 10 2019
5
+
6
+ Authors:
7
+ Nathan De Lara <nathan.delara@telecom-paris.fr>
8
+ """
9
+ from abc import ABC
10
+ from typing import Union
11
+
12
+ import numpy as np
13
+ from scipy import sparse
14
+ from scipy.sparse.linalg import svds
15
+
16
+ from sknetwork.base import Algorithm
17
+
18
+
19
+ class SVDSolver(Algorithm, ABC):
20
+ """Generic class for SVD-solvers.
21
+
22
+ Attributes
23
+ ----------
24
+ singular_vectors_left_: np.ndarray
25
+ Two-dimensional array, each column is a left singular vector of the input.
26
+ singular_vectors_right_: np.ndarray
27
+ Two-dimensional array, each column is a right singular vector of the input.
28
+ singular_values_: np.ndarray
29
+ Singular values.
30
+ """
31
+ def __init__(self):
32
+ self.singular_vectors_left_ = None
33
+ self.singular_vectors_right_ = None
34
+ self.singular_values_ = None
35
+
36
+
37
+ class LanczosSVD(SVDSolver):
38
+ """SVD solver using Lanczos method on :math:`AA^T` or :math:`A^TA`.
39
+
40
+ Parameters
41
+ ----------
42
+ n_iter : int
43
+ Maximum number of Arnoldi update iterations allowed.
44
+ Default = 10 * nb or rows or columns.
45
+ tol : float
46
+ Relative accuracy for eigenvalues (stopping criterion).
47
+ Default = 0 (machine precision).
48
+
49
+ Attributes
50
+ ----------
51
+ singular_vectors_left_: np.ndarray
52
+ Two-dimensional array, each column is a left singular vector of the input.
53
+ singular_vectors_right_: np.ndarray
54
+ Two-dimensional array, each column is a right singular vector of the input.
55
+ singular_values_: np.ndarray
56
+ Singular values.
57
+
58
+ See Also
59
+ --------
60
+ scipy.sparse.linalg.svds
61
+ """
62
+ def __init__(self, n_iter: int = None, tol: float = 0.):
63
+ super(LanczosSVD, self).__init__()
64
+ self.n_iter = n_iter
65
+ self.tol = tol
66
+
67
+ def fit(self, matrix: Union[sparse.csr_matrix, sparse.linalg.LinearOperator], n_components: int,
68
+ init_vector: np.ndarray = None):
69
+ """Perform singular value decomposition on input matrix.
70
+
71
+ Parameters
72
+ ----------
73
+ matrix :
74
+ Matrix to decompose.
75
+ n_components : int
76
+ Number of singular values to compute
77
+ init_vector : np.ndarray
78
+ Starting vector for iteration.
79
+ Default = random.
80
+ Returns
81
+ -------
82
+ self: :class:`SVDSolver`
83
+ """
84
+ u, s, vt = svds(matrix.astype(float), n_components, v0=init_vector)
85
+ # order the singular values by decreasing order
86
+ index = np.argsort(-s)
87
+ self.singular_vectors_left_ = u[:, index]
88
+ self.singular_vectors_right_ = vt.T[:, index]
89
+ self.singular_values_ = s[index]
90
+
91
+ return self
@@ -0,0 +1 @@
1
+ """Tests for linalg"""
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tests for eigenvalue solver."""
4
+
5
+ import unittest
6
+
7
+ import numpy as np
8
+
9
+ from sknetwork.data import miserables, karate_club
10
+ from sknetwork.linalg import LanczosEig, SparseLR
11
+
12
+
13
+ def eigenvector_err(matrix, eigenvectors, eigenvalues):
14
+ """Approximation error for eigenvectors."""
15
+ err = matrix.dot(eigenvectors) - eigenvectors * eigenvalues
16
+ return np.linalg.norm(err)
17
+
18
+
19
+ # noinspection DuplicatedCode
20
+ class TestSolvers(unittest.TestCase):
21
+
22
+ def setUp(self):
23
+ """Load les Miserables and regularized version"""
24
+ self.adjacency = miserables()
25
+ self.random_state = np.random.RandomState(123)
26
+ n = self.adjacency.shape[0]
27
+ x = np.random.random(n)
28
+ self.slr = SparseLR(self.adjacency, [(x, x)])
29
+
30
+ def test_lanczos(self):
31
+ solver = LanczosEig('LM')
32
+ solver.fit(self.adjacency, 2)
33
+ self.assertEqual(len(solver.eigenvalues_), 2)
34
+ self.assertAlmostEqual(eigenvector_err(self.adjacency, solver.eigenvectors_, solver.eigenvalues_), 0)
35
+
36
+ solver.fit(self.slr, 2)
37
+ self.assertEqual(len(solver.eigenvalues_), 2)
38
+ self.assertAlmostEqual(eigenvector_err(self.slr, solver.eigenvectors_, solver.eigenvalues_), 0)
39
+
40
+ adjacency = karate_club()
41
+ solver = LanczosEig('SM')
42
+ solver.fit(adjacency, 2)
43
+ self.assertEqual(len(solver.eigenvalues_), 2)
44
+ self.assertAlmostEqual(eigenvector_err(adjacency, solver.eigenvectors_, solver.eigenvalues_), 0)
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tests for laplacian."""
4
+
5
+ import unittest
6
+
7
+ import numpy as np
8
+
9
+ from sknetwork.data.test_graphs import test_graph
10
+ from sknetwork.linalg import get_laplacian
11
+
12
+
13
+ class TestLaplacian(unittest.TestCase):
14
+
15
+ def test(self):
16
+ adjacency = test_graph()
17
+ laplacian = get_laplacian(adjacency)
18
+ self.assertEqual(np.linalg.norm(laplacian.dot(np.ones(adjacency.shape[0]))), 0)
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created in April 2020
5
+ @author: Nathan de Lara <nathan.delara@polytechnique.org>
6
+ """
7
+
8
+ import unittest
9
+
10
+ import numpy as np
11
+ from scipy import sparse
12
+
13
+ from sknetwork.linalg import normalize
14
+
15
+
16
+ class TestNormalization(unittest.TestCase):
17
+
18
+ def test_formats(self):
19
+ n = 5
20
+ mat1 = normalize(np.eye(n))
21
+ mat2 = normalize(sparse.eye(n))
22
+
23
+ x = np.random.randn(n)
24
+ self.assertAlmostEqual(np.linalg.norm(mat1.dot(x) - x), 0)
25
+ self.assertAlmostEqual(np.linalg.norm(mat2.dot(x) - x), 0)
26
+
27
+ mat1 = np.random.rand(n**2).reshape((n, n))
28
+ mat2 = sparse.csr_matrix(mat1)
29
+ mat1 = normalize(mat1, p=2)
30
+ mat2 = normalize(mat2, p=2)
31
+ self.assertAlmostEqual(np.linalg.norm(mat1.dot(x) - mat2.dot(x)), 0)
32
+
33
+ with self.assertRaises(ValueError):
34
+ normalize(mat1, p=3)