scikit-network 0.33.3__cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.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.33.3.dist-info/METADATA +122 -0
- scikit_network-0.33.3.dist-info/RECORD +229 -0
- scikit_network-0.33.3.dist-info/WHEEL +6 -0
- scikit_network-0.33.3.dist-info/licenses/AUTHORS.rst +43 -0
- scikit_network-0.33.3.dist-info/licenses/LICENSE +34 -0
- scikit_network-0.33.3.dist-info/top_level.txt +1 -0
- scikit_network.libs/libgomp-d22c30c5.so.1.0.0 +0 -0
- sknetwork/__init__.py +21 -0
- sknetwork/base.py +67 -0
- sknetwork/classification/__init__.py +8 -0
- sknetwork/classification/base.py +142 -0
- sknetwork/classification/base_rank.py +133 -0
- sknetwork/classification/diffusion.py +134 -0
- sknetwork/classification/knn.py +139 -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 +30 -0
- sknetwork/classification/tests/test_diffusion.py +77 -0
- sknetwork/classification/tests/test_knn.py +23 -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.cpp +27587 -0
- sknetwork/classification/vote.cpython-313-aarch64-linux-gnu.so +0 -0
- sknetwork/classification/vote.pyx +56 -0
- sknetwork/clustering/__init__.py +8 -0
- sknetwork/clustering/base.py +172 -0
- sknetwork/clustering/kcenters.py +253 -0
- sknetwork/clustering/leiden.py +242 -0
- sknetwork/clustering/leiden_core.cpp +31578 -0
- sknetwork/clustering/leiden_core.cpython-313-aarch64-linux-gnu.so +0 -0
- sknetwork/clustering/leiden_core.pyx +124 -0
- sknetwork/clustering/louvain.py +286 -0
- sknetwork/clustering/louvain_core.cpp +31223 -0
- sknetwork/clustering/louvain_core.cpython-313-aarch64-linux-gnu.so +0 -0
- sknetwork/clustering/louvain_core.pyx +124 -0
- sknetwork/clustering/metrics.py +91 -0
- sknetwork/clustering/postprocess.py +66 -0
- sknetwork/clustering/propagation_clustering.py +104 -0
- sknetwork/clustering/tests/__init__.py +1 -0
- sknetwork/clustering/tests/test_API.py +38 -0
- sknetwork/clustering/tests/test_kcenters.py +60 -0
- sknetwork/clustering/tests/test_leiden.py +34 -0
- sknetwork/clustering/tests/test_louvain.py +135 -0
- sknetwork/clustering/tests/test_metrics.py +50 -0
- sknetwork/clustering/tests/test_postprocess.py +39 -0
- sknetwork/data/__init__.py +6 -0
- sknetwork/data/base.py +33 -0
- sknetwork/data/load.py +406 -0
- sknetwork/data/models.py +459 -0
- sknetwork/data/parse.py +644 -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_base.py +14 -0
- sknetwork/data/tests/test_load.py +95 -0
- sknetwork/data/tests/test_models.py +52 -0
- sknetwork/data/tests/test_parse.py +250 -0
- sknetwork/data/tests/test_test_graphs.py +29 -0
- sknetwork/data/tests/test_toy_graphs.py +68 -0
- sknetwork/data/timeout.py +38 -0
- sknetwork/data/toy_graphs.py +611 -0
- sknetwork/embedding/__init__.py +8 -0
- sknetwork/embedding/base.py +94 -0
- sknetwork/embedding/force_atlas.py +198 -0
- sknetwork/embedding/louvain_embedding.py +148 -0
- sknetwork/embedding/random_projection.py +135 -0
- sknetwork/embedding/spectral.py +141 -0
- sknetwork/embedding/spring.py +198 -0
- sknetwork/embedding/svd.py +359 -0
- sknetwork/embedding/tests/__init__.py +1 -0
- sknetwork/embedding/tests/test_API.py +49 -0
- sknetwork/embedding/tests/test_force_atlas.py +35 -0
- sknetwork/embedding/tests/test_louvain_embedding.py +33 -0
- sknetwork/embedding/tests/test_random_projection.py +28 -0
- sknetwork/embedding/tests/test_spectral.py +81 -0
- sknetwork/embedding/tests/test_spring.py +50 -0
- sknetwork/embedding/tests/test_svd.py +43 -0
- sknetwork/gnn/__init__.py +10 -0
- sknetwork/gnn/activation.py +117 -0
- sknetwork/gnn/base.py +181 -0
- sknetwork/gnn/base_activation.py +90 -0
- sknetwork/gnn/base_layer.py +109 -0
- sknetwork/gnn/gnn_classifier.py +305 -0
- sknetwork/gnn/layer.py +153 -0
- sknetwork/gnn/loss.py +180 -0
- sknetwork/gnn/neighbor_sampler.py +65 -0
- sknetwork/gnn/optimizer.py +164 -0
- sknetwork/gnn/tests/__init__.py +1 -0
- sknetwork/gnn/tests/test_activation.py +56 -0
- sknetwork/gnn/tests/test_base.py +75 -0
- sknetwork/gnn/tests/test_base_layer.py +37 -0
- sknetwork/gnn/tests/test_gnn_classifier.py +130 -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 +41 -0
- sknetwork/gnn/utils.py +127 -0
- sknetwork/hierarchy/__init__.py +6 -0
- sknetwork/hierarchy/base.py +96 -0
- sknetwork/hierarchy/louvain_hierarchy.py +272 -0
- sknetwork/hierarchy/metrics.py +234 -0
- sknetwork/hierarchy/paris.cpp +37871 -0
- sknetwork/hierarchy/paris.cpython-313-aarch64-linux-gnu.so +0 -0
- sknetwork/hierarchy/paris.pyx +316 -0
- sknetwork/hierarchy/postprocess.py +350 -0
- sknetwork/hierarchy/tests/__init__.py +1 -0
- sknetwork/hierarchy/tests/test_API.py +24 -0
- sknetwork/hierarchy/tests/test_algos.py +34 -0
- sknetwork/hierarchy/tests/test_metrics.py +62 -0
- sknetwork/hierarchy/tests/test_postprocess.py +57 -0
- sknetwork/linalg/__init__.py +9 -0
- sknetwork/linalg/basics.py +37 -0
- sknetwork/linalg/diteration.cpp +27403 -0
- sknetwork/linalg/diteration.cpython-313-aarch64-linux-gnu.so +0 -0
- sknetwork/linalg/diteration.pyx +47 -0
- sknetwork/linalg/eig_solver.py +93 -0
- sknetwork/linalg/laplacian.py +15 -0
- sknetwork/linalg/normalizer.py +86 -0
- sknetwork/linalg/operators.py +225 -0
- sknetwork/linalg/polynome.py +76 -0
- sknetwork/linalg/ppr_solver.py +170 -0
- sknetwork/linalg/push.cpp +31075 -0
- sknetwork/linalg/push.cpython-313-aarch64-linux-gnu.so +0 -0
- sknetwork/linalg/push.pyx +71 -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 +34 -0
- sknetwork/linalg/tests/test_operators.py +66 -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 +2 -0
- sknetwork/linkpred/base.py +46 -0
- sknetwork/linkpred/nn.py +126 -0
- sknetwork/linkpred/tests/__init__.py +1 -0
- sknetwork/linkpred/tests/test_nn.py +27 -0
- sknetwork/log.py +19 -0
- sknetwork/path/__init__.py +5 -0
- sknetwork/path/dag.py +54 -0
- sknetwork/path/distances.py +98 -0
- sknetwork/path/search.py +31 -0
- sknetwork/path/shortest_path.py +61 -0
- sknetwork/path/tests/__init__.py +1 -0
- sknetwork/path/tests/test_dag.py +37 -0
- sknetwork/path/tests/test_distances.py +62 -0
- sknetwork/path/tests/test_search.py +40 -0
- sknetwork/path/tests/test_shortest_path.py +40 -0
- sknetwork/ranking/__init__.py +8 -0
- sknetwork/ranking/base.py +61 -0
- sknetwork/ranking/betweenness.cpp +9710 -0
- sknetwork/ranking/betweenness.cpython-313-aarch64-linux-gnu.so +0 -0
- sknetwork/ranking/betweenness.pyx +97 -0
- sknetwork/ranking/closeness.py +92 -0
- sknetwork/ranking/hits.py +94 -0
- sknetwork/ranking/katz.py +83 -0
- sknetwork/ranking/pagerank.py +110 -0
- sknetwork/ranking/postprocess.py +37 -0
- sknetwork/ranking/tests/__init__.py +1 -0
- sknetwork/ranking/tests/test_API.py +32 -0
- sknetwork/ranking/tests/test_betweenness.py +38 -0
- sknetwork/ranking/tests/test_closeness.py +30 -0
- sknetwork/ranking/tests/test_hits.py +20 -0
- sknetwork/ranking/tests/test_pagerank.py +62 -0
- sknetwork/ranking/tests/test_postprocess.py +26 -0
- sknetwork/regression/__init__.py +4 -0
- sknetwork/regression/base.py +61 -0
- sknetwork/regression/diffusion.py +210 -0
- sknetwork/regression/tests/__init__.py +1 -0
- sknetwork/regression/tests/test_API.py +32 -0
- sknetwork/regression/tests/test_diffusion.py +56 -0
- sknetwork/sknetwork.py +3 -0
- sknetwork/test_base.py +35 -0
- sknetwork/test_log.py +15 -0
- sknetwork/topology/__init__.py +8 -0
- sknetwork/topology/cliques.cpp +32568 -0
- sknetwork/topology/cliques.cpython-313-aarch64-linux-gnu.so +0 -0
- sknetwork/topology/cliques.pyx +149 -0
- sknetwork/topology/core.cpp +30654 -0
- sknetwork/topology/core.cpython-313-aarch64-linux-gnu.so +0 -0
- sknetwork/topology/core.pyx +90 -0
- sknetwork/topology/cycles.py +243 -0
- sknetwork/topology/minheap.cpp +27335 -0
- sknetwork/topology/minheap.cpython-313-aarch64-linux-gnu.so +0 -0
- sknetwork/topology/minheap.pxd +20 -0
- sknetwork/topology/minheap.pyx +109 -0
- sknetwork/topology/structure.py +194 -0
- sknetwork/topology/tests/__init__.py +1 -0
- sknetwork/topology/tests/test_cliques.py +28 -0
- sknetwork/topology/tests/test_core.py +19 -0
- sknetwork/topology/tests/test_cycles.py +65 -0
- sknetwork/topology/tests/test_structure.py +85 -0
- sknetwork/topology/tests/test_triangles.py +38 -0
- sknetwork/topology/tests/test_wl.py +72 -0
- sknetwork/topology/triangles.cpp +8897 -0
- sknetwork/topology/triangles.cpython-313-aarch64-linux-gnu.so +0 -0
- sknetwork/topology/triangles.pyx +151 -0
- sknetwork/topology/weisfeiler_lehman.py +133 -0
- sknetwork/topology/weisfeiler_lehman_core.cpp +27638 -0
- sknetwork/topology/weisfeiler_lehman_core.cpython-313-aarch64-linux-gnu.so +0 -0
- sknetwork/topology/weisfeiler_lehman_core.pyx +114 -0
- sknetwork/utils/__init__.py +7 -0
- sknetwork/utils/check.py +355 -0
- sknetwork/utils/format.py +221 -0
- sknetwork/utils/membership.py +82 -0
- sknetwork/utils/neighbors.py +115 -0
- sknetwork/utils/tests/__init__.py +1 -0
- sknetwork/utils/tests/test_check.py +190 -0
- sknetwork/utils/tests/test_format.py +63 -0
- sknetwork/utils/tests/test_membership.py +24 -0
- sknetwork/utils/tests/test_neighbors.py +41 -0
- sknetwork/utils/tests/test_tfidf.py +18 -0
- sknetwork/utils/tests/test_values.py +66 -0
- sknetwork/utils/tfidf.py +37 -0
- sknetwork/utils/values.py +76 -0
- sknetwork/visualization/__init__.py +4 -0
- sknetwork/visualization/colors.py +34 -0
- sknetwork/visualization/dendrograms.py +277 -0
- sknetwork/visualization/graphs.py +1039 -0
- sknetwork/visualization/tests/__init__.py +1 -0
- sknetwork/visualization/tests/test_dendrograms.py +53 -0
- sknetwork/visualization/tests/test_graphs.py +176 -0
|
Binary file
|
|
@@ -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)
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
|
|
11
|
+
cdef inline int parent(int i):
|
|
12
|
+
"""Index of the parent node of i in the tree."""
|
|
13
|
+
return (i - 1) // 2
|
|
14
|
+
|
|
15
|
+
cdef inline int left(int i):
|
|
16
|
+
"""Index of the left child of i in the tree."""
|
|
17
|
+
return 2 * i + 1
|
|
18
|
+
|
|
19
|
+
cdef inline int right(int i):
|
|
20
|
+
"""Index of the right child of i in the tree."""
|
|
21
|
+
return 2 * i + 2
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@cython.boundscheck(False)
|
|
25
|
+
@cython.wraparound(False)
|
|
26
|
+
cdef class MinHeap:
|
|
27
|
+
"""Min heap data structure."""
|
|
28
|
+
def __cinit__(self, int n):
|
|
29
|
+
self.val.reserve(n) # reserves the necessary space in the vector
|
|
30
|
+
self.pos.reserve(n) # reserves the necessary space in the other vector
|
|
31
|
+
self.size = 0
|
|
32
|
+
|
|
33
|
+
cdef bint empty(self):
|
|
34
|
+
"""Check if the heap is empty."""
|
|
35
|
+
return self.size == 0
|
|
36
|
+
|
|
37
|
+
cdef inline void swap(self, int x, int y):
|
|
38
|
+
"""Exchange two elements in the heap."""
|
|
39
|
+
cdef int tmp
|
|
40
|
+
tmp = self.val[x]
|
|
41
|
+
self.val[x] = self.val[y]
|
|
42
|
+
self.val[y] = tmp
|
|
43
|
+
|
|
44
|
+
# updates the position of the corresponding elements
|
|
45
|
+
self.pos[self.val[x]] = x
|
|
46
|
+
self.pos[self.val[y]] = y
|
|
47
|
+
|
|
48
|
+
# Inserts a new key k
|
|
49
|
+
cdef void insert_key(self, int k, int[:] scores):
|
|
50
|
+
"""Insert new element into the heap"""
|
|
51
|
+
# First insert the new key at the end
|
|
52
|
+
self.val[self.size] = k
|
|
53
|
+
self.pos[k] = self.size
|
|
54
|
+
cdef int i = self.size
|
|
55
|
+
self.size += 1
|
|
56
|
+
|
|
57
|
+
cdef int p = parent(i)
|
|
58
|
+
while (p >= 0) and (scores[self.val[p]] > scores[self.val[i]]) :
|
|
59
|
+
self.swap(i, p)
|
|
60
|
+
i = p
|
|
61
|
+
p = parent(i)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
cdef void decrease_key(self, int i, int[:] scores):
|
|
65
|
+
"""Decrease value of key at index 'i' to new_val.
|
|
66
|
+
It is assumed that the new value is smaller than the old one.
|
|
67
|
+
"""
|
|
68
|
+
cdef int pos, p
|
|
69
|
+
pos = self.pos[i]
|
|
70
|
+
if pos < self.size:
|
|
71
|
+
p = parent(pos)
|
|
72
|
+
|
|
73
|
+
while (pos != 0) and (scores[self.val[p]] > scores[self.val[pos]]):
|
|
74
|
+
self.swap(pos, p)
|
|
75
|
+
pos = p
|
|
76
|
+
p = parent(pos)
|
|
77
|
+
|
|
78
|
+
cdef int pop_min(self, int[:] scores):
|
|
79
|
+
"""Remove and return the minimum element (or root) from the heap."""
|
|
80
|
+
if self.size == 1:
|
|
81
|
+
self.size = 0
|
|
82
|
+
return self.val[0]
|
|
83
|
+
|
|
84
|
+
# Store the minimum value, and remove it from heap
|
|
85
|
+
cdef int root = self.val[0]
|
|
86
|
+
self.val[0] = self.val[self.size-1]
|
|
87
|
+
self.pos[self.val[0]] = 0
|
|
88
|
+
self.size -= 1
|
|
89
|
+
self.min_heapify(0, scores)
|
|
90
|
+
|
|
91
|
+
return root
|
|
92
|
+
|
|
93
|
+
cdef void min_heapify(self, int i, int[:] scores):
|
|
94
|
+
"""A recursive method to heapify a subtree with the root at given index
|
|
95
|
+
This function assumes that the subtrees are already heapified.
|
|
96
|
+
"""
|
|
97
|
+
cdef int l, r, smallest
|
|
98
|
+
l = left(i)
|
|
99
|
+
r = right(i)
|
|
100
|
+
smallest = i
|
|
101
|
+
if (l < self.size) and (scores[self.val[l]] < scores[self.val[i]]):
|
|
102
|
+
smallest = l
|
|
103
|
+
|
|
104
|
+
if (r < self.size) and (scores[self.val[r]] < scores[self.val[smallest]]):
|
|
105
|
+
smallest = r
|
|
106
|
+
|
|
107
|
+
if smallest != i:
|
|
108
|
+
self.swap(i, smallest)
|
|
109
|
+
self.min_heapify(smallest, scores)
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Created in July 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, List
|
|
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
|
+
from sknetwork.path import get_distances
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_connected_components(input_matrix: sparse.csr_matrix, connection: str = 'weak', force_bipartite: bool = False) \
|
|
20
|
+
-> np.ndarray:
|
|
21
|
+
"""Extract the connected components of a graph.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
input_matrix :
|
|
26
|
+
Input matrix (either the adjacency matrix or the biadjacency matrix of the graph).
|
|
27
|
+
connection :
|
|
28
|
+
Must be ``'weak'`` (default) or ``'strong'``. The type of connection to use for directed graphs.
|
|
29
|
+
force_bipartite : bool
|
|
30
|
+
If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
labels :
|
|
35
|
+
Connected component of each node.
|
|
36
|
+
For bipartite graphs, rows and columns are concatenated (rows first).
|
|
37
|
+
|
|
38
|
+
Example
|
|
39
|
+
-------
|
|
40
|
+
>>> from sknetwork.topology import get_connected_components
|
|
41
|
+
>>> from sknetwork.data import house
|
|
42
|
+
>>> get_connected_components(house())
|
|
43
|
+
array([0, 0, 0, 0, 0], dtype=int32)
|
|
44
|
+
"""
|
|
45
|
+
input_matrix = check_format(input_matrix)
|
|
46
|
+
if len(input_matrix.data) == 0:
|
|
47
|
+
raise ValueError('The graph is empty (no edge).')
|
|
48
|
+
adjacency, _ = get_adjacency(input_matrix, force_bipartite=force_bipartite)
|
|
49
|
+
labels = sparse.csgraph.connected_components(adjacency, connection=connection, return_labels=True)[1]
|
|
50
|
+
return labels
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def is_connected(input_matrix: sparse.csr_matrix, connection: str = 'weak', force_bipartite: bool = False) -> bool:
|
|
54
|
+
"""Check whether the graph is connected.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
input_matrix :
|
|
59
|
+
Input matrix (either the adjacency matrix or the biadjacency matrix of the graph).
|
|
60
|
+
connection :
|
|
61
|
+
Must be ``'weak'`` (default) or ``'strong'``. The type of connection to use for directed graphs.
|
|
62
|
+
force_bipartite : bool
|
|
63
|
+
If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
|
|
64
|
+
|
|
65
|
+
Example
|
|
66
|
+
-------
|
|
67
|
+
>>> from sknetwork.topology import is_connected
|
|
68
|
+
>>> from sknetwork.data import house
|
|
69
|
+
>>> is_connected(house())
|
|
70
|
+
True
|
|
71
|
+
"""
|
|
72
|
+
return len(set(get_connected_components(input_matrix, connection, force_bipartite))) == 1
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_largest_connected_component(input_matrix: sparse.csr_matrix, connection: str = "weak",
|
|
76
|
+
force_bipartite: bool = False, return_index: bool = False) \
|
|
77
|
+
-> Union[sparse.csr_matrix, Tuple[sparse.csr_matrix, np.ndarray]]:
|
|
78
|
+
"""Extract the largest connected component of a graph. Bipartite graphs are treated as undirected.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
input_matrix :
|
|
83
|
+
Adjacency matrix or biadjacency matrix of the graph.
|
|
84
|
+
connection :
|
|
85
|
+
Must be ``'weak'`` (default) or ``'strong'``. The type of connection to use for directed graphs.
|
|
86
|
+
force_bipartite : bool
|
|
87
|
+
If ``True``, consider the input matrix as the biadjacency matrix of a bipartite graph.
|
|
88
|
+
return_index : bool
|
|
89
|
+
Whether to return the index of the nodes of the largest connected component in the original graph.
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
output_matrix : sparse.csr_matrix
|
|
94
|
+
Adjacency matrix or biadjacency matrix of the largest connected component.
|
|
95
|
+
index : array
|
|
96
|
+
Indices of the nodes in the original graph.
|
|
97
|
+
For bipartite graphs, rows and columns are concatenated (rows first).
|
|
98
|
+
|
|
99
|
+
Example
|
|
100
|
+
-------
|
|
101
|
+
>>> from sknetwork.topology import get_largest_connected_component
|
|
102
|
+
>>> from sknetwork.data import house
|
|
103
|
+
>>> get_largest_connected_component(house()).shape
|
|
104
|
+
(5, 5)
|
|
105
|
+
"""
|
|
106
|
+
input_matrix = check_format(input_matrix)
|
|
107
|
+
adjacency, bipartite = get_adjacency(input_matrix, force_bipartite=force_bipartite)
|
|
108
|
+
labels = get_connected_components(adjacency, connection=connection)
|
|
109
|
+
unique_labels, counts = np.unique(labels, return_counts=True)
|
|
110
|
+
largest_component_label = unique_labels[np.argmax(counts)]
|
|
111
|
+
|
|
112
|
+
if bipartite:
|
|
113
|
+
n_row, n_col = input_matrix.shape
|
|
114
|
+
index_row = np.argwhere(labels[:n_row] == largest_component_label).ravel()
|
|
115
|
+
index_col = np.argwhere(labels[n_row:] == largest_component_label).ravel()
|
|
116
|
+
index = np.hstack((index_row, index_col))
|
|
117
|
+
output_matrix = input_matrix[index_row, :]
|
|
118
|
+
output_matrix = (output_matrix.tocsc()[:, index_col]).tocsr()
|
|
119
|
+
else:
|
|
120
|
+
index = np.argwhere(labels == largest_component_label).ravel()
|
|
121
|
+
output_matrix = input_matrix[index, :]
|
|
122
|
+
output_matrix = (output_matrix.tocsc()[:, index]).tocsr()
|
|
123
|
+
if return_index:
|
|
124
|
+
return output_matrix, index
|
|
125
|
+
else:
|
|
126
|
+
return output_matrix
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def is_bipartite(adjacency: sparse.csr_matrix, return_biadjacency: bool = False) \
|
|
130
|
+
-> Union[bool, Tuple[bool, Optional[sparse.csr_matrix], Optional[np.ndarray], Optional[np.ndarray]]]:
|
|
131
|
+
"""Check whether a graph is bipartite.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
adjacency :
|
|
136
|
+
Adjacency matrix of the graph (symmetric).
|
|
137
|
+
return_biadjacency :
|
|
138
|
+
If ``True``, return a biadjacency matrix of the graph if bipartite.
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
is_bipartite : bool
|
|
143
|
+
A boolean denoting if the graph is bipartite.
|
|
144
|
+
biadjacency : sparse.csr_matrix
|
|
145
|
+
A biadjacency matrix of the graph if bipartite (optional).
|
|
146
|
+
rows : np.ndarray
|
|
147
|
+
Index of rows in the original graph (optional).
|
|
148
|
+
cols : np.ndarray
|
|
149
|
+
Index of columns in the original graph (optional).
|
|
150
|
+
|
|
151
|
+
Example
|
|
152
|
+
-------
|
|
153
|
+
>>> from sknetwork.topology import is_bipartite
|
|
154
|
+
>>> from sknetwork.data import cyclic_graph
|
|
155
|
+
>>> is_bipartite(cyclic_graph(4))
|
|
156
|
+
True
|
|
157
|
+
>>> is_bipartite(cyclic_graph(3))
|
|
158
|
+
False
|
|
159
|
+
"""
|
|
160
|
+
if not is_symmetric(adjacency):
|
|
161
|
+
raise ValueError('The graph must be undirected.')
|
|
162
|
+
if adjacency.diagonal().any():
|
|
163
|
+
if return_biadjacency:
|
|
164
|
+
return False, None, None, None
|
|
165
|
+
else:
|
|
166
|
+
return False
|
|
167
|
+
n = adjacency.indptr.shape[0] - 1
|
|
168
|
+
coloring = np.full(n, -1, dtype=int)
|
|
169
|
+
exists_remaining = n
|
|
170
|
+
while exists_remaining:
|
|
171
|
+
src = np.argwhere(coloring == -1)[0, 0]
|
|
172
|
+
next_nodes = [src]
|
|
173
|
+
coloring[src] = 0
|
|
174
|
+
exists_remaining -= 1
|
|
175
|
+
while next_nodes:
|
|
176
|
+
node = next_nodes.pop()
|
|
177
|
+
for neighbor in adjacency.indices[adjacency.indptr[node]:adjacency.indptr[node + 1]]:
|
|
178
|
+
if coloring[neighbor] == -1:
|
|
179
|
+
coloring[neighbor] = 1 - coloring[node]
|
|
180
|
+
next_nodes.append(neighbor)
|
|
181
|
+
exists_remaining -= 1
|
|
182
|
+
elif coloring[neighbor] == coloring[node]:
|
|
183
|
+
if return_biadjacency:
|
|
184
|
+
return False, None, None, None
|
|
185
|
+
else:
|
|
186
|
+
return False
|
|
187
|
+
if return_biadjacency:
|
|
188
|
+
rows = np.argwhere(coloring == 0).ravel()
|
|
189
|
+
cols = np.argwhere(coloring == 1).ravel()
|
|
190
|
+
return True, adjacency[rows, :][:, cols], rows, cols
|
|
191
|
+
else:
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""tests for topology"""
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for cliques"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from scipy.special import comb
|
|
7
|
+
|
|
8
|
+
from sknetwork.data.test_graphs import *
|
|
9
|
+
from sknetwork.topology.cliques import count_cliques
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestClique(unittest.TestCase):
|
|
13
|
+
|
|
14
|
+
def test_empty(self):
|
|
15
|
+
adjacency = test_graph_empty()
|
|
16
|
+
self.assertEqual(count_cliques(adjacency), 0)
|
|
17
|
+
with self.assertRaises(ValueError):
|
|
18
|
+
count_cliques(adjacency, 1)
|
|
19
|
+
|
|
20
|
+
def test_disconnected(self):
|
|
21
|
+
adjacency = test_disconnected_graph()
|
|
22
|
+
self.assertEqual(count_cliques(adjacency), 1)
|
|
23
|
+
|
|
24
|
+
def test_cliques(self):
|
|
25
|
+
adjacency = test_clique()
|
|
26
|
+
n = adjacency.shape[0]
|
|
27
|
+
self.assertEqual(count_cliques(adjacency), comb(n, 3, exact=True))
|
|
28
|
+
self.assertEqual(count_cliques(adjacency, 4), comb(n, 4, exact=True))
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for k-core decomposition"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sknetwork.data.test_graphs import *
|
|
7
|
+
from sknetwork.topology.core import get_core_decomposition
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestCoreDecomposition(unittest.TestCase):
|
|
11
|
+
|
|
12
|
+
def test_empty(self):
|
|
13
|
+
adjacency = test_graph_empty()
|
|
14
|
+
self.assertEqual(max(get_core_decomposition(adjacency)), 0)
|
|
15
|
+
|
|
16
|
+
def test_cliques(self):
|
|
17
|
+
adjacency = test_clique()
|
|
18
|
+
n = adjacency.shape[0]
|
|
19
|
+
self.assertEqual(max(get_core_decomposition(adjacency)), n - 1)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
""""tests for cycles.py"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from scipy import sparse
|
|
8
|
+
|
|
9
|
+
from sknetwork.data import star_wars, house, cyclic_digraph, cyclic_graph, linear_digraph, linear_graph
|
|
10
|
+
from sknetwork.topology import is_connected, is_acyclic, get_cycles, break_cycles
|
|
11
|
+
from sknetwork.utils.format import bipartite2undirected, directed2undirected
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestCycle(unittest.TestCase):
|
|
15
|
+
|
|
16
|
+
def test_is_acyclic(self):
|
|
17
|
+
adjacency_with_self_loops = sparse.identity(2, format='csr')
|
|
18
|
+
self.assertFalse(is_acyclic(adjacency_with_self_loops))
|
|
19
|
+
self.assertFalse(is_acyclic(adjacency_with_self_loops, directed=True))
|
|
20
|
+
directed_cycle = cyclic_digraph(3)
|
|
21
|
+
self.assertFalse(is_acyclic(directed_cycle))
|
|
22
|
+
with self.assertRaises(ValueError):
|
|
23
|
+
is_acyclic(directed_cycle, directed=False)
|
|
24
|
+
undirected_line = linear_graph(2)
|
|
25
|
+
self.assertTrue(is_acyclic(undirected_line))
|
|
26
|
+
self.assertFalse(is_acyclic(undirected_line, directed=True))
|
|
27
|
+
acyclic_graph = linear_digraph(2)
|
|
28
|
+
self.assertTrue(is_acyclic(acyclic_graph))
|
|
29
|
+
|
|
30
|
+
def test_get_cycles(self):
|
|
31
|
+
adjacency_with_self_loops = sparse.identity(2, format='csr')
|
|
32
|
+
node_cycles = get_cycles(adjacency_with_self_loops, directed=True)
|
|
33
|
+
self.assertEqual(node_cycles, [[0], [1]])
|
|
34
|
+
|
|
35
|
+
cycle_adjacency = cyclic_digraph(4)
|
|
36
|
+
node_cycles = get_cycles(cycle_adjacency, directed=True)
|
|
37
|
+
self.assertEqual(sorted(node_cycles[0]), [0, 1, 2, 3])
|
|
38
|
+
adjacency_with_subcycles = cycle_adjacency + sparse.csr_matrix(([1], ([1], [3])), shape=cycle_adjacency.shape)
|
|
39
|
+
node_cycles = get_cycles(adjacency_with_subcycles, directed=True)
|
|
40
|
+
self.assertEqual(node_cycles, [[0, 1, 3], [0, 1, 2, 3]])
|
|
41
|
+
|
|
42
|
+
undirected_cycle = cyclic_graph(4)
|
|
43
|
+
node_cycles = get_cycles(undirected_cycle, directed=False)
|
|
44
|
+
self.assertEqual(sorted(node_cycles[0]), [0, 1, 2, 3])
|
|
45
|
+
|
|
46
|
+
disconnected_cycles = sparse.csr_matrix(([1, 1, 1], ([1, 2, 3], [2, 3, 1])), shape=(4, 4))
|
|
47
|
+
node_cycles = get_cycles(disconnected_cycles, directed=True)
|
|
48
|
+
self.assertEqual(sorted(node_cycles[0]), [1, 2, 3])
|
|
49
|
+
|
|
50
|
+
def test_break_cycles(self):
|
|
51
|
+
cycle_adjacency = cyclic_digraph(4)
|
|
52
|
+
acyclic_graph = break_cycles(cycle_adjacency, root=0, directed=True)
|
|
53
|
+
self.assertTrue(is_acyclic(acyclic_graph))
|
|
54
|
+
adjacency_with_subcycles = cycle_adjacency + sparse.csr_matrix(([1], ([1], [0])), shape=cycle_adjacency.shape)
|
|
55
|
+
acyclic_graph = break_cycles(adjacency_with_subcycles, root=0, directed=True)
|
|
56
|
+
self.assertTrue(is_acyclic(acyclic_graph))
|
|
57
|
+
|
|
58
|
+
undirected_cycle = house(metadata=False)
|
|
59
|
+
acyclic_graph = break_cycles(undirected_cycle, root=0, directed=False)
|
|
60
|
+
self.assertTrue(is_acyclic(acyclic_graph))
|
|
61
|
+
|
|
62
|
+
disconnected_cycles = sparse.csr_matrix(([1, 1, 1, 1, 1], ([0, 1, 2, 3, 4], [1, 0, 3, 4, 2])), shape=(5, 5))
|
|
63
|
+
self.assertFalse(is_connected(disconnected_cycles))
|
|
64
|
+
acyclic_graph = break_cycles(disconnected_cycles, root=[0, 2], directed=True)
|
|
65
|
+
self.assertTrue(is_acyclic(acyclic_graph))
|
|
@@ -0,0 +1,85 @@
|
|
|
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, house, cyclic_digraph, cyclic_graph, 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
|
|
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)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for triangle counting"""
|
|
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.triangles import count_triangles, get_clustering_coefficient
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestTriangle(unittest.TestCase):
|
|
16
|
+
|
|
17
|
+
def test_empty(self):
|
|
18
|
+
adjacency = test_graph_empty()
|
|
19
|
+
self.assertEqual(count_triangles(adjacency), 0)
|
|
20
|
+
|
|
21
|
+
def test_disconnected(self):
|
|
22
|
+
adjacency = test_disconnected_graph()
|
|
23
|
+
self.assertEqual(count_triangles(adjacency), 1)
|
|
24
|
+
|
|
25
|
+
def test_cliques(self):
|
|
26
|
+
adjacency = test_clique()
|
|
27
|
+
n = adjacency.shape[0]
|
|
28
|
+
self.assertEqual(count_triangles(adjacency), comb(n, 3, exact=True))
|
|
29
|
+
|
|
30
|
+
def test_clustering_coefficient(self):
|
|
31
|
+
edges = [(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)]
|
|
32
|
+
adjacency = from_edge_list(edges, directed=False, matrix_only=True)
|
|
33
|
+
self.assertEqual(0.75, get_clustering_coefficient(adjacency))
|
|
34
|
+
|
|
35
|
+
def test_options(self):
|
|
36
|
+
adjacency = karate_club()
|
|
37
|
+
self.assertEqual(count_triangles(adjacency, parallelize=False), 45)
|
|
38
|
+
self.assertEqual(count_triangles(adjacency, parallelize=True), 45)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for Weisfeiler-Lehman"""
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from sknetwork.data import house, bow_tie, linear_graph
|
|
9
|
+
from sknetwork.data.test_graphs import *
|
|
10
|
+
from sknetwork.topology import color_weisfeiler_lehman, are_isomorphic
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestWLKernel(unittest.TestCase):
|
|
14
|
+
|
|
15
|
+
def test_isomorphism(self):
|
|
16
|
+
ref = house()
|
|
17
|
+
n = ref.shape[0]
|
|
18
|
+
|
|
19
|
+
adjacency = house()
|
|
20
|
+
reorder = list(range(n))
|
|
21
|
+
np.random.shuffle(reorder)
|
|
22
|
+
adjacency = adjacency[reorder][:, reorder]
|
|
23
|
+
self.assertTrue(are_isomorphic(ref, adjacency))
|
|
24
|
+
|
|
25
|
+
adjacency = bow_tie()
|
|
26
|
+
self.assertFalse(are_isomorphic(ref, adjacency))
|
|
27
|
+
|
|
28
|
+
adjacency = linear_graph(n)
|
|
29
|
+
self.assertFalse(are_isomorphic(ref, adjacency))
|
|
30
|
+
|
|
31
|
+
adjacency = linear_graph(n + 1)
|
|
32
|
+
self.assertFalse(are_isomorphic(ref, adjacency))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestWLColoring(unittest.TestCase):
|
|
36
|
+
|
|
37
|
+
def test_empty(self):
|
|
38
|
+
adjacency = test_graph_empty()
|
|
39
|
+
labels = color_weisfeiler_lehman(adjacency)
|
|
40
|
+
self.assertTrue((labels == np.zeros(10)).all())
|
|
41
|
+
|
|
42
|
+
def test_cliques(self):
|
|
43
|
+
adjacency = test_clique()
|
|
44
|
+
labels = color_weisfeiler_lehman(adjacency)
|
|
45
|
+
self.assertTrue((labels == np.zeros(10)).all())
|
|
46
|
+
|
|
47
|
+
def test_house(self):
|
|
48
|
+
adjacency = house()
|
|
49
|
+
labels = color_weisfeiler_lehman(adjacency)
|
|
50
|
+
self.assertTrue((labels == np.array([0, 2, 1, 1, 2])).all())
|
|
51
|
+
|
|
52
|
+
def test_bow_tie(self):
|
|
53
|
+
adjacency = bow_tie()
|
|
54
|
+
labels = color_weisfeiler_lehman(adjacency)
|
|
55
|
+
self.assertTrue((labels == np.array([1, 0, 0, 0, 0])).all())
|
|
56
|
+
|
|
57
|
+
def test_iso(self):
|
|
58
|
+
adjacency = house()
|
|
59
|
+
n = adjacency.indptr.shape[0] - 1
|
|
60
|
+
reorder = list(range(n))
|
|
61
|
+
np.random.shuffle(reorder)
|
|
62
|
+
adjacency2 = adjacency[reorder][:, reorder]
|
|
63
|
+
l1 = color_weisfeiler_lehman(adjacency)
|
|
64
|
+
l2 = color_weisfeiler_lehman(adjacency2)
|
|
65
|
+
l1.sort()
|
|
66
|
+
l2.sort()
|
|
67
|
+
self.assertTrue((l1 == l2).all())
|
|
68
|
+
|
|
69
|
+
def test_early_stop(self):
|
|
70
|
+
adjacency = house()
|
|
71
|
+
labels = color_weisfeiler_lehman(adjacency, max_iter=1)
|
|
72
|
+
self.assertTrue((labels == np.array([0, 1, 0, 0, 1])).all())
|