scikit-network 0.28.3__cp39-cp39-macosx_12_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of scikit-network might be problematic. Click here for more details.
- scikit_network-0.28.3.dist-info/AUTHORS.rst +41 -0
- scikit_network-0.28.3.dist-info/LICENSE +34 -0
- scikit_network-0.28.3.dist-info/METADATA +457 -0
- scikit_network-0.28.3.dist-info/RECORD +240 -0
- scikit_network-0.28.3.dist-info/WHEEL +5 -0
- scikit_network-0.28.3.dist-info/top_level.txt +1 -0
- sknetwork/__init__.py +21 -0
- sknetwork/classification/__init__.py +8 -0
- sknetwork/classification/base.py +84 -0
- sknetwork/classification/base_rank.py +143 -0
- sknetwork/classification/diffusion.py +134 -0
- sknetwork/classification/knn.py +162 -0
- sknetwork/classification/metrics.py +205 -0
- sknetwork/classification/pagerank.py +66 -0
- sknetwork/classification/propagation.py +152 -0
- sknetwork/classification/tests/__init__.py +1 -0
- sknetwork/classification/tests/test_API.py +35 -0
- sknetwork/classification/tests/test_diffusion.py +37 -0
- sknetwork/classification/tests/test_knn.py +24 -0
- sknetwork/classification/tests/test_metrics.py +53 -0
- sknetwork/classification/tests/test_pagerank.py +20 -0
- sknetwork/classification/tests/test_propagation.py +24 -0
- sknetwork/classification/vote.cpython-39-darwin.so +0 -0
- sknetwork/classification/vote.pyx +58 -0
- sknetwork/clustering/__init__.py +7 -0
- sknetwork/clustering/base.py +102 -0
- sknetwork/clustering/kmeans.py +142 -0
- sknetwork/clustering/louvain.py +255 -0
- sknetwork/clustering/louvain_core.cpython-39-darwin.so +0 -0
- sknetwork/clustering/louvain_core.pyx +134 -0
- sknetwork/clustering/metrics.py +91 -0
- sknetwork/clustering/postprocess.py +66 -0
- sknetwork/clustering/propagation_clustering.py +108 -0
- sknetwork/clustering/tests/__init__.py +1 -0
- sknetwork/clustering/tests/test_API.py +37 -0
- sknetwork/clustering/tests/test_kmeans.py +47 -0
- sknetwork/clustering/tests/test_louvain.py +104 -0
- sknetwork/clustering/tests/test_metrics.py +50 -0
- sknetwork/clustering/tests/test_post_processing.py +23 -0
- sknetwork/clustering/tests/test_postprocess.py +39 -0
- sknetwork/data/__init__.py +5 -0
- sknetwork/data/load.py +408 -0
- sknetwork/data/models.py +459 -0
- sknetwork/data/parse.py +621 -0
- sknetwork/data/test_graphs.py +84 -0
- sknetwork/data/tests/__init__.py +1 -0
- sknetwork/data/tests/test_API.py +30 -0
- sknetwork/data/tests/test_load.py +95 -0
- sknetwork/data/tests/test_models.py +52 -0
- sknetwork/data/tests/test_parse.py +253 -0
- sknetwork/data/tests/test_test_graphs.py +30 -0
- sknetwork/data/tests/test_toy_graphs.py +68 -0
- sknetwork/data/toy_graphs.py +619 -0
- sknetwork/embedding/__init__.py +10 -0
- sknetwork/embedding/base.py +90 -0
- sknetwork/embedding/force_atlas.py +197 -0
- sknetwork/embedding/louvain_embedding.py +174 -0
- sknetwork/embedding/louvain_hierarchy.py +142 -0
- sknetwork/embedding/metrics.py +66 -0
- sknetwork/embedding/random_projection.py +133 -0
- sknetwork/embedding/spectral.py +214 -0
- sknetwork/embedding/spring.py +198 -0
- sknetwork/embedding/svd.py +363 -0
- sknetwork/embedding/tests/__init__.py +1 -0
- sknetwork/embedding/tests/test_API.py +73 -0
- sknetwork/embedding/tests/test_force_atlas.py +35 -0
- sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
- sknetwork/embedding/tests/test_louvain_hierarchy.py +19 -0
- sknetwork/embedding/tests/test_metrics.py +29 -0
- sknetwork/embedding/tests/test_random_projection.py +28 -0
- sknetwork/embedding/tests/test_spectral.py +84 -0
- sknetwork/embedding/tests/test_spring.py +50 -0
- sknetwork/embedding/tests/test_svd.py +37 -0
- sknetwork/flow/__init__.py +3 -0
- sknetwork/flow/flow.py +73 -0
- sknetwork/flow/tests/__init__.py +1 -0
- sknetwork/flow/tests/test_flow.py +17 -0
- sknetwork/flow/tests/test_utils.py +69 -0
- sknetwork/flow/utils.py +91 -0
- sknetwork/gnn/__init__.py +10 -0
- sknetwork/gnn/activation.py +117 -0
- sknetwork/gnn/base.py +155 -0
- sknetwork/gnn/base_activation.py +89 -0
- sknetwork/gnn/base_layer.py +109 -0
- sknetwork/gnn/gnn_classifier.py +381 -0
- sknetwork/gnn/layer.py +153 -0
- sknetwork/gnn/layers.py +127 -0
- sknetwork/gnn/loss.py +180 -0
- sknetwork/gnn/neighbor_sampler.py +65 -0
- sknetwork/gnn/optimizer.py +163 -0
- sknetwork/gnn/tests/__init__.py +1 -0
- sknetwork/gnn/tests/test_activation.py +56 -0
- sknetwork/gnn/tests/test_base.py +79 -0
- sknetwork/gnn/tests/test_base_layer.py +37 -0
- sknetwork/gnn/tests/test_gnn_classifier.py +192 -0
- sknetwork/gnn/tests/test_layers.py +80 -0
- sknetwork/gnn/tests/test_loss.py +33 -0
- sknetwork/gnn/tests/test_neigh_sampler.py +23 -0
- sknetwork/gnn/tests/test_optimizer.py +43 -0
- sknetwork/gnn/tests/test_utils.py +93 -0
- sknetwork/gnn/utils.py +219 -0
- sknetwork/hierarchy/__init__.py +7 -0
- sknetwork/hierarchy/base.py +69 -0
- sknetwork/hierarchy/louvain_hierarchy.py +264 -0
- sknetwork/hierarchy/metrics.py +234 -0
- sknetwork/hierarchy/paris.cpython-39-darwin.so +0 -0
- sknetwork/hierarchy/paris.pyx +317 -0
- sknetwork/hierarchy/postprocess.py +350 -0
- sknetwork/hierarchy/tests/__init__.py +1 -0
- sknetwork/hierarchy/tests/test_API.py +25 -0
- sknetwork/hierarchy/tests/test_algos.py +29 -0
- sknetwork/hierarchy/tests/test_metrics.py +62 -0
- sknetwork/hierarchy/tests/test_postprocess.py +57 -0
- sknetwork/hierarchy/tests/test_ward.py +25 -0
- sknetwork/hierarchy/ward.py +94 -0
- sknetwork/linalg/__init__.py +9 -0
- sknetwork/linalg/basics.py +37 -0
- sknetwork/linalg/diteration.cpython-39-darwin.so +0 -0
- sknetwork/linalg/diteration.pyx +49 -0
- sknetwork/linalg/eig_solver.py +93 -0
- sknetwork/linalg/laplacian.py +15 -0
- sknetwork/linalg/normalization.py +66 -0
- sknetwork/linalg/operators.py +225 -0
- sknetwork/linalg/polynome.py +76 -0
- sknetwork/linalg/ppr_solver.py +170 -0
- sknetwork/linalg/push.cpython-39-darwin.so +0 -0
- sknetwork/linalg/push.pyx +73 -0
- sknetwork/linalg/sparse_lowrank.py +142 -0
- sknetwork/linalg/svd_solver.py +91 -0
- sknetwork/linalg/tests/__init__.py +1 -0
- sknetwork/linalg/tests/test_eig.py +44 -0
- sknetwork/linalg/tests/test_laplacian.py +18 -0
- sknetwork/linalg/tests/test_normalization.py +38 -0
- sknetwork/linalg/tests/test_operators.py +70 -0
- sknetwork/linalg/tests/test_polynome.py +38 -0
- sknetwork/linalg/tests/test_ppr.py +50 -0
- sknetwork/linalg/tests/test_sparse_lowrank.py +61 -0
- sknetwork/linalg/tests/test_svd.py +38 -0
- sknetwork/linkpred/__init__.py +4 -0
- sknetwork/linkpred/base.py +80 -0
- sknetwork/linkpred/first_order.py +508 -0
- sknetwork/linkpred/first_order_core.cpython-39-darwin.so +0 -0
- sknetwork/linkpred/first_order_core.pyx +315 -0
- sknetwork/linkpred/postprocessing.py +98 -0
- sknetwork/linkpred/tests/__init__.py +1 -0
- sknetwork/linkpred/tests/test_API.py +49 -0
- sknetwork/linkpred/tests/test_postprocessing.py +21 -0
- sknetwork/path/__init__.py +4 -0
- sknetwork/path/metrics.py +148 -0
- sknetwork/path/search.py +65 -0
- sknetwork/path/shortest_path.py +186 -0
- sknetwork/path/tests/__init__.py +1 -0
- sknetwork/path/tests/test_metrics.py +29 -0
- sknetwork/path/tests/test_search.py +25 -0
- sknetwork/path/tests/test_shortest_path.py +45 -0
- sknetwork/ranking/__init__.py +9 -0
- sknetwork/ranking/base.py +56 -0
- sknetwork/ranking/betweenness.cpython-39-darwin.so +0 -0
- sknetwork/ranking/betweenness.pyx +99 -0
- sknetwork/ranking/closeness.py +95 -0
- sknetwork/ranking/harmonic.py +82 -0
- sknetwork/ranking/hits.py +94 -0
- sknetwork/ranking/katz.py +81 -0
- sknetwork/ranking/pagerank.py +107 -0
- sknetwork/ranking/postprocess.py +25 -0
- sknetwork/ranking/tests/__init__.py +1 -0
- sknetwork/ranking/tests/test_API.py +34 -0
- sknetwork/ranking/tests/test_betweenness.py +38 -0
- sknetwork/ranking/tests/test_closeness.py +34 -0
- sknetwork/ranking/tests/test_hits.py +20 -0
- sknetwork/ranking/tests/test_pagerank.py +69 -0
- sknetwork/regression/__init__.py +4 -0
- sknetwork/regression/base.py +56 -0
- sknetwork/regression/diffusion.py +190 -0
- sknetwork/regression/tests/__init__.py +1 -0
- sknetwork/regression/tests/test_API.py +34 -0
- sknetwork/regression/tests/test_diffusion.py +48 -0
- sknetwork/sknetwork.py +3 -0
- sknetwork/topology/__init__.py +9 -0
- sknetwork/topology/dag.py +74 -0
- sknetwork/topology/dag_core.cpython-39-darwin.so +0 -0
- sknetwork/topology/dag_core.pyx +38 -0
- sknetwork/topology/kcliques.cpython-39-darwin.so +0 -0
- sknetwork/topology/kcliques.pyx +193 -0
- sknetwork/topology/kcore.cpython-39-darwin.so +0 -0
- sknetwork/topology/kcore.pyx +120 -0
- sknetwork/topology/structure.py +234 -0
- sknetwork/topology/tests/__init__.py +1 -0
- sknetwork/topology/tests/test_cliques.py +28 -0
- sknetwork/topology/tests/test_cores.py +21 -0
- sknetwork/topology/tests/test_dag.py +26 -0
- sknetwork/topology/tests/test_structure.py +99 -0
- sknetwork/topology/tests/test_triangles.py +42 -0
- sknetwork/topology/tests/test_wl_coloring.py +49 -0
- sknetwork/topology/tests/test_wl_kernel.py +31 -0
- sknetwork/topology/triangles.cpython-39-darwin.so +0 -0
- sknetwork/topology/triangles.pyx +166 -0
- sknetwork/topology/weisfeiler_lehman.py +163 -0
- sknetwork/topology/weisfeiler_lehman_core.cpython-39-darwin.so +0 -0
- sknetwork/topology/weisfeiler_lehman_core.pyx +116 -0
- sknetwork/utils/__init__.py +40 -0
- sknetwork/utils/base.py +35 -0
- sknetwork/utils/check.py +354 -0
- sknetwork/utils/co_neighbor.py +71 -0
- sknetwork/utils/format.py +219 -0
- sknetwork/utils/kmeans.py +89 -0
- sknetwork/utils/knn.py +166 -0
- sknetwork/utils/knn1d.cpython-39-darwin.so +0 -0
- sknetwork/utils/knn1d.pyx +80 -0
- sknetwork/utils/membership.py +82 -0
- sknetwork/utils/minheap.cpython-39-darwin.so +0 -0
- sknetwork/utils/minheap.pxd +22 -0
- sknetwork/utils/minheap.pyx +111 -0
- sknetwork/utils/neighbors.py +115 -0
- sknetwork/utils/seeds.py +75 -0
- sknetwork/utils/simplex.py +140 -0
- sknetwork/utils/tests/__init__.py +1 -0
- sknetwork/utils/tests/test_base.py +28 -0
- sknetwork/utils/tests/test_bunch.py +16 -0
- sknetwork/utils/tests/test_check.py +190 -0
- sknetwork/utils/tests/test_co_neighbor.py +43 -0
- sknetwork/utils/tests/test_format.py +61 -0
- sknetwork/utils/tests/test_kmeans.py +21 -0
- sknetwork/utils/tests/test_knn.py +32 -0
- sknetwork/utils/tests/test_membership.py +24 -0
- sknetwork/utils/tests/test_neighbors.py +41 -0
- sknetwork/utils/tests/test_projection_simplex.py +33 -0
- sknetwork/utils/tests/test_seeds.py +67 -0
- sknetwork/utils/tests/test_verbose.py +15 -0
- sknetwork/utils/tests/test_ward.py +20 -0
- sknetwork/utils/timeout.py +38 -0
- sknetwork/utils/verbose.py +37 -0
- sknetwork/utils/ward.py +60 -0
- sknetwork/visualization/__init__.py +4 -0
- sknetwork/visualization/colors.py +34 -0
- sknetwork/visualization/dendrograms.py +229 -0
- sknetwork/visualization/graphs.py +819 -0
- sknetwork/visualization/tests/__init__.py +1 -0
- sknetwork/visualization/tests/test_dendrograms.py +53 -0
- sknetwork/visualization/tests/test_graphs.py +167 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# distutils: language = c++
|
|
2
|
+
# cython: language_level=3
|
|
3
|
+
# cython: linetrace=True
|
|
4
|
+
# distutils: define_macros=CYTHON_TRACE_NOGIL=1
|
|
5
|
+
"""
|
|
6
|
+
Created on Jun 3, 2020
|
|
7
|
+
@author: Julien Simonnet <julien.simonnet@etu.upmc.fr>
|
|
8
|
+
@author: Yohann Robert <yohann.robert@etu.upmc.fr>
|
|
9
|
+
"""
|
|
10
|
+
cimport cython
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
cimport numpy as np
|
|
14
|
+
|
|
15
|
+
from scipy import sparse
|
|
16
|
+
|
|
17
|
+
from sknetwork.utils.base import Algorithm
|
|
18
|
+
from sknetwork.utils.minheap cimport MinHeap
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@cython.boundscheck(False)
|
|
22
|
+
@cython.wraparound(False)
|
|
23
|
+
cdef fit_core(int[:] indptr, int[:] indices):
|
|
24
|
+
"""Compute the core value of each node.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
indptr :
|
|
29
|
+
CSR format index array of the normalized adjacency matrix.
|
|
30
|
+
indices :
|
|
31
|
+
CSR format index pointer array of the normalized adjacency matrix.
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
labels :
|
|
36
|
+
Core value of each node.
|
|
37
|
+
"""
|
|
38
|
+
cdef int n = indptr.shape[0] - 1
|
|
39
|
+
cdef int core_value = 0 # current/max core value of the graph
|
|
40
|
+
cdef int min_node # current node of minimum degree
|
|
41
|
+
cdef int i, j, k
|
|
42
|
+
cdef int[:] degrees = np.asarray(indptr)[1:] - np.asarray(indptr)[:n]
|
|
43
|
+
cdef np.ndarray[int, ndim=1] labels = np.empty((n,), dtype=np.int32)
|
|
44
|
+
cdef MinHeap mh = MinHeap.__new__(MinHeap, n) # minimum heap with an update system
|
|
45
|
+
|
|
46
|
+
# inserts all nodes in the heap
|
|
47
|
+
for i in range(n):
|
|
48
|
+
mh.insert_key(i, degrees)
|
|
49
|
+
|
|
50
|
+
i = n - 1 # index of the rear of the list/array
|
|
51
|
+
while not mh.empty(): # until the heap is emptied
|
|
52
|
+
min_node = mh.pop_min(degrees)
|
|
53
|
+
core_value = max(core_value, degrees[min_node])
|
|
54
|
+
|
|
55
|
+
# decreases the degree of each neighbors of min_node to simulate its deletion
|
|
56
|
+
for k in range(indptr[min_node], indptr[min_node+1]):
|
|
57
|
+
j = indices[k]
|
|
58
|
+
degrees[j] -= 1
|
|
59
|
+
mh.decrease_key(j, degrees) # updates the heap to take into account the new degrees
|
|
60
|
+
|
|
61
|
+
labels[min_node] = core_value
|
|
62
|
+
i -= 1
|
|
63
|
+
|
|
64
|
+
return np.asarray(labels)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class CoreDecomposition(Algorithm):
|
|
68
|
+
"""K-core decomposition algorithm.
|
|
69
|
+
|
|
70
|
+
* Graphs
|
|
71
|
+
|
|
72
|
+
Attributes
|
|
73
|
+
----------
|
|
74
|
+
labels_ : np.ndarray
|
|
75
|
+
Core value of each node.
|
|
76
|
+
core_value_ : int
|
|
77
|
+
Maximum core value of the graph
|
|
78
|
+
|
|
79
|
+
Example
|
|
80
|
+
-------
|
|
81
|
+
>>> from sknetwork.topology import CoreDecomposition
|
|
82
|
+
>>> from sknetwork.data import karate_club
|
|
83
|
+
>>> kcore = CoreDecomposition()
|
|
84
|
+
>>> adjacency = karate_club()
|
|
85
|
+
>>> kcore.fit(adjacency)
|
|
86
|
+
>>> kcore.core_value_
|
|
87
|
+
4
|
|
88
|
+
"""
|
|
89
|
+
def __init__(self):
|
|
90
|
+
super(CoreDecomposition, self).__init__()
|
|
91
|
+
self.labels_ = None
|
|
92
|
+
self.core_value_ = None
|
|
93
|
+
|
|
94
|
+
def fit(self, adjacency: sparse.csr_matrix) -> 'CoreDecomposition':
|
|
95
|
+
"""K-core decomposition.
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
adjacency :
|
|
100
|
+
Adjacency matrix of the graph.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
self: :class:`CoreDecomposition`
|
|
105
|
+
"""
|
|
106
|
+
labels = fit_core(adjacency.indptr, adjacency.indices)
|
|
107
|
+
self.labels_ = labels
|
|
108
|
+
self.core_value_ = labels.max()
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
def fit_transform(self, adjacency: sparse.csr_matrix):
|
|
112
|
+
"""Fit algorithm to the data and return the core value of each node. Same parameters as the ``fit`` method.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
labels :
|
|
117
|
+
Core value of the nodes.
|
|
118
|
+
"""
|
|
119
|
+
self.fit(adjacency)
|
|
120
|
+
return self.labels_
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created on July 24, 2019
|
|
5
|
+
@author: Nathan de Lara <nathan.delara@polytechnique.org>
|
|
6
|
+
@author: Quentin Lutz <qlutz@enst.fr>
|
|
7
|
+
@author: Thomas Bonald <tbonald@enst.fr>
|
|
8
|
+
"""
|
|
9
|
+
from typing import Tuple, Optional, Union
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from scipy import sparse
|
|
13
|
+
|
|
14
|
+
from sknetwork.utils.check import is_symmetric, check_format
|
|
15
|
+
from sknetwork.utils.format import get_adjacency
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_connected_components(input_matrix: sparse.csr_matrix, connection: str = 'weak', force_bipartite: bool = False) \
|
|
19
|
+
-> np.ndarray:
|
|
20
|
+
"""Extract the connected components of a graph.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
input_matrix :
|
|
25
|
+
Input matrix (either the adjacency matrix or the biadjacency matrix of the graph).
|
|
26
|
+
connection :
|
|
27
|
+
Must be ``'weak'`` (default) or ``'strong'``. The type of connection to use for directed graphs.
|
|
28
|
+
force_bipartite : bool
|
|
29
|
+
If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
labels :
|
|
34
|
+
Connected component of each node.
|
|
35
|
+
For bipartite graphs, rows and columns are concatenated (rows first).
|
|
36
|
+
|
|
37
|
+
Example
|
|
38
|
+
-------
|
|
39
|
+
>>> from sknetwork.topology import get_connected_components
|
|
40
|
+
>>> from sknetwork.data import house
|
|
41
|
+
>>> get_connected_components(house())
|
|
42
|
+
array([0, 0, 0, 0, 0], dtype=int32)
|
|
43
|
+
"""
|
|
44
|
+
input_matrix = check_format(input_matrix)
|
|
45
|
+
if len(input_matrix.data) == 0:
|
|
46
|
+
raise ValueError('The graph is empty (no edge).')
|
|
47
|
+
adjacency, _ = get_adjacency(input_matrix, force_bipartite=force_bipartite)
|
|
48
|
+
labels = sparse.csgraph.connected_components(adjacency, connection=connection, return_labels=True)[1]
|
|
49
|
+
return labels
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def is_connected(input_matrix: sparse.csr_matrix, connection: str = 'weak', force_bipartite: bool = False) -> bool:
|
|
53
|
+
"""Check whether the graph is connected.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
input_matrix :
|
|
58
|
+
Input matrix (either the adjacency matrix or the biadjacency matrix of the graph).
|
|
59
|
+
connection :
|
|
60
|
+
Must be ``'weak'`` (default) or ``'strong'``. The type of connection to use for directed graphs.
|
|
61
|
+
force_bipartite : bool
|
|
62
|
+
If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
|
|
63
|
+
|
|
64
|
+
Example
|
|
65
|
+
-------
|
|
66
|
+
>>> from sknetwork.topology import is_connected
|
|
67
|
+
>>> from sknetwork.data import house
|
|
68
|
+
>>> is_connected(house())
|
|
69
|
+
True
|
|
70
|
+
"""
|
|
71
|
+
return len(set(get_connected_components(input_matrix, connection, force_bipartite))) == 1
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_largest_connected_component(input_matrix: sparse.csr_matrix, connection: str = "weak",
|
|
75
|
+
force_bipartite: bool = False, return_index: bool = False) \
|
|
76
|
+
-> Union[sparse.csr_matrix, Tuple[sparse.csr_matrix, np.ndarray]]:
|
|
77
|
+
"""Extract the largest connected component of a graph. Bipartite graphs are treated as undirected.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
input_matrix :
|
|
82
|
+
Adjacency matrix or biadjacency matrix of the graph.
|
|
83
|
+
connection :
|
|
84
|
+
Must be ``'weak'`` (default) or ``'strong'``. The type of connection to use for directed graphs.
|
|
85
|
+
force_bipartite : bool
|
|
86
|
+
If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
|
|
87
|
+
return_index : bool
|
|
88
|
+
Whether to return the index of the nodes of the largest connected component in the original graph.
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
output_matrix : sparse.csr_matrix
|
|
93
|
+
Adjacency matrix or biadjacency matrix of the largest connected component.
|
|
94
|
+
index : array
|
|
95
|
+
Indices of the nodes in the original graph.
|
|
96
|
+
For bipartite graphs, rows and columns are concatenated (rows first).
|
|
97
|
+
|
|
98
|
+
Example
|
|
99
|
+
-------
|
|
100
|
+
>>> from sknetwork.topology import get_largest_connected_component
|
|
101
|
+
>>> from sknetwork.data import house
|
|
102
|
+
>>> get_largest_connected_component(house()).shape
|
|
103
|
+
(5, 5)
|
|
104
|
+
"""
|
|
105
|
+
input_matrix = check_format(input_matrix)
|
|
106
|
+
adjacency, bipartite = get_adjacency(input_matrix, force_bipartite=force_bipartite)
|
|
107
|
+
labels = get_connected_components(adjacency, connection=connection)
|
|
108
|
+
unique_labels, counts = np.unique(labels, return_counts=True)
|
|
109
|
+
largest_component_label = unique_labels[np.argmax(counts)]
|
|
110
|
+
|
|
111
|
+
if bipartite:
|
|
112
|
+
n_row, n_col = input_matrix.shape
|
|
113
|
+
index_row = np.argwhere(labels[:n_row] == largest_component_label).ravel()
|
|
114
|
+
index_col = np.argwhere(labels[n_row:] == largest_component_label).ravel()
|
|
115
|
+
index = np.hstack((index_row, index_col))
|
|
116
|
+
output_matrix = input_matrix[index_row, :]
|
|
117
|
+
output_matrix = (output_matrix.tocsc()[:, index_col]).tocsr()
|
|
118
|
+
else:
|
|
119
|
+
index = np.argwhere(labels == largest_component_label).ravel()
|
|
120
|
+
output_matrix = input_matrix[index, :]
|
|
121
|
+
output_matrix = (output_matrix.tocsc()[:, index]).tocsr()
|
|
122
|
+
if return_index:
|
|
123
|
+
return output_matrix, index
|
|
124
|
+
else:
|
|
125
|
+
return output_matrix
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def is_bipartite(adjacency: sparse.csr_matrix, return_biadjacency: bool = False) \
|
|
129
|
+
-> Union[bool, Tuple[bool, Optional[sparse.csr_matrix], Optional[np.ndarray], Optional[np.ndarray]]]:
|
|
130
|
+
"""Check whether a graph is bipartite.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
adjacency :
|
|
135
|
+
Adjacency matrix of the graph (symmetric).
|
|
136
|
+
return_biadjacency :
|
|
137
|
+
If ``True``, return a biadjacency matrix of the graph if bipartite.
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
is_bipartite : bool
|
|
142
|
+
A boolean denoting if the graph is bipartite.
|
|
143
|
+
biadjacency : sparse.csr_matrix
|
|
144
|
+
A biadjacency matrix of the graph if bipartite (optional).
|
|
145
|
+
rows : np.ndarray
|
|
146
|
+
Index of rows in the original graph (optional).
|
|
147
|
+
cols : np.ndarray
|
|
148
|
+
Index of columns in the original graph (optional).
|
|
149
|
+
|
|
150
|
+
Example
|
|
151
|
+
-------
|
|
152
|
+
>>> from sknetwork.topology import is_bipartite
|
|
153
|
+
>>> from sknetwork.data import cyclic_graph
|
|
154
|
+
>>> is_bipartite(cyclic_graph(4))
|
|
155
|
+
True
|
|
156
|
+
>>> is_bipartite(cyclic_graph(3))
|
|
157
|
+
False
|
|
158
|
+
"""
|
|
159
|
+
if not is_symmetric(adjacency):
|
|
160
|
+
raise ValueError('The graph must be undirected.')
|
|
161
|
+
if adjacency.diagonal().any():
|
|
162
|
+
if return_biadjacency:
|
|
163
|
+
return False, None, None, None
|
|
164
|
+
else:
|
|
165
|
+
return False
|
|
166
|
+
n = adjacency.indptr.shape[0] - 1
|
|
167
|
+
coloring = np.full(n, -1, dtype=int)
|
|
168
|
+
exists_remaining = n
|
|
169
|
+
while exists_remaining:
|
|
170
|
+
src = np.argwhere(coloring == -1)[0, 0]
|
|
171
|
+
next_nodes = [src]
|
|
172
|
+
coloring[src] = 0
|
|
173
|
+
exists_remaining -= 1
|
|
174
|
+
while next_nodes:
|
|
175
|
+
node = next_nodes.pop()
|
|
176
|
+
for neighbor in adjacency.indices[adjacency.indptr[node]:adjacency.indptr[node + 1]]:
|
|
177
|
+
if coloring[neighbor] == -1:
|
|
178
|
+
coloring[neighbor] = 1 - coloring[node]
|
|
179
|
+
next_nodes.append(neighbor)
|
|
180
|
+
exists_remaining -= 1
|
|
181
|
+
elif coloring[neighbor] == coloring[node]:
|
|
182
|
+
if return_biadjacency:
|
|
183
|
+
return False, None, None, None
|
|
184
|
+
else:
|
|
185
|
+
return False
|
|
186
|
+
if return_biadjacency:
|
|
187
|
+
rows = np.argwhere(coloring == 0).ravel()
|
|
188
|
+
cols = np.argwhere(coloring == 1).ravel()
|
|
189
|
+
return True, adjacency[rows, :][:, cols], rows, cols
|
|
190
|
+
else:
|
|
191
|
+
return True
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def is_acyclic(adjacency: sparse.csr_matrix, directed: Optional[bool] = None) -> bool:
|
|
195
|
+
"""Check whether a graph has no cycle.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
adjacency:
|
|
200
|
+
Adjacency matrix of the graph.
|
|
201
|
+
directed:
|
|
202
|
+
Whether to consider the graph as directed (inferred if not specified).
|
|
203
|
+
Returns
|
|
204
|
+
-------
|
|
205
|
+
is_acyclic : bool
|
|
206
|
+
A boolean with value True if the graph has no cycle and False otherwise.
|
|
207
|
+
|
|
208
|
+
Example
|
|
209
|
+
-------
|
|
210
|
+
>>> from sknetwork.topology import is_acyclic
|
|
211
|
+
>>> from sknetwork.data import star, grid
|
|
212
|
+
>>> is_acyclic(star())
|
|
213
|
+
True
|
|
214
|
+
>>> is_acyclic(grid())
|
|
215
|
+
False
|
|
216
|
+
"""
|
|
217
|
+
if directed is False:
|
|
218
|
+
# the graph must be undirected
|
|
219
|
+
if not is_symmetric(adjacency):
|
|
220
|
+
raise ValueError("The adjacency matrix is not symmetric. The parameter 'directed' must be True.")
|
|
221
|
+
elif directed is None:
|
|
222
|
+
# if not specified, infer from the graph
|
|
223
|
+
directed = not is_symmetric(adjacency)
|
|
224
|
+
has_loops = (adjacency.diagonal() > 0).any()
|
|
225
|
+
if has_loops:
|
|
226
|
+
return False
|
|
227
|
+
else:
|
|
228
|
+
n_cc = sparse.csgraph.connected_components(adjacency, directed, connection='strong', return_labels=False)
|
|
229
|
+
n_nodes = adjacency.shape[0]
|
|
230
|
+
if directed:
|
|
231
|
+
return n_cc == n_nodes
|
|
232
|
+
else:
|
|
233
|
+
n_edges = adjacency.nnz // 2
|
|
234
|
+
return n_cc == n_nodes - n_edges
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""tests for topology"""
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for k-cliques count"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from scipy.special import comb
|
|
7
|
+
|
|
8
|
+
from sknetwork.data.test_graphs import *
|
|
9
|
+
from sknetwork.topology import Cliques
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestCliqueListing(unittest.TestCase):
|
|
13
|
+
|
|
14
|
+
def test_empty(self):
|
|
15
|
+
adjacency = test_graph_empty()
|
|
16
|
+
self.assertEqual(Cliques(2).fit_transform(adjacency), 0)
|
|
17
|
+
cliques = Cliques(1)
|
|
18
|
+
self.assertRaises(ValueError, cliques.fit_transform, adjacency)
|
|
19
|
+
|
|
20
|
+
def test_disconnected(self):
|
|
21
|
+
adjacency = test_graph_disconnect()
|
|
22
|
+
self.assertEqual(Cliques(3).fit_transform(adjacency), 1)
|
|
23
|
+
|
|
24
|
+
def test_cliques(self):
|
|
25
|
+
adjacency = test_graph_clique()
|
|
26
|
+
n = adjacency.shape[0]
|
|
27
|
+
self.assertEqual(Cliques(3).fit_transform(adjacency), comb(n, 3, exact=True))
|
|
28
|
+
self.assertEqual(Cliques(4).fit_transform(adjacency), comb(n, 4, exact=True))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for k-core decimposition"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sknetwork.data.test_graphs import *
|
|
7
|
+
from sknetwork.topology import CoreDecomposition
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestCoreDecomposition(unittest.TestCase):
|
|
11
|
+
|
|
12
|
+
def test_empty(self):
|
|
13
|
+
adjacency = test_graph_empty()
|
|
14
|
+
kcore = CoreDecomposition().fit(adjacency)
|
|
15
|
+
self.assertEqual(kcore.core_value_, 0)
|
|
16
|
+
|
|
17
|
+
def test_cliques(self):
|
|
18
|
+
adjacency = test_graph_clique()
|
|
19
|
+
n = adjacency.shape[0]
|
|
20
|
+
kcore = CoreDecomposition().fit(adjacency)
|
|
21
|
+
self.assertEqual(kcore.core_value_, n - 1)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
""""tests for dag.py"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from sknetwork.data import house
|
|
9
|
+
from sknetwork.topology import DAG
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestDAG(unittest.TestCase):
|
|
13
|
+
|
|
14
|
+
def test_options(self):
|
|
15
|
+
adjacency = house()
|
|
16
|
+
dag = DAG()
|
|
17
|
+
dag.fit(adjacency)
|
|
18
|
+
self.assertEqual(dag.indptr_.shape[0], adjacency.shape[0]+1)
|
|
19
|
+
self.assertEqual(dag.indices_.shape[0], 6)
|
|
20
|
+
|
|
21
|
+
with self.assertRaises(ValueError):
|
|
22
|
+
dag.fit(adjacency, sorted_nodes=np.arange(3))
|
|
23
|
+
|
|
24
|
+
with self.assertRaises(ValueError):
|
|
25
|
+
dag = DAG(ordering='toto')
|
|
26
|
+
dag.fit(adjacency)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
""""tests for structure.py"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from scipy import sparse
|
|
8
|
+
|
|
9
|
+
from sknetwork.data import star_wars, cyclic_digraph, linear_digraph, linear_graph
|
|
10
|
+
from sknetwork.topology import get_connected_components, get_largest_connected_component
|
|
11
|
+
from sknetwork.topology import is_connected, is_bipartite, is_acyclic
|
|
12
|
+
from sknetwork.utils.format import bipartite2undirected, directed2undirected
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestStructure(unittest.TestCase):
|
|
16
|
+
|
|
17
|
+
def test_cc(self):
|
|
18
|
+
adjacency = linear_digraph(3)
|
|
19
|
+
labels = get_connected_components(adjacency, connection='weak')
|
|
20
|
+
self.assertEqual(len(set(labels)), 1)
|
|
21
|
+
adjacency = linear_digraph(3)
|
|
22
|
+
labels = get_connected_components(adjacency, connection='weak', force_bipartite=True)
|
|
23
|
+
self.assertEqual(len(set(labels)), 4)
|
|
24
|
+
biadjacency = sparse.csr_matrix(([1, 1, 1, 1], [[1, 2, 3, 3], [1, 2, 2, 3]]))
|
|
25
|
+
labels = get_connected_components(biadjacency)
|
|
26
|
+
self.assertEqual(len(set(labels)), 3)
|
|
27
|
+
|
|
28
|
+
def test_connected(self):
|
|
29
|
+
adjacency = cyclic_digraph(3)
|
|
30
|
+
self.assertEqual(is_connected(adjacency), True)
|
|
31
|
+
adjacency = linear_digraph(3)
|
|
32
|
+
self.assertEqual(is_connected(adjacency, connection='strong'), False)
|
|
33
|
+
biadjacency = star_wars()
|
|
34
|
+
self.assertEqual(is_connected(biadjacency), True)
|
|
35
|
+
|
|
36
|
+
def test_largest_cc(self):
|
|
37
|
+
adjacency = cyclic_digraph(3)
|
|
38
|
+
adjacency += adjacency.T
|
|
39
|
+
largest_cc, index = get_largest_connected_component(adjacency, return_index=True)
|
|
40
|
+
|
|
41
|
+
self.assertAlmostEqual(np.linalg.norm(largest_cc.toarray() - np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]])), 0)
|
|
42
|
+
self.assertEqual(np.linalg.norm(index - np.array([0, 1, 2])), 0)
|
|
43
|
+
|
|
44
|
+
biadjacency = star_wars(metadata=False)
|
|
45
|
+
largest_cc, index = get_largest_connected_component(biadjacency, return_index=True)
|
|
46
|
+
self.assertAlmostEqual(np.linalg.norm(largest_cc.toarray() - np.array([[1, 0, 1],
|
|
47
|
+
[1, 0, 0],
|
|
48
|
+
[1, 1, 1],
|
|
49
|
+
[0, 1, 1]])), 0)
|
|
50
|
+
self.assertEqual(np.linalg.norm(index - np.array([0, 1, 2, 3, 0, 1, 2])), 0)
|
|
51
|
+
|
|
52
|
+
biadjacency = sparse.csr_matrix(([1, 1, 1, 1], [[1, 2, 3, 3], [1, 2, 2, 3]]))
|
|
53
|
+
largest_cc, index = get_largest_connected_component(biadjacency, force_bipartite=True, return_index=True)
|
|
54
|
+
self.assertEqual(largest_cc.shape[0], 2)
|
|
55
|
+
self.assertEqual(len(index), 4)
|
|
56
|
+
|
|
57
|
+
self.assertTrue(isinstance(get_largest_connected_component(adjacency, return_index=False), sparse.csr_matrix))
|
|
58
|
+
|
|
59
|
+
def test_is_bipartite(self):
|
|
60
|
+
biadjacency = star_wars(metadata=False)
|
|
61
|
+
adjacency = bipartite2undirected(biadjacency)
|
|
62
|
+
self.assertTrue(is_bipartite(adjacency))
|
|
63
|
+
|
|
64
|
+
bipartite, biadjacency_pred, _, _ = is_bipartite(adjacency, return_biadjacency=True)
|
|
65
|
+
self.assertEqual(bipartite, True)
|
|
66
|
+
self.assertEqual(np.all(biadjacency.data == biadjacency_pred.data), True)
|
|
67
|
+
|
|
68
|
+
adjacency = sparse.identity(2, format='csr')
|
|
69
|
+
bipartite, biadjacency, _, _ = is_bipartite(adjacency, return_biadjacency=True)
|
|
70
|
+
self.assertEqual(bipartite, False)
|
|
71
|
+
self.assertIsNone(biadjacency)
|
|
72
|
+
|
|
73
|
+
adjacency = directed2undirected(cyclic_digraph(3))
|
|
74
|
+
bipartite, biadjacency, _, _ = is_bipartite(adjacency, return_biadjacency=True)
|
|
75
|
+
self.assertEqual(bipartite, False)
|
|
76
|
+
self.assertIsNone(biadjacency)
|
|
77
|
+
|
|
78
|
+
with self.assertRaises(ValueError):
|
|
79
|
+
is_bipartite(cyclic_digraph(3))
|
|
80
|
+
|
|
81
|
+
self.assertFalse(is_bipartite(sparse.eye(3)))
|
|
82
|
+
|
|
83
|
+
adjacency = directed2undirected(cyclic_digraph(3))
|
|
84
|
+
bipartite = is_bipartite(adjacency, return_biadjacency=False)
|
|
85
|
+
self.assertEqual(bipartite, False)
|
|
86
|
+
|
|
87
|
+
def test_is_acyclic(self):
|
|
88
|
+
adjacency_with_self_loops = sparse.identity(2, format='csr')
|
|
89
|
+
self.assertFalse(is_acyclic(adjacency_with_self_loops))
|
|
90
|
+
self.assertFalse(is_acyclic(adjacency_with_self_loops, directed=True))
|
|
91
|
+
directed_cycle = cyclic_digraph(3)
|
|
92
|
+
self.assertFalse(is_acyclic(directed_cycle))
|
|
93
|
+
with self.assertRaises(ValueError):
|
|
94
|
+
is_acyclic(directed_cycle, directed=False)
|
|
95
|
+
undirected_line = linear_graph(2)
|
|
96
|
+
self.assertTrue(is_acyclic(undirected_line))
|
|
97
|
+
self.assertFalse(is_acyclic(undirected_line, directed=True))
|
|
98
|
+
acyclic_graph = linear_digraph(2)
|
|
99
|
+
self.assertTrue(is_acyclic(acyclic_graph))
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for label propagation"""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
from scipy.special import comb
|
|
8
|
+
|
|
9
|
+
from sknetwork.data import karate_club
|
|
10
|
+
from sknetwork.data.parse import from_edge_list
|
|
11
|
+
from sknetwork.data.test_graphs import *
|
|
12
|
+
from sknetwork.topology import Triangles
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestTriangleListing(unittest.TestCase):
|
|
16
|
+
|
|
17
|
+
def test_empty(self):
|
|
18
|
+
adjacency = test_graph_empty()
|
|
19
|
+
self.assertEqual(Triangles().fit_transform(adjacency), 0)
|
|
20
|
+
|
|
21
|
+
def test_disconnected(self):
|
|
22
|
+
adjacency = test_graph_disconnect()
|
|
23
|
+
self.assertEqual(Triangles().fit_transform(adjacency), 1)
|
|
24
|
+
|
|
25
|
+
def test_cliques(self):
|
|
26
|
+
adjacency = test_graph_clique()
|
|
27
|
+
n = adjacency.shape[0]
|
|
28
|
+
nb = Triangles().fit_transform(adjacency)
|
|
29
|
+
self.assertEqual(nb, comb(n, 3, exact=True))
|
|
30
|
+
|
|
31
|
+
def test_clustering_coefficient(self):
|
|
32
|
+
edges = [(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)]
|
|
33
|
+
adjacency = from_edge_list(edges, directed=False, matrix_only=True)
|
|
34
|
+
|
|
35
|
+
triangles = Triangles().fit(adjacency)
|
|
36
|
+
self.assertEqual(0.75, triangles.clustering_coef_)
|
|
37
|
+
|
|
38
|
+
def test_options(self):
|
|
39
|
+
adjacency = karate_club()
|
|
40
|
+
|
|
41
|
+
self.assertEqual(Triangles(parallelize=False).fit_transform(adjacency), 45)
|
|
42
|
+
self.assertEqual(Triangles(parallelize=True).fit_transform(adjacency), 45)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for Weisfeiler-Lehman coloring"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sknetwork.data import house, bow_tie
|
|
7
|
+
from sknetwork.data.test_graphs import *
|
|
8
|
+
from sknetwork.topology import WeisfeilerLehman
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestWLColoring(unittest.TestCase):
|
|
12
|
+
|
|
13
|
+
def test_empty(self):
|
|
14
|
+
adjacency = test_graph_empty()
|
|
15
|
+
labels = WeisfeilerLehman().fit_transform(adjacency)
|
|
16
|
+
self.assertTrue((labels == np.zeros(10)).all())
|
|
17
|
+
|
|
18
|
+
def test_cliques(self):
|
|
19
|
+
adjacency = test_graph_clique()
|
|
20
|
+
labels = WeisfeilerLehman().fit_transform(adjacency)
|
|
21
|
+
self.assertTrue((labels == np.zeros(10)).all())
|
|
22
|
+
|
|
23
|
+
def test_house(self):
|
|
24
|
+
adjacency = house()
|
|
25
|
+
labels = WeisfeilerLehman().fit_transform(adjacency)
|
|
26
|
+
self.assertTrue((labels == np.array([0, 2, 1, 1, 2])).all())
|
|
27
|
+
|
|
28
|
+
def test_bow_tie(self):
|
|
29
|
+
adjacency = bow_tie()
|
|
30
|
+
labels = WeisfeilerLehman().fit_transform(adjacency)
|
|
31
|
+
self.assertTrue((labels == np.array([1, 0, 0, 0, 0])).all())
|
|
32
|
+
|
|
33
|
+
def test_iso(self):
|
|
34
|
+
adjacency = house()
|
|
35
|
+
n = adjacency.indptr.shape[0] - 1
|
|
36
|
+
reorder = list(range(n))
|
|
37
|
+
np.random.shuffle(reorder)
|
|
38
|
+
adjacency2 = adjacency[reorder][:, reorder]
|
|
39
|
+
l1 = WeisfeilerLehman().fit_transform(adjacency)
|
|
40
|
+
l2 = WeisfeilerLehman().fit_transform(adjacency2)
|
|
41
|
+
l1.sort()
|
|
42
|
+
l2.sort()
|
|
43
|
+
self.assertTrue((l1 == l2).all())
|
|
44
|
+
|
|
45
|
+
def test_early_stop(self):
|
|
46
|
+
adjacency = house()
|
|
47
|
+
wl = WeisfeilerLehman(max_iter=1)
|
|
48
|
+
labels = wl.fit_transform(adjacency)
|
|
49
|
+
self.assertTrue((labels == np.array([0, 1, 0, 0, 1])).all())
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for Weisfeiler-Lehman kernels"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from sknetwork.data import house, bow_tie, linear_graph
|
|
9
|
+
from sknetwork.topology import are_isomorphic
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestWLKernel(unittest.TestCase):
|
|
13
|
+
|
|
14
|
+
def test_isomorphism(self):
|
|
15
|
+
ref = house()
|
|
16
|
+
n = ref.shape[0]
|
|
17
|
+
|
|
18
|
+
adjacency = house()
|
|
19
|
+
reorder = list(range(n))
|
|
20
|
+
np.random.shuffle(reorder)
|
|
21
|
+
adjacency = adjacency[reorder][:, reorder]
|
|
22
|
+
self.assertTrue(are_isomorphic(ref, adjacency))
|
|
23
|
+
|
|
24
|
+
adjacency = bow_tie()
|
|
25
|
+
self.assertFalse(are_isomorphic(ref, adjacency))
|
|
26
|
+
|
|
27
|
+
adjacency = linear_graph(n)
|
|
28
|
+
self.assertFalse(are_isomorphic(ref, adjacency))
|
|
29
|
+
|
|
30
|
+
adjacency = linear_graph(n + 1)
|
|
31
|
+
self.assertFalse(are_isomorphic(ref, adjacency))
|
|
Binary file
|