scikit-network 0.30.0__cp38-cp38-win_amd64.whl → 0.32.1__cp38-cp38-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of scikit-network might be problematic. Click here for more details.
- {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/AUTHORS.rst +3 -0
- {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/METADATA +31 -3
- scikit_network-0.32.1.dist-info/RECORD +228 -0
- {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/WHEEL +1 -1
- sknetwork/__init__.py +1 -1
- sknetwork/base.py +67 -0
- sknetwork/classification/base.py +24 -24
- sknetwork/classification/base_rank.py +17 -25
- sknetwork/classification/diffusion.py +35 -35
- sknetwork/classification/knn.py +24 -21
- sknetwork/classification/metrics.py +1 -1
- sknetwork/classification/pagerank.py +10 -10
- sknetwork/classification/propagation.py +23 -20
- sknetwork/classification/tests/test_diffusion.py +13 -3
- sknetwork/classification/vote.cp38-win_amd64.pyd +0 -0
- sknetwork/classification/vote.cpp +14482 -10351
- sknetwork/classification/vote.pyx +1 -3
- sknetwork/clustering/__init__.py +3 -1
- sknetwork/clustering/base.py +36 -40
- sknetwork/clustering/kcenters.py +253 -0
- sknetwork/clustering/leiden.py +241 -0
- sknetwork/clustering/leiden_core.cp38-win_amd64.pyd +0 -0
- sknetwork/clustering/leiden_core.cpp +31564 -0
- sknetwork/clustering/leiden_core.pyx +124 -0
- sknetwork/clustering/louvain.py +133 -102
- sknetwork/clustering/louvain_core.cp38-win_amd64.pyd +0 -0
- sknetwork/clustering/louvain_core.cpp +22457 -18792
- sknetwork/clustering/louvain_core.pyx +86 -96
- sknetwork/clustering/postprocess.py +2 -2
- sknetwork/clustering/propagation_clustering.py +15 -19
- sknetwork/clustering/tests/test_API.py +8 -4
- sknetwork/clustering/tests/test_kcenters.py +92 -0
- sknetwork/clustering/tests/test_leiden.py +34 -0
- sknetwork/clustering/tests/test_louvain.py +3 -4
- sknetwork/data/__init__.py +2 -1
- sknetwork/data/base.py +28 -0
- sknetwork/data/load.py +38 -37
- sknetwork/data/models.py +18 -18
- sknetwork/data/parse.py +54 -33
- sknetwork/data/test_graphs.py +2 -2
- sknetwork/data/tests/test_API.py +1 -1
- sknetwork/data/tests/test_base.py +14 -0
- sknetwork/data/tests/test_load.py +1 -1
- sknetwork/data/tests/test_parse.py +9 -12
- sknetwork/data/tests/test_test_graphs.py +1 -2
- sknetwork/data/toy_graphs.py +18 -18
- sknetwork/embedding/__init__.py +0 -1
- sknetwork/embedding/base.py +21 -20
- sknetwork/embedding/force_atlas.py +3 -2
- sknetwork/embedding/louvain_embedding.py +2 -2
- sknetwork/embedding/random_projection.py +5 -3
- sknetwork/embedding/spectral.py +0 -73
- sknetwork/embedding/tests/test_API.py +4 -28
- sknetwork/embedding/tests/test_louvain_embedding.py +4 -9
- sknetwork/embedding/tests/test_random_projection.py +2 -2
- sknetwork/embedding/tests/test_spectral.py +5 -8
- sknetwork/embedding/tests/test_svd.py +1 -1
- sknetwork/gnn/base.py +4 -4
- sknetwork/gnn/base_layer.py +3 -3
- sknetwork/gnn/gnn_classifier.py +45 -89
- sknetwork/gnn/layer.py +1 -1
- sknetwork/gnn/loss.py +1 -1
- sknetwork/gnn/optimizer.py +4 -3
- sknetwork/gnn/tests/test_base_layer.py +4 -4
- sknetwork/gnn/tests/test_gnn_classifier.py +12 -35
- sknetwork/gnn/utils.py +8 -8
- sknetwork/hierarchy/base.py +29 -2
- sknetwork/hierarchy/louvain_hierarchy.py +45 -41
- sknetwork/hierarchy/paris.cp38-win_amd64.pyd +0 -0
- sknetwork/hierarchy/paris.cpp +27371 -22844
- sknetwork/hierarchy/paris.pyx +7 -9
- sknetwork/hierarchy/postprocess.py +16 -16
- sknetwork/hierarchy/tests/test_API.py +1 -1
- sknetwork/hierarchy/tests/test_algos.py +5 -0
- sknetwork/hierarchy/tests/test_metrics.py +1 -1
- sknetwork/linalg/__init__.py +1 -1
- sknetwork/linalg/diteration.cp38-win_amd64.pyd +0 -0
- sknetwork/linalg/diteration.cpp +13474 -9454
- sknetwork/linalg/diteration.pyx +0 -2
- sknetwork/linalg/eig_solver.py +1 -1
- sknetwork/linalg/{normalization.py → normalizer.py} +18 -15
- sknetwork/linalg/operators.py +1 -1
- sknetwork/linalg/ppr_solver.py +1 -1
- sknetwork/linalg/push.cp38-win_amd64.pyd +0 -0
- sknetwork/linalg/push.cpp +23003 -18807
- sknetwork/linalg/push.pyx +0 -2
- sknetwork/linalg/svd_solver.py +1 -1
- sknetwork/linalg/tests/test_normalization.py +3 -7
- sknetwork/linalg/tests/test_operators.py +4 -8
- sknetwork/linalg/tests/test_ppr.py +1 -1
- sknetwork/linkpred/base.py +13 -2
- sknetwork/linkpred/nn.py +6 -6
- sknetwork/log.py +19 -0
- sknetwork/path/__init__.py +4 -3
- sknetwork/path/dag.py +54 -0
- sknetwork/path/distances.py +98 -0
- sknetwork/path/search.py +13 -47
- sknetwork/path/shortest_path.py +37 -162
- sknetwork/path/tests/test_dag.py +37 -0
- sknetwork/path/tests/test_distances.py +62 -0
- sknetwork/path/tests/test_search.py +26 -11
- sknetwork/path/tests/test_shortest_path.py +31 -36
- sknetwork/ranking/__init__.py +0 -1
- sknetwork/ranking/base.py +13 -8
- sknetwork/ranking/betweenness.cp38-win_amd64.pyd +0 -0
- sknetwork/ranking/betweenness.cpp +5709 -3017
- sknetwork/ranking/betweenness.pyx +0 -2
- sknetwork/ranking/closeness.py +7 -10
- sknetwork/ranking/pagerank.py +14 -14
- sknetwork/ranking/postprocess.py +12 -3
- sknetwork/ranking/tests/test_API.py +2 -4
- sknetwork/ranking/tests/test_betweenness.py +3 -3
- sknetwork/ranking/tests/test_closeness.py +3 -7
- sknetwork/ranking/tests/test_pagerank.py +11 -5
- sknetwork/ranking/tests/test_postprocess.py +5 -0
- sknetwork/regression/base.py +19 -2
- sknetwork/regression/diffusion.py +24 -10
- sknetwork/regression/tests/test_diffusion.py +8 -0
- sknetwork/test_base.py +35 -0
- sknetwork/test_log.py +15 -0
- sknetwork/topology/__init__.py +7 -8
- sknetwork/topology/cliques.cp38-win_amd64.pyd +0 -0
- sknetwork/topology/{kcliques.cpp → cliques.cpp} +23423 -20277
- sknetwork/topology/cliques.pyx +149 -0
- sknetwork/topology/core.cp38-win_amd64.pyd +0 -0
- sknetwork/topology/{kcore.cpp → core.cpp} +21637 -18762
- sknetwork/topology/core.pyx +90 -0
- sknetwork/topology/cycles.py +243 -0
- sknetwork/topology/minheap.cp38-win_amd64.pyd +0 -0
- sknetwork/{utils → topology}/minheap.cpp +19452 -15368
- sknetwork/{utils → topology}/minheap.pxd +1 -3
- sknetwork/{utils → topology}/minheap.pyx +1 -3
- sknetwork/topology/structure.py +3 -43
- sknetwork/topology/tests/test_cliques.py +11 -11
- sknetwork/topology/tests/test_core.py +19 -0
- sknetwork/topology/tests/test_cycles.py +65 -0
- sknetwork/topology/tests/test_structure.py +2 -16
- sknetwork/topology/tests/test_triangles.py +11 -15
- sknetwork/topology/tests/test_wl.py +72 -0
- sknetwork/topology/triangles.cp38-win_amd64.pyd +0 -0
- sknetwork/topology/triangles.cpp +5056 -2696
- sknetwork/topology/triangles.pyx +74 -89
- sknetwork/topology/weisfeiler_lehman.py +56 -86
- sknetwork/topology/weisfeiler_lehman_core.cp38-win_amd64.pyd +0 -0
- sknetwork/topology/weisfeiler_lehman_core.cpp +14727 -10622
- sknetwork/topology/weisfeiler_lehman_core.pyx +0 -2
- sknetwork/utils/__init__.py +1 -31
- sknetwork/utils/check.py +2 -2
- sknetwork/utils/format.py +5 -3
- sknetwork/utils/membership.py +2 -2
- sknetwork/utils/tests/test_check.py +3 -3
- sknetwork/utils/tests/test_format.py +3 -1
- sknetwork/utils/values.py +1 -1
- sknetwork/visualization/__init__.py +2 -2
- sknetwork/visualization/dendrograms.py +55 -7
- sknetwork/visualization/graphs.py +292 -72
- sknetwork/visualization/tests/test_dendrograms.py +9 -9
- sknetwork/visualization/tests/test_graphs.py +71 -62
- scikit_network-0.30.0.dist-info/RECORD +0 -227
- sknetwork/embedding/louvain_hierarchy.py +0 -142
- sknetwork/embedding/tests/test_louvain_hierarchy.py +0 -19
- sknetwork/path/metrics.py +0 -148
- sknetwork/path/tests/test_metrics.py +0 -29
- sknetwork/ranking/harmonic.py +0 -82
- sknetwork/topology/dag.py +0 -74
- sknetwork/topology/dag_core.cp38-win_amd64.pyd +0 -0
- sknetwork/topology/dag_core.cpp +0 -23350
- sknetwork/topology/dag_core.pyx +0 -38
- sknetwork/topology/kcliques.cp38-win_amd64.pyd +0 -0
- sknetwork/topology/kcliques.pyx +0 -193
- sknetwork/topology/kcore.cp38-win_amd64.pyd +0 -0
- sknetwork/topology/kcore.pyx +0 -120
- sknetwork/topology/tests/test_cores.py +0 -21
- sknetwork/topology/tests/test_dag.py +0 -26
- sknetwork/topology/tests/test_wl_coloring.py +0 -49
- sknetwork/topology/tests/test_wl_kernel.py +0 -31
- sknetwork/utils/base.py +0 -35
- sknetwork/utils/minheap.cp38-win_amd64.pyd +0 -0
- sknetwork/utils/simplex.py +0 -140
- sknetwork/utils/tests/test_base.py +0 -28
- sknetwork/utils/tests/test_bunch.py +0 -16
- sknetwork/utils/tests/test_projection_simplex.py +0 -33
- sknetwork/utils/tests/test_verbose.py +0 -15
- sknetwork/utils/verbose.py +0 -37
- {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/LICENSE +0 -0
- {scikit_network-0.30.0.dist-info → scikit_network-0.32.1.dist-info}/top_level.txt +0 -0
- /sknetwork/{utils → data}/timeout.py +0 -0
sknetwork/topology/triangles.pyx
CHANGED
|
@@ -1,54 +1,53 @@
|
|
|
1
|
-
# distutils: language
|
|
1
|
+
# distutils: language=c++
|
|
2
2
|
# cython: language_level=3
|
|
3
|
-
# cython: linetrace=True
|
|
4
|
-
# distutils: define_macros=CYTHON_TRACE_NOGIL=1
|
|
5
3
|
"""
|
|
6
|
-
Created
|
|
4
|
+
Created in June 2020
|
|
7
5
|
@author: Julien Simonnet <julien.simonnet@etu.upmc.fr>
|
|
8
6
|
@author: Yohann Robert <yohann.robert@etu.upmc.fr>
|
|
9
7
|
@author: Nathan de Lara <nathan.delara@polytechnique.org>
|
|
8
|
+
@author: Thomas Bonald <bonald@enst.fr>
|
|
10
9
|
"""
|
|
11
10
|
from libcpp.vector cimport vector
|
|
12
11
|
from scipy import sparse
|
|
13
|
-
from scipy.special import comb
|
|
14
12
|
from cython.parallel import prange
|
|
15
13
|
|
|
16
|
-
from sknetwork.
|
|
17
|
-
from sknetwork.utils.
|
|
14
|
+
from sknetwork.path.dag import get_dag
|
|
15
|
+
from sknetwork.utils.check import check_square
|
|
16
|
+
from sknetwork.utils.format import directed2undirected
|
|
17
|
+
from sknetwork.utils.neighbors import get_degrees
|
|
18
18
|
|
|
19
19
|
cimport cython
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@cython.boundscheck(False)
|
|
23
23
|
@cython.wraparound(False)
|
|
24
|
-
cdef long
|
|
25
|
-
"""
|
|
24
|
+
cdef long count_local_triangles_from_dag(int node, vector[int] indptr, vector[int] indices) nogil:
|
|
25
|
+
"""Count the number of triangles from a given node in a directed acyclic graph.
|
|
26
26
|
|
|
27
27
|
Parameters
|
|
28
28
|
----------
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
node :
|
|
30
|
+
Node.
|
|
31
31
|
indptr :
|
|
32
|
-
CSR format index pointer array of the
|
|
32
|
+
CSR format index pointer array of the adjacency matrix of the graph.
|
|
33
33
|
indices :
|
|
34
|
-
CSR format index array of the
|
|
34
|
+
CSR format index array of the adjacency matrix of the graph.
|
|
35
35
|
|
|
36
36
|
Returns
|
|
37
37
|
-------
|
|
38
38
|
n_triangles :
|
|
39
|
-
Number of
|
|
39
|
+
Number of triangles.
|
|
40
40
|
"""
|
|
41
41
|
cdef int i, j, k
|
|
42
|
-
cdef int
|
|
43
|
-
cdef long n_triangles = 0
|
|
42
|
+
cdef int neighbor
|
|
43
|
+
cdef long n_triangles = 0
|
|
44
44
|
|
|
45
|
-
for k in range(indptr[
|
|
46
|
-
|
|
47
|
-
i = indptr[
|
|
48
|
-
j = indptr[
|
|
45
|
+
for k in range(indptr[node], indptr[node + 1]):
|
|
46
|
+
neighbor = indices[k]
|
|
47
|
+
i = indptr[node]
|
|
48
|
+
j = indptr[neighbor]
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
while (i < indptr[source+1]) and (j < indptr[v+1]):
|
|
50
|
+
while (i < indptr[node + 1]) and (j < indptr[neighbor + 1]):
|
|
52
51
|
if indices[i] == indices[j]:
|
|
53
52
|
i += 1
|
|
54
53
|
j += 1
|
|
@@ -61,18 +60,17 @@ cdef long count_local_triangles(int source, vector[int] indptr, vector[int] indi
|
|
|
61
60
|
|
|
62
61
|
return n_triangles
|
|
63
62
|
|
|
64
|
-
|
|
65
63
|
@cython.boundscheck(False)
|
|
66
64
|
@cython.wraparound(False)
|
|
67
|
-
cdef long
|
|
68
|
-
"""
|
|
65
|
+
cdef long count_triangles_from_dag(vector[int] indptr, vector[int] indices, bint parallelize):
|
|
66
|
+
"""Count the number of triangles in a directed acyclic graph.
|
|
69
67
|
|
|
70
68
|
Parameters
|
|
71
69
|
----------
|
|
72
70
|
indptr :
|
|
73
|
-
CSR format index pointer array of the
|
|
71
|
+
CSR format index pointer array of the adjacency matrix of the graph.
|
|
74
72
|
indices :
|
|
75
|
-
CSR format index array of the
|
|
73
|
+
CSR format index array of the adjacency matrix of the graph.
|
|
76
74
|
parallelize :
|
|
77
75
|
If ``True``, use a parallel range to count triangles.
|
|
78
76
|
|
|
@@ -81,86 +79,73 @@ cdef long fit_core(vector[int] indptr, vector[int] indices, bint parallelize):
|
|
|
81
79
|
n_triangles :
|
|
82
80
|
Number of triangles in the graph
|
|
83
81
|
"""
|
|
84
|
-
cdef int
|
|
85
|
-
cdef int
|
|
82
|
+
cdef int n_nodes = indptr.size() - 1
|
|
83
|
+
cdef int node
|
|
86
84
|
cdef long n_triangles = 0
|
|
87
85
|
|
|
88
86
|
if parallelize:
|
|
89
|
-
for
|
|
90
|
-
n_triangles +=
|
|
87
|
+
for node in prange(n_nodes, nogil=True):
|
|
88
|
+
n_triangles += count_local_triangles_from_dag(node, indptr, indices)
|
|
91
89
|
else:
|
|
92
|
-
for
|
|
93
|
-
n_triangles +=
|
|
90
|
+
for node in range(n_nodes):
|
|
91
|
+
n_triangles += count_local_triangles_from_dag(node, indptr, indices)
|
|
94
92
|
|
|
95
93
|
return n_triangles
|
|
96
94
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"""Count the number of triangles in a graph, and evaluate the clustering coefficient.
|
|
100
|
-
|
|
101
|
-
* Graphs
|
|
95
|
+
def count_triangles(adjacency: sparse.csr_matrix, parallelize: bool = False) -> int:
|
|
96
|
+
"""Count the number of triangles in a graph. The graph is considered undirected.
|
|
102
97
|
|
|
103
98
|
Parameters
|
|
104
99
|
----------
|
|
100
|
+
adjacency :
|
|
101
|
+
Adjacency matrix of the graph.
|
|
105
102
|
parallelize :
|
|
106
103
|
If ``True``, use a parallel range while listing the triangles.
|
|
107
104
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
n_triangles : int
|
|
111
108
|
Number of triangles.
|
|
112
|
-
clustering_coef_ : float
|
|
113
|
-
Global clustering coefficient of the graph.
|
|
114
109
|
|
|
115
110
|
Example
|
|
116
111
|
-------
|
|
117
112
|
>>> from sknetwork.data import karate_club
|
|
118
|
-
>>> triangles = Triangles()
|
|
119
113
|
>>> adjacency = karate_club()
|
|
120
|
-
>>>
|
|
114
|
+
>>> count_triangles(adjacency)
|
|
121
115
|
45
|
|
122
116
|
"""
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
""" Fit algorithm to the data and return the number of triangles. Same parameters as the ``fit`` method.
|
|
159
|
-
|
|
160
|
-
Returns
|
|
161
|
-
-------
|
|
162
|
-
n_triangles_ : int
|
|
163
|
-
Number of triangles.
|
|
164
|
-
"""
|
|
165
|
-
self.fit(adjacency)
|
|
166
|
-
return self.n_triangles_
|
|
117
|
+
check_square(adjacency)
|
|
118
|
+
dag = get_dag(directed2undirected(adjacency))
|
|
119
|
+
indptr = dag.indptr
|
|
120
|
+
indices = dag.indices
|
|
121
|
+
n_triangles = count_triangles_from_dag(indptr, indices, parallelize)
|
|
122
|
+
return n_triangles
|
|
123
|
+
|
|
124
|
+
def get_clustering_coefficient(adjacency: sparse.csr_matrix, parallelize: bool = False) -> float:
|
|
125
|
+
"""Get the clustering coefficient of a graph.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
adjacency :
|
|
130
|
+
Adjacency matrix of the graph.
|
|
131
|
+
parallelize :
|
|
132
|
+
If ``True``, use a parallel range while listing the triangles.
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
coefficient : float
|
|
137
|
+
Clustering coefficient.
|
|
138
|
+
|
|
139
|
+
Example
|
|
140
|
+
-------
|
|
141
|
+
>>> from sknetwork.data import karate_club
|
|
142
|
+
>>> adjacency = karate_club()
|
|
143
|
+
>>> np.round(get_clustering_coefficient(adjacency), 2)
|
|
144
|
+
0.26
|
|
145
|
+
"""
|
|
146
|
+
n_triangles = count_triangles(adjacency, parallelize)
|
|
147
|
+
degrees = get_degrees(directed2undirected(adjacency))
|
|
148
|
+
degrees = degrees[degrees > 1]
|
|
149
|
+
n_edge_pairs = (degrees * (degrees - 1)).sum() / 2
|
|
150
|
+
coefficient = 3 * n_triangles / n_edge_pairs
|
|
151
|
+
return coefficient
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""
|
|
4
|
-
Created
|
|
4
|
+
Created in July 2020
|
|
5
5
|
@author: Pierre Pebereau <pierre.pebereau@telecom-paris.fr>
|
|
6
6
|
@author: Alexis Barreaux <alexis.barreaux@telecom-paris.fr>
|
|
7
7
|
"""
|
|
@@ -9,33 +9,33 @@ from typing import Union
|
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
from scipy import sparse
|
|
12
|
-
from sknetwork.topology.weisfeiler_lehman_core import weisfeiler_lehman_coloring
|
|
13
12
|
|
|
14
|
-
from sknetwork.
|
|
13
|
+
from sknetwork.topology.weisfeiler_lehman_core import weisfeiler_lehman_coloring
|
|
14
|
+
from sknetwork.utils.check import check_format, check_square
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
"""Weisfeiler-Lehman algorithm
|
|
17
|
+
def color_weisfeiler_lehman(adjacency: Union[sparse.csr_matrix, np.ndarray], max_iter: int = -1) -> np.ndarray:
|
|
18
|
+
"""Color nodes using Weisfeiler-Lehman algorithm.
|
|
19
19
|
|
|
20
20
|
Parameters
|
|
21
21
|
----------
|
|
22
|
+
adjacency : sparse.csr_matrix
|
|
23
|
+
Adjacency matrix of the graph
|
|
22
24
|
max_iter : int
|
|
23
|
-
Maximum number of iterations. Negative value
|
|
25
|
+
Maximum number of iterations. Negative value means no limit (until convergence).
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
labels : np.ndarray
|
|
28
30
|
Label of each node.
|
|
29
31
|
|
|
30
32
|
Example
|
|
31
33
|
-------
|
|
32
|
-
>>> from sknetwork.topology import WeisfeilerLehman
|
|
33
34
|
>>> from sknetwork.data import house
|
|
34
|
-
>>> weisfeiler_lehman = WeisfeilerLehman()
|
|
35
35
|
>>> adjacency = house()
|
|
36
|
-
>>> labels =
|
|
37
|
-
>>> labels
|
|
38
|
-
|
|
36
|
+
>>> labels = color_weisfeiler_lehman(adjacency)
|
|
37
|
+
>>> print(labels)
|
|
38
|
+
[0 2 1 1 2]
|
|
39
39
|
|
|
40
40
|
References
|
|
41
41
|
----------
|
|
@@ -45,57 +45,26 @@ class WeisfeilerLehman(Algorithm):
|
|
|
45
45
|
|
|
46
46
|
* Shervashidze, N., Schweitzer, P., van Leeuwen, E. J., Melhorn, K., Borgwardt, K. M. (2011)
|
|
47
47
|
`Weisfeiler-Lehman graph kernels.
|
|
48
|
-
<
|
|
48
|
+
<https://www.jmlr.org/papers/volume12/shervashidze11a/shervashidze11a.pdf>`_
|
|
49
49
|
Journal of Machine Learning Research 12, 2011.
|
|
50
50
|
"""
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
n: int = adjacency.shape[0]
|
|
69
|
-
if self.max_iter < 0 or self.max_iter > n:
|
|
70
|
-
max_iter = np.int32(n)
|
|
71
|
-
else:
|
|
72
|
-
max_iter = np.int32(self.max_iter)
|
|
73
|
-
|
|
74
|
-
labels = np.zeros(n, dtype=np.int32)
|
|
75
|
-
powers = (-np.pi / 3.15) ** np.arange(n, dtype=np.double)
|
|
76
|
-
indptr = adjacency.indptr.astype(np.int32)
|
|
77
|
-
indices = adjacency.indices.astype(np.int32)
|
|
78
|
-
|
|
79
|
-
labels, _ = weisfeiler_lehman_coloring(indptr, indices, labels, powers, max_iter)
|
|
80
|
-
self.labels_ = np.asarray(labels).astype(np.int32)
|
|
81
|
-
return self
|
|
82
|
-
|
|
83
|
-
def fit_transform(self, adjacency: Union[sparse.csr_matrix, np.ndarray]) -> np.ndarray:
|
|
84
|
-
"""Fit algorithm to the data and return the labels. Same parameters as the ``fit`` method.
|
|
85
|
-
|
|
86
|
-
Returns
|
|
87
|
-
-------
|
|
88
|
-
labels : np.ndarray
|
|
89
|
-
Labels.
|
|
90
|
-
"""
|
|
91
|
-
self.fit(adjacency)
|
|
92
|
-
return self.labels_
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def are_isomorphic(adjacency1: sparse.csr_matrix,
|
|
96
|
-
adjacency2: sparse.csr_matrix, max_iter: int = -1) -> bool:
|
|
97
|
-
"""Weisfeiler-Lehman isomorphism test. If the test is False, the graphs cannot be isomorphic,
|
|
98
|
-
otherwise, they might be.
|
|
51
|
+
|
|
52
|
+
adjacency = check_format(adjacency, allow_empty=True)
|
|
53
|
+
check_square(adjacency)
|
|
54
|
+
n_nodes = adjacency.shape[0]
|
|
55
|
+
if max_iter < 0 or max_iter > n_nodes:
|
|
56
|
+
max_iter = n_nodes
|
|
57
|
+
|
|
58
|
+
labels = np.zeros(n_nodes, dtype=np.int32)
|
|
59
|
+
powers = (-np.pi / 3.15) ** np.arange(n_nodes, dtype=np.double)
|
|
60
|
+
indptr = adjacency.indptr
|
|
61
|
+
indices = adjacency.indices
|
|
62
|
+
labels, _ = weisfeiler_lehman_coloring(indptr, indices, labels, powers, max_iter)
|
|
63
|
+
return np.array(labels)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def are_isomorphic(adjacency1: sparse.csr_matrix, adjacency2: sparse.csr_matrix, max_iter: int = -1) -> bool:
|
|
67
|
+
"""Weisfeiler-Lehman isomorphism test. If the test is False, the graphs cannot be isomorphic.
|
|
99
68
|
|
|
100
69
|
Parameters
|
|
101
70
|
-----------
|
|
@@ -104,7 +73,7 @@ def are_isomorphic(adjacency1: sparse.csr_matrix,
|
|
|
104
73
|
adjacency2 :
|
|
105
74
|
Second adjacency matrix.
|
|
106
75
|
max_iter : int
|
|
107
|
-
Maximum number of
|
|
76
|
+
Maximum number of iterations. Negative value means no limit (until convergence).
|
|
108
77
|
|
|
109
78
|
Returns
|
|
110
79
|
-------
|
|
@@ -112,7 +81,6 @@ def are_isomorphic(adjacency1: sparse.csr_matrix,
|
|
|
112
81
|
|
|
113
82
|
Example
|
|
114
83
|
-------
|
|
115
|
-
>>> from sknetwork.topology import are_isomorphic
|
|
116
84
|
>>> from sknetwork.data import house, bow_tie
|
|
117
85
|
>>> are_isomorphic(house(), bow_tie())
|
|
118
86
|
False
|
|
@@ -125,39 +93,41 @@ def are_isomorphic(adjacency1: sparse.csr_matrix,
|
|
|
125
93
|
|
|
126
94
|
* Shervashidze, N., Schweitzer, P., van Leeuwen, E. J., Melhorn, K., Borgwardt, K. M. (2011)
|
|
127
95
|
`Weisfeiler-Lehman graph kernels.
|
|
128
|
-
<
|
|
96
|
+
<https://www.jmlr.org/papers/volume12/shervashidze11a/shervashidze11a.pdf>`_
|
|
129
97
|
Journal of Machine Learning Research 12, 2011.
|
|
130
98
|
"""
|
|
99
|
+
adjacency1 = check_format(adjacency1)
|
|
100
|
+
check_square(adjacency1)
|
|
101
|
+
adjacency2 = check_format(adjacency2)
|
|
102
|
+
check_square(adjacency2)
|
|
103
|
+
|
|
131
104
|
if (adjacency1.shape != adjacency2.shape) or (adjacency1.nnz != adjacency2.nnz):
|
|
132
105
|
return False
|
|
133
106
|
|
|
134
|
-
|
|
107
|
+
n_nodes = adjacency1.shape[0]
|
|
135
108
|
|
|
136
|
-
if max_iter < 0 or max_iter >
|
|
137
|
-
max_iter =
|
|
109
|
+
if max_iter < 0 or max_iter > n_nodes:
|
|
110
|
+
max_iter = n_nodes
|
|
138
111
|
|
|
139
|
-
indptr1 = adjacency1.indptr
|
|
140
|
-
indptr2 = adjacency2.indptr
|
|
141
|
-
indices1 = adjacency1.indices
|
|
142
|
-
indices2 = adjacency2.indices
|
|
112
|
+
indptr1 = adjacency1.indptr
|
|
113
|
+
indptr2 = adjacency2.indptr
|
|
114
|
+
indices1 = adjacency1.indices
|
|
115
|
+
indices2 = adjacency2.indices
|
|
143
116
|
|
|
144
|
-
|
|
145
|
-
|
|
117
|
+
labels1 = np.zeros(n_nodes, dtype=np.int32)
|
|
118
|
+
labels2 = np.zeros(n_nodes, dtype=np.int32)
|
|
146
119
|
|
|
147
|
-
powers = (-
|
|
120
|
+
powers = (-np.pi / 3.15) ** np.arange(n_nodes, dtype=np.double)
|
|
148
121
|
|
|
149
122
|
iteration = 0
|
|
150
|
-
|
|
151
|
-
while iteration < max_iter and (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (colors_1.shape != colors_2.shape) or (counts_1 != counts_2).any():
|
|
123
|
+
has_changed1, has_changed2 = True, True
|
|
124
|
+
while iteration < max_iter and (has_changed1 or has_changed2):
|
|
125
|
+
labels1, has_changed1 = weisfeiler_lehman_coloring(indptr1, indices1, labels1, powers, max_iter=1)
|
|
126
|
+
labels2, has_changed2 = weisfeiler_lehman_coloring(indptr2, indices2, labels2, powers, max_iter=1)
|
|
127
|
+
_, counts1 = np.unique(np.array(labels1), return_counts=True)
|
|
128
|
+
_, counts2 = np.unique(np.array(labels2), return_counts=True)
|
|
129
|
+
if (counts1 != counts2).any():
|
|
159
130
|
return False
|
|
160
|
-
|
|
161
131
|
iteration += 1
|
|
162
132
|
|
|
163
133
|
return True
|
|
Binary file
|