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,56 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for diffusion.py"""
4
+
5
+ import unittest
6
+
7
+ from sknetwork.data.test_graphs import *
8
+ from sknetwork.regression import Diffusion, Dirichlet
9
+
10
+
11
+ # noinspection DuplicatedCode
12
+ class TestDiffusion(unittest.TestCase):
13
+
14
+ def setUp(self):
15
+ self.algos = [Diffusion(), Dirichlet()]
16
+
17
+ def test_predict(self):
18
+ adjacency = test_graph()
19
+ for algo in self.algos:
20
+ values = algo.fit_predict(adjacency, {0: 0, 1: 1, 2: 0.5})
21
+ values_ = algo.predict()
22
+ self.assertAlmostEqual(np.linalg.norm(values - values_), 0)
23
+
24
+ def test_no_iter(self):
25
+ with self.assertRaises(ValueError):
26
+ Diffusion(n_iter=-1)
27
+
28
+ def test_single_node_graph(self):
29
+ for algo in self.algos:
30
+ algo.fit(sparse.identity(1, format='csr'), {0: 1})
31
+ self.assertEqual(algo.values_, [1])
32
+
33
+ def test_range(self):
34
+ for adjacency in [test_graph(), test_digraph()]:
35
+ for algo in self.algos:
36
+ values = algo.fit_predict(adjacency, {0: 0, 1: 1, 2: 0.5})
37
+ self.assertTrue(np.all(values <= 1) and np.all(values >= 0))
38
+
39
+ biadjacency = test_bigraph()
40
+ for algo in [Diffusion(), Dirichlet()]:
41
+ values = algo.fit_predict(biadjacency, values_row={0: 1})
42
+ self.assertTrue(np.all(values <= 1) and np.all(values >= 0))
43
+ values = algo.fit_predict(biadjacency, values_row={0: 0.1}, values_col={1: 2}, init=0.3)
44
+ self.assertTrue(np.all(values <= 2) and np.all(values >= 0.1))
45
+ self.assertAlmostEqual(np.linalg.norm(algo.values_col_ - algo.predict(columns=True)), 0)
46
+
47
+ def test_initial_state(self):
48
+ for adjacency in [test_graph(), test_digraph()]:
49
+ for algo in self.algos:
50
+ values = algo.fit_predict(adjacency, {0: 0, 1: 1, 2: 0.5}, 0.3)
51
+ self.assertTrue(np.all(values <= 1) and np.all(values >= 0))
52
+
53
+ def test_n_iter(self):
54
+ with self.assertRaises(ValueError):
55
+ Dirichlet(n_iter=0)
56
+
sknetwork/sknetwork.py ADDED
@@ -0,0 +1,3 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """Main module."""
sknetwork/test_base.py ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for base.py"""
4
+ import unittest
5
+
6
+ from sknetwork.base import Algorithm
7
+
8
+
9
+ class TestBase(unittest.TestCase):
10
+
11
+ def setUp(self):
12
+ class NewAlgo(Algorithm):
13
+ """Docstring"""
14
+ def __init__(self, param: int, name: str):
15
+ self.param = param
16
+ self.name = name
17
+
18
+ def fit(self):
19
+ """Docstring"""
20
+ pass
21
+ self.algo = NewAlgo(1, 'abc')
22
+
23
+ def test_repr(self):
24
+ self.assertEqual(repr(self.algo), "NewAlgo(param=1, name='abc')")
25
+
26
+ def test_get_params(self):
27
+ self.assertEqual(len(self.algo.get_params()), 2)
28
+
29
+ def test_set_params(self):
30
+ self.algo.set_params({'param': 3})
31
+ self.assertEqual(self.algo.param, 3)
32
+
33
+ def test_fit(self):
34
+ stub = Algorithm()
35
+ self.assertRaises(NotImplementedError, stub.fit, None)
sknetwork/test_log.py ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """tests for verbose.py"""
4
+
5
+ import unittest
6
+
7
+ from sknetwork.log import Log
8
+
9
+
10
+ class TestVerbose(unittest.TestCase):
11
+
12
+ def test_prints(self):
13
+ logger = Log(verbose=True)
14
+ logger.print_log('Hello', 42)
15
+ self.assertEqual(str(logger.log), 'Hello 42\n')
@@ -0,0 +1,8 @@
1
+ """Module on topology."""
2
+ from sknetwork.topology.cliques import count_cliques
3
+ from sknetwork.topology.core import get_core_decomposition
4
+ from sknetwork.topology.triangles import count_triangles, get_clustering_coefficient
5
+ from sknetwork.topology.structure import is_connected, is_bipartite, is_symmetric, get_connected_components, \
6
+ get_largest_connected_component
7
+ from sknetwork.topology.cycles import is_acyclic, get_cycles, break_cycles
8
+ from sknetwork.topology.weisfeiler_lehman import color_weisfeiler_lehman, are_isomorphic
@@ -0,0 +1,149 @@
1
+ # distutils: language = c++
2
+ # cython: language_level=3
3
+ """
4
+ Created in June 2020
5
+ @author: Julien Simonnet <julien.simonnet@etu.upmc.fr>
6
+ @author: Yohann Robert <yohann.robert@etu.upmc.fr>
7
+ """
8
+ from libcpp.vector cimport vector
9
+ import numpy as np
10
+ cimport numpy as np
11
+ from scipy import sparse
12
+
13
+ cimport cython
14
+
15
+ from sknetwork.path.dag import get_dag
16
+ from sknetwork.topology.core import get_core_decomposition
17
+
18
+
19
+ @cython.boundscheck(False)
20
+ @cython.wraparound(False)
21
+ cdef class ListingBox:
22
+ cdef int[:] ns
23
+ cdef np.ndarray degrees
24
+ cdef np.ndarray subs
25
+ cdef short[:] lab
26
+
27
+ def __cinit__(self, vector[int] indptr, int k):
28
+ cdef int n = indptr.size() - 1
29
+ cdef int i
30
+ cdef int max_deg = 0
31
+
32
+ cdef np.ndarray[int, ndim=1] ns = np.empty((k+1,), dtype=np.int32)
33
+ ns[k] = n
34
+ self.ns = ns
35
+
36
+ cdef np.ndarray[short, ndim=1] lab = np.full((n,), k, dtype=np.int16)
37
+ self.lab = lab
38
+
39
+ cdef np.ndarray[int, ndim=1] deg = np.zeros(n, dtype=np.int32)
40
+ cdef np.ndarray[int, ndim=1] sub = np.zeros(n, dtype=np.int32)
41
+
42
+ for i in range(n):
43
+ deg[i] = indptr[i+1] - indptr[i]
44
+ max_deg = max(deg[i], max_deg)
45
+ sub[i] = i
46
+
47
+ self.degrees = np.empty((k+1,), dtype=object)
48
+ self.subs = np.empty((k+1,), dtype=object)
49
+
50
+ self.degrees[k] = deg
51
+ self.subs[k] = sub
52
+
53
+ for i in range(2, k):
54
+ deg = np.zeros(n, dtype=np.int32)
55
+ sub = np.zeros(max_deg, dtype=np.int32)
56
+ self.degrees[i] = deg
57
+ self.subs[i] = sub
58
+
59
+
60
+ @cython.boundscheck(False)
61
+ @cython.wraparound(False)
62
+ cdef long count_cliques_from_dag(vector[int] indptr, vector[int] indices, int clique_size, ListingBox box):
63
+ cdef int n = indptr.size() - 1
64
+ cdef long n_cliques = 0
65
+ cdef int i, j, k, k_max
66
+ cdef int u, v, w
67
+
68
+ if clique_size == 2:
69
+ degree_ = box.degrees[2]
70
+ sub_ = box.subs[2]
71
+ for i in range(box.ns[2]):
72
+ j = sub_[i]
73
+ n_cliques += degree_[j]
74
+ return n_cliques
75
+
76
+ sub_ = box.subs[clique_size]
77
+ sub_prev = box.subs[clique_size - 1]
78
+ degree_ = box.degrees[clique_size]
79
+ deg_prev = box.degrees[clique_size - 1]
80
+ for i in range(box.ns[clique_size]):
81
+ u = sub_[i]
82
+ box.ns[clique_size - 1] = 0
83
+ for j in range(indptr[u], indptr[u] + degree_[u]):
84
+ v = indices[j]
85
+ if box.lab[v] == clique_size:
86
+ box.lab[v] = clique_size - 1
87
+ sub_prev[box.ns[clique_size - 1]] = v
88
+ box.ns[clique_size - 1] += 1
89
+ deg_prev[v] = 0
90
+ for j in range(box.ns[clique_size - 1]):
91
+ v = sub_prev[j]
92
+ k = indptr[v]
93
+ k_max = indptr[v] + degree_[v]
94
+ while k < k_max:
95
+ w = indices[k]
96
+ if box.lab[w] == clique_size - 1:
97
+ deg_prev[v] += 1
98
+ else:
99
+ k_max -= 1
100
+ indices[k] = indices[k_max]
101
+ k -= 1
102
+ indices[k_max] = w
103
+ k += 1
104
+ n_cliques += count_cliques_from_dag(indptr, indices, clique_size - 1, box)
105
+ for j in range(box.ns[clique_size - 1]):
106
+ v = sub_prev[j]
107
+ box.lab[v] = clique_size
108
+ return n_cliques
109
+
110
+
111
+ def count_cliques(adjacency: sparse.csr_matrix, clique_size: int = 3) -> int:
112
+ """Count the number of cliques of some size.
113
+
114
+ Parameters
115
+ ----------
116
+ adjacency :
117
+ Adjacency matrix of the graph.
118
+ clique_size : int
119
+ Clique size (default = 3, corresponding to triangles.
120
+
121
+ Returns
122
+ -------
123
+ n_cliques : int
124
+ Number of cliques.
125
+
126
+ Example
127
+ -------
128
+ >>> from sknetwork.data import karate_club
129
+ >>> adjacency = karate_club()
130
+ >>> count_cliques(adjacency, 3)
131
+ 45
132
+
133
+ References
134
+ ----------
135
+ Danisch, M., Balalau, O., & Sozio, M. (2018, April).
136
+ `Listing k-cliques in sparse real-world graphs.
137
+ <https://dl.acm.org/doi/pdf/10.1145/3178876.3186125>`_
138
+ In Proceedings of the 2018 World Wide Web Conference (pp. 589-598).
139
+ """
140
+ if clique_size < 2:
141
+ raise ValueError("The clique size must be at least 2.")
142
+
143
+ values = get_core_decomposition(adjacency)
144
+ dag = get_dag(adjacency, order=np.argsort(values))
145
+ indptr = dag.indptr
146
+ indices = dag.indices
147
+ box = ListingBox.__new__(ListingBox, indptr, clique_size)
148
+ n_cliques = count_cliques_from_dag(indptr, indices, clique_size, box)
149
+ return n_cliques
@@ -0,0 +1,90 @@
1
+ # distutils: language = c++
2
+ # cython: language_level=3
3
+ """
4
+ Created in June 2020
5
+ @author: Julien Simonnet <julien.simonnet@etu.upmc.fr>
6
+ @author: Yohann Robert <yohann.robert@etu.upmc.fr>
7
+ """
8
+ cimport cython
9
+
10
+ from typing import Union
11
+
12
+ import numpy as np
13
+ cimport numpy as np
14
+ from scipy import sparse
15
+
16
+ from sknetwork.utils.check import check_format
17
+ from sknetwork.topology.minheap cimport MinHeap
18
+
19
+
20
+ @cython.boundscheck(False)
21
+ @cython.wraparound(False)
22
+ cdef compute_core(int[:] indptr, int[:] indices):
23
+ """Compute the core value of each node.
24
+
25
+ Parameters
26
+ ----------
27
+ indptr :
28
+ CSR format index array of the adjacency matrix.
29
+ indices :
30
+ CSR format index pointer array of the adjacency matrix.
31
+
32
+ Returns
33
+ -------
34
+ labels :
35
+ Core value of each node.
36
+ """
37
+ cdef int n = indptr.shape[0] - 1
38
+ cdef int core_value = 0 # current/max core value of the graph
39
+ cdef int min_node # current node of minimum degree
40
+ cdef int i, j, k
41
+ cdef int[:] degrees = np.asarray(indptr)[1:] - np.asarray(indptr)[:n]
42
+ cdef np.ndarray[int, ndim=1] labels = np.empty((n,), dtype=np.int32)
43
+ cdef MinHeap mh = MinHeap.__new__(MinHeap, n) # minimum heap with an update system
44
+
45
+ # insert all nodes in the heap
46
+ for i in range(n):
47
+ mh.insert_key(i, degrees)
48
+
49
+ i = n - 1
50
+ while not mh.empty():
51
+ min_node = mh.pop_min(degrees)
52
+ core_value = max(core_value, degrees[min_node])
53
+
54
+ # decrease the degree of each neighbor of min_node
55
+ for k in range(indptr[min_node], indptr[min_node+1]):
56
+ j = indices[k]
57
+ degrees[j] -= 1
58
+ mh.decrease_key(j, degrees) # update the heap to take the new degree into account
59
+
60
+ labels[min_node] = core_value
61
+ i -= 1
62
+
63
+ return np.asarray(labels)
64
+
65
+
66
+ def get_core_decomposition(adjacency: Union[np.ndarray, sparse.csr_matrix]) -> np.ndarray:
67
+ """Get the k-core decomposition of a graph.
68
+
69
+ Parameters
70
+ ----------
71
+ adjacency :
72
+ Adjacency matrix of the graph.
73
+
74
+ Returns
75
+ -------
76
+ core_values :
77
+ Core value of each node.
78
+
79
+ Example
80
+ -------
81
+ >>> from sknetwork.data import karate_club
82
+ >>> adjacency = karate_club()
83
+ >>> core_values = get_core_decomposition(adjacency)
84
+ >>> len(core_values)
85
+ 34
86
+ """
87
+ adjacency = check_format(adjacency, allow_empty=True)
88
+ indptr = adjacency.indptr
89
+ indices = adjacency.indices
90
+ return compute_core(indptr, indices)
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created in February 2024
5
+ @author: Thomas Bonald <thomas.bonald@telecom-paris.fr>
6
+ @author: Yiwen Peng <yiwen.peng@telecom-paris.fr>
7
+ """
8
+ from typing import Tuple, Optional, Union, List
9
+
10
+ import numpy as np
11
+ from scipy import sparse
12
+
13
+ from sknetwork.utils.check import is_symmetric, check_format
14
+ from sknetwork.utils.format import get_adjacency
15
+ from sknetwork.path import get_distances
16
+
17
+
18
+ def is_acyclic(adjacency: sparse.csr_matrix, directed: Optional[bool] = None) -> bool:
19
+ """Check whether a graph has no cycle.
20
+
21
+ Parameters
22
+ ----------
23
+ adjacency:
24
+ Adjacency matrix of the graph.
25
+ directed:
26
+ Whether to consider the graph as directed (inferred if not specified).
27
+ Returns
28
+ -------
29
+ is_acyclic : bool
30
+ A boolean with value True if the graph has no cycle and False otherwise.
31
+
32
+ Example
33
+ -------
34
+ >>> from sknetwork.topology import is_acyclic
35
+ >>> from sknetwork.data import star, grid
36
+ >>> is_acyclic(star())
37
+ True
38
+ >>> is_acyclic(grid())
39
+ False
40
+ """
41
+ if directed is False:
42
+ # the graph must be undirected
43
+ if not is_symmetric(adjacency):
44
+ raise ValueError("The adjacency matrix is not symmetric. The parameter 'directed' must be True.")
45
+ elif directed is None:
46
+ # if not specified, infer from the graph
47
+ directed = not is_symmetric(adjacency)
48
+ has_loops = (adjacency.diagonal() > 0).any()
49
+ if has_loops:
50
+ return False
51
+ else:
52
+ n_cc = sparse.csgraph.connected_components(adjacency, directed, connection='strong', return_labels=False)
53
+ n_nodes = adjacency.shape[0]
54
+ if directed:
55
+ return n_cc == n_nodes
56
+ else:
57
+ n_edges = adjacency.nnz // 2
58
+ return n_cc == n_nodes - n_edges
59
+
60
+
61
+ def get_cycles(adjacency: sparse.csr_matrix, directed: Optional[bool] = None) -> List[List[int]]:
62
+ """Get all possible cycles of a graph.
63
+
64
+ Parameters
65
+ ----------
66
+ adjacency :
67
+ Adjacency matrix of the graph.
68
+ directed :
69
+ Whether to consider the graph as directed (inferred if not specified).
70
+
71
+ Returns
72
+ -------
73
+ cycles : list
74
+ List of cycles, each cycle represented as a list of nodes.
75
+
76
+ Example
77
+ -------
78
+ >>> from sknetwork.topology import get_cycles
79
+ >>> from sknetwork.data import cyclic_digraph
80
+ >>> graph = cyclic_digraph(4, metadata=True)
81
+ >>> get_cycles(graph.adjacency, directed=True)
82
+ [[0, 1, 2, 3]]
83
+ """
84
+
85
+ if directed is False:
86
+ # the graph must be undirected
87
+ if not is_symmetric(adjacency):
88
+ raise ValueError("The adjacency matrix is not symmetric. The parameter 'directed' must be True.")
89
+ elif directed is None:
90
+ # if not specified, infer from the graph
91
+ directed = not is_symmetric(adjacency)
92
+
93
+ cycles = []
94
+ n_nodes = adjacency.shape[0]
95
+ self_loops = np.argwhere(adjacency.diagonal() > 0).ravel()
96
+ if len(self_loops) > 0:
97
+ # add self-loops as cycles
98
+ for node in self_loops:
99
+ cycles.append([node])
100
+
101
+ # check if the graph is acyclic
102
+ n_cc, cc_labels = sparse.csgraph.connected_components(adjacency, directed, connection='strong', return_labels=True)
103
+ if directed and n_cc == n_nodes:
104
+ return cycles
105
+ elif not directed:
106
+ # acyclic undirected graph
107
+ n_edges = adjacency.nnz // 2
108
+ if n_cc == n_nodes - n_edges:
109
+ return cycles
110
+
111
+ # locate possible cycles
112
+ labels, counts = np.unique(cc_labels, return_counts=True)
113
+ if directed:
114
+ labels = labels[counts > 1]
115
+ cycle_starts = [np.argwhere(cc_labels == label).ravel()[0] for label in labels]
116
+
117
+ # find cycles for indicated nodes (depth-first traversal)
118
+ for start_node in cycle_starts:
119
+ stack = [(start_node, [start_node])]
120
+ while stack:
121
+ current_node, path = stack.pop()
122
+ for neighbor in adjacency.indices[adjacency.indptr[current_node]:adjacency.indptr[current_node+1]]:
123
+ if not directed and len(path) > 1 and neighbor == path[-2]:
124
+ # ignore the inverse edge (back move) in undirected graph
125
+ continue
126
+ if neighbor in path:
127
+ cycles.append(path[path.index(neighbor):])
128
+ else:
129
+ stack.append((neighbor, path + [neighbor]))
130
+
131
+ # remove duplicates
132
+ visited_cycles, unique_cycles = set(), []
133
+ for cycle in cycles:
134
+ candidate = np.roll(cycle, -cycle.index(min(cycle)))
135
+ current_cycle = tuple(candidate)
136
+ if not directed:
137
+ current_cycle = tuple(np.sort(candidate))
138
+ if current_cycle not in visited_cycles:
139
+ unique_cycles.append(list(candidate))
140
+ visited_cycles.add(current_cycle)
141
+
142
+ return unique_cycles
143
+
144
+
145
+ def break_cycles(adjacency: sparse.csr_matrix, root: Union[int, List[int]],
146
+ directed: Optional[bool] = None) -> sparse.csr_matrix:
147
+ """Break cycles of a graph from given roots.
148
+
149
+ Parameters
150
+ ----------
151
+ adjacency :
152
+ Adjacency matrix of the graph.
153
+ root :
154
+ The root node or list of root nodes to break cycles from.
155
+ directed :
156
+ Whether to consider the graph as directed (inferred if not specified).
157
+
158
+ Returns
159
+ -------
160
+ adjacency : sparse.csr_matrix
161
+ Adjacency matrix of the acyclic graph.
162
+
163
+ Example
164
+ -------
165
+ >>> from sknetwork.topology import break_cycles, is_acyclic
166
+ >>> from sknetwork.data import cyclic_digraph
167
+ >>> adjacency = cyclic_digraph(4)
168
+ >>> dag = break_cycles(adjacency, root=0, directed=True)
169
+ >>> is_acyclic(dag, directed=True)
170
+ True
171
+ """
172
+
173
+ if is_acyclic(adjacency, directed):
174
+ return adjacency
175
+
176
+ if root is None:
177
+ raise ValueError("The parameter root must be specified.")
178
+ else:
179
+ out_degree = len(adjacency[root].indices)
180
+ if out_degree == 0:
181
+ raise ValueError("Invalid root node. The root must have at least one outgoing edge.")
182
+ if isinstance(root, int):
183
+ root = [root]
184
+
185
+ if directed is False:
186
+ # the graph must be undirected
187
+ if not is_symmetric(adjacency):
188
+ raise ValueError("The adjacency matrix is not symmetric. The parameter 'directed' must be True.")
189
+ elif directed is None:
190
+ # if not specified, infer from the graph
191
+ directed = not is_symmetric(adjacency)
192
+
193
+ # break self-loops
194
+ adjacency = (adjacency - sparse.diags(adjacency.diagonal().astype(int), format='csr')).astype(int)
195
+
196
+ if directed:
197
+ # break cycles from the cycle node closest to the root
198
+ _, cc_labels = sparse.csgraph.connected_components(adjacency, directed, connection='strong', return_labels=True)
199
+ labels, counts = np.unique(cc_labels, return_counts=True)
200
+ cycle_labels = labels[counts > 1]
201
+ distances = get_distances(adjacency, source=root)
202
+
203
+ for label in cycle_labels:
204
+ cycle_nodes = np.argwhere(cc_labels == label).ravel()
205
+ roots_ix = np.argwhere(distances[cycle_nodes] == min(distances[cycle_nodes])).ravel()
206
+ subroots = set(cycle_nodes[roots_ix])
207
+ stack = [(subroot, [subroot]) for subroot in subroots]
208
+ # break cycles using depth-first traversal
209
+ while stack:
210
+ current_node, path = stack.pop()
211
+ # check if the edge still exists
212
+ if len(path) > 1 and adjacency[path[-2], path[-1]] <= 0:
213
+ continue
214
+ neighbors = adjacency.indices[adjacency.indptr[current_node]:adjacency.indptr[current_node+1]]
215
+ cycle_neighbors = set(neighbors) & set(cycle_nodes)
216
+ for neighbor in cycle_neighbors:
217
+ if neighbor in path:
218
+ adjacency[current_node, neighbor] = 0
219
+ adjacency.eliminate_zeros()
220
+ else:
221
+ stack.append((neighbor, path + [neighbor]))
222
+ else:
223
+ # break cycles from given roots for undirected graphs
224
+ for start_node in root:
225
+ stack = [(start_node, [start_node])]
226
+ # break cycles using depth-first traversal
227
+ while stack:
228
+ current_node, path = stack.pop()
229
+ # check if the edge still exists
230
+ if len(path) > 1 and adjacency[path[-2], path[-1]] <= 0:
231
+ continue
232
+ neighbors = list(adjacency.indices[adjacency.indptr[current_node]:adjacency.indptr[current_node+1]])
233
+ for neighbor in neighbors:
234
+ if len(path) > 1 and neighbor == path[-2]:
235
+ continue
236
+ if neighbor in path:
237
+ adjacency[current_node, neighbor] = 0
238
+ adjacency[neighbor, current_node] = 0
239
+ adjacency.eliminate_zeros()
240
+ else:
241
+ stack.append((neighbor, path + [neighbor]))
242
+
243
+ return adjacency
@@ -0,0 +1,20 @@
1
+ # distutils: language = c++
2
+ # cython: language_level=3
3
+ """
4
+ Created in June 2020
5
+ @author: Julien Simonnet <julien.simonnet@etu.upmc.fr>
6
+ @author: Yohann Robert <yohann.robert@etu.upmc.fr>
7
+ """
8
+ from libcpp.vector cimport vector
9
+
10
+ cdef class MinHeap:
11
+
12
+ cdef vector[int] val, pos
13
+ cdef int size
14
+
15
+ cdef int pop_min(self, int[:] scores)
16
+ cdef bint empty(self)
17
+ cdef void swap(self, int x, int y)
18
+ cdef void insert_key(self, int k, int[:] scores)
19
+ cdef void decrease_key(self, int i, int[:] scores)
20
+ cdef void min_heapify(self, int i, int[:] scores)