modularitypruning 1.3.5__tar.gz → 1.4.0__tar.gz
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.
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/PKG-INFO +2 -1
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/modularitypruning.egg-info/PKG-INFO +2 -1
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/modularitypruning.egg-info/SOURCES.txt +2 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/modularitypruning.egg-info/requires.txt +1 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/setup.py +2 -2
- modularitypruning-1.4.0/tests/test_documentation_examples.py +265 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/tests/test_multiplex_parameter_estimation.py +4 -4
- modularitypruning-1.4.0/tests/test_parallel_leiden_performance.py +146 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/utilities/leiden_utilities.py +36 -71
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/utilities/parameter_estimation_utilities.py +22 -4
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/utilities/plotting.py +4 -4
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/LICENSE +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/README.md +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/modularitypruning.egg-info/dependency_links.txt +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/modularitypruning.egg-info/top_level.txt +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/setup.cfg +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/tests/test_champ_coefficients_2D.py +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/tests/test_champ_coefficients_3D.py +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/tests/test_champ_usage_2D.py +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/tests/test_champ_usage_3D.py +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/tests/test_deprecated_louvain_names.py +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/tests/test_monolayer_parameter_estimation.py +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/tests/test_temporal_multilevel_parameter_estimation.py +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/utilities/__init__.py +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/utilities/champ_utilities.py +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/utilities/louvain_utilities.py +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/utilities/parameter_estimation.py +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/utilities/partition_utilities.py +0 -0
- {modularitypruning-1.3.5 → modularitypruning-1.4.0}/utilities/progress.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: modularitypruning
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.4.0
|
4
4
|
Summary: Pruning tool to identify small subsets of network partitions that are significant from the perspective of stochastic block model inference.
|
5
5
|
Home-page: https://github.com/ragibson/ModularityPruning
|
6
6
|
Author: Ryan Gibson
|
@@ -26,6 +26,7 @@ Requires-Dist: igraph
|
|
26
26
|
Requires-Dist: scikit-learn
|
27
27
|
Requires-Dist: scipy>=1.7
|
28
28
|
Requires-Dist: seaborn
|
29
|
+
Requires-Dist: tqdm
|
29
30
|
|
30
31
|
# ModularityPruning
|
31
32
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: modularitypruning
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.4.0
|
4
4
|
Summary: Pruning tool to identify small subsets of network partitions that are significant from the perspective of stochastic block model inference.
|
5
5
|
Home-page: https://github.com/ragibson/ModularityPruning
|
6
6
|
Author: Ryan Gibson
|
@@ -26,6 +26,7 @@ Requires-Dist: igraph
|
|
26
26
|
Requires-Dist: scikit-learn
|
27
27
|
Requires-Dist: scipy>=1.7
|
28
28
|
Requires-Dist: seaborn
|
29
|
+
Requires-Dist: tqdm
|
29
30
|
|
30
31
|
# ModularityPruning
|
31
32
|
|
@@ -11,8 +11,10 @@ tests/test_champ_coefficients_3D.py
|
|
11
11
|
tests/test_champ_usage_2D.py
|
12
12
|
tests/test_champ_usage_3D.py
|
13
13
|
tests/test_deprecated_louvain_names.py
|
14
|
+
tests/test_documentation_examples.py
|
14
15
|
tests/test_monolayer_parameter_estimation.py
|
15
16
|
tests/test_multiplex_parameter_estimation.py
|
17
|
+
tests/test_parallel_leiden_performance.py
|
16
18
|
tests/test_temporal_multilevel_parameter_estimation.py
|
17
19
|
utilities/__init__.py
|
18
20
|
utilities/champ_utilities.py
|
@@ -9,7 +9,7 @@ with open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
|
|
9
9
|
|
10
10
|
setup(
|
11
11
|
name='modularitypruning',
|
12
|
-
version='1.
|
12
|
+
version='1.4.0',
|
13
13
|
package_dir={'modularitypruning': 'utilities'},
|
14
14
|
packages=['modularitypruning'],
|
15
15
|
url='https://github.com/ragibson/ModularityPruning',
|
@@ -34,5 +34,5 @@ setup(
|
|
34
34
|
],
|
35
35
|
python_requires='>=3.8, <4',
|
36
36
|
install_requires=['leidenalg', 'matplotlib', "numpy", 'psutil', 'igraph',
|
37
|
-
"scikit-learn", "scipy>=1.7", 'seaborn']
|
37
|
+
"scikit-learn", "scipy>=1.7", 'seaborn', 'tqdm']
|
38
38
|
)
|
@@ -0,0 +1,265 @@
|
|
1
|
+
"""
|
2
|
+
This set of tests checks that the examples from the documentation still work correctly.
|
3
|
+
|
4
|
+
Sometimes this is simply checking that the code produces the intended output or runs without errors.
|
5
|
+
"""
|
6
|
+
from modularitypruning import prune_to_stable_partitions, prune_to_multilayer_stable_partitions
|
7
|
+
from modularitypruning.champ_utilities import CHAMP_2D, CHAMP_3D
|
8
|
+
from modularitypruning.leiden_utilities import (repeated_parallel_leiden_from_gammas,
|
9
|
+
repeated_parallel_leiden_from_gammas_omegas)
|
10
|
+
from modularitypruning.parameter_estimation_utilities import domains_to_gamma_omega_estimates, ranges_to_gamma_estimates
|
11
|
+
from modularitypruning.partition_utilities import num_communities
|
12
|
+
from modularitypruning.plotting import (plot_2d_domains_with_estimates, plot_2d_domains, plot_2d_domains_with_ami,
|
13
|
+
plot_2d_domains_with_num_communities, plot_estimates, plot_multiplex_community)
|
14
|
+
from random import seed, random
|
15
|
+
import igraph as ig
|
16
|
+
import matplotlib.pyplot as plt
|
17
|
+
import numpy as np
|
18
|
+
import unittest
|
19
|
+
|
20
|
+
|
21
|
+
class TestDocumentationExamples(unittest.TestCase):
|
22
|
+
def test_basic_singlelayer_example(self):
|
23
|
+
"""
|
24
|
+
Taken verbatim from basic_example.rst.
|
25
|
+
|
26
|
+
Like a lot of our other tests, this is stochastic but appears incredibly stable.
|
27
|
+
"""
|
28
|
+
# get Karate Club graph in igraph
|
29
|
+
G = ig.Graph.Famous("Zachary")
|
30
|
+
|
31
|
+
# run leiden 1000 times on this graph from gamma=0 to gamma=2
|
32
|
+
partitions = repeated_parallel_leiden_from_gammas(G, np.linspace(0, 2, 1000))
|
33
|
+
|
34
|
+
# prune to the stable partitions from gamma=0 to gamma=2
|
35
|
+
stable_partitions = prune_to_stable_partitions(G, partitions, 0, 2)
|
36
|
+
|
37
|
+
intended_stable_partition = [(0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 1, 0, 0, 0, 2, 2, 1,
|
38
|
+
0, 2, 0, 2, 0, 2, 3, 3, 3, 2, 3, 3, 2, 2, 3, 2, 2)]
|
39
|
+
self.assertEqual(stable_partitions, intended_stable_partition)
|
40
|
+
|
41
|
+
@staticmethod
|
42
|
+
def generate_basic_multilayer_network():
|
43
|
+
"""This is taken verbatim from basic_multilayer_example.rst."""
|
44
|
+
num_layers = 3
|
45
|
+
n_per_layer = 30
|
46
|
+
p_in = 0.5
|
47
|
+
p_out = 0.05
|
48
|
+
K = 3
|
49
|
+
|
50
|
+
# layer_vec holds the layer membership of each node
|
51
|
+
# e.g. layer_vec[5] = 2 means that node 5 resides in layer 2 (the third layer)
|
52
|
+
layer_vec = [i // n_per_layer for i in range(n_per_layer * num_layers)]
|
53
|
+
interlayer_edges = [(n_per_layer * layer + v, n_per_layer * layer + v + n_per_layer)
|
54
|
+
for layer in range(num_layers - 1)
|
55
|
+
for v in range(n_per_layer)]
|
56
|
+
|
57
|
+
# set up a community vector with
|
58
|
+
# three communities in layer 0 (each of size 10)
|
59
|
+
# three communities in layer 1 (each of size 10)
|
60
|
+
# one community in layer 2 (of size 30)
|
61
|
+
comm_per_layer = [[i // (n_per_layer // K) if layer < num_layers - 1 else 0
|
62
|
+
for i in range(n_per_layer)] for layer in range(num_layers)]
|
63
|
+
comm_vec = [item for sublist in comm_per_layer for item in sublist]
|
64
|
+
|
65
|
+
# randomly connect nodes inside each layer with undirected edges according to
|
66
|
+
# within-community probability p_in and between-community probability p_out
|
67
|
+
intralayer_edges = [(u, v) for v in range(len(comm_vec)) for u in range(v + 1, len(comm_vec))
|
68
|
+
if layer_vec[v] == layer_vec[u] and (
|
69
|
+
(comm_vec[v] == comm_vec[u] and random() < p_in) or
|
70
|
+
(comm_vec[v] != comm_vec[u] and random() < p_out)
|
71
|
+
)]
|
72
|
+
|
73
|
+
# create the networks in igraph. By Pamfil et al.'s convention, the interlayer edges
|
74
|
+
# of a temporal network are directed (representing the "one-way" nature of time)
|
75
|
+
G_intralayer = ig.Graph(intralayer_edges)
|
76
|
+
G_interlayer = ig.Graph(interlayer_edges, directed=True)
|
77
|
+
|
78
|
+
return G_intralayer, G_interlayer, layer_vec
|
79
|
+
|
80
|
+
def test_basic_multilayer_example(self):
|
81
|
+
"""
|
82
|
+
This is taken verbatim from basic_multilayer_example.rst.
|
83
|
+
|
84
|
+
For simplicity and re-use, the network generation is encapsulated in generate_basic_multilayer_network().
|
85
|
+
"""
|
86
|
+
n_per_layer = 30 # from network generation code
|
87
|
+
G_intralayer, G_interlayer, layer_vec = self.generate_basic_multilayer_network()
|
88
|
+
|
89
|
+
# run leidenalg on a uniform 32x32 grid (1024 samples) of gamma and omega in [0, 2]
|
90
|
+
gamma_range = (0, 2)
|
91
|
+
omega_range = (0, 2)
|
92
|
+
leiden_gammas = np.linspace(*gamma_range, 32)
|
93
|
+
leiden_omegas = np.linspace(*omega_range, 32)
|
94
|
+
|
95
|
+
parts = repeated_parallel_leiden_from_gammas_omegas(G_intralayer, G_interlayer, layer_vec,
|
96
|
+
gammas=leiden_gammas, omegas=leiden_omegas)
|
97
|
+
|
98
|
+
# prune to the stable partitions from (gamma=0, omega=0) to (gamma=2, omega=2)
|
99
|
+
stable_parts = prune_to_multilayer_stable_partitions(G_intralayer, G_interlayer, layer_vec,
|
100
|
+
"temporal", parts,
|
101
|
+
*gamma_range, *omega_range)
|
102
|
+
|
103
|
+
# check all 3-partition stable partitions closely match ground truth communities
|
104
|
+
for membership in stable_parts:
|
105
|
+
if num_communities(membership) != 3:
|
106
|
+
continue
|
107
|
+
|
108
|
+
most_common_label = []
|
109
|
+
for chunk_idx in range(6): # check most common label of each community (10 nodes each)
|
110
|
+
counts = {i: 0 for i in range(max(membership) + 1)}
|
111
|
+
for chunk_label in membership[10 * chunk_idx:10 * (chunk_idx + 1)]:
|
112
|
+
counts[chunk_label] += 1
|
113
|
+
most_common_label.append(max(counts.items(), key=lambda x: x[1])[0])
|
114
|
+
|
115
|
+
# check these communities look like the intended ground truth communities for the first layer
|
116
|
+
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
|
117
|
+
self.assertNotEqual(most_common_label[0], most_common_label[1])
|
118
|
+
self.assertNotEqual(most_common_label[1], most_common_label[2])
|
119
|
+
|
120
|
+
# at least one partition has the last layer mostly in one community and another splits it into multiple
|
121
|
+
unified_final_layer_count = 0
|
122
|
+
split_final_layer_count = 0
|
123
|
+
for membership in stable_parts:
|
124
|
+
count_final_layer = {i: 0 for i in range(max(membership) + 1)}
|
125
|
+
for label in membership[-n_per_layer:]:
|
126
|
+
count_final_layer[label] += 1
|
127
|
+
most_common_label_final_layer, most_common_label_count = max(count_final_layer.items(),
|
128
|
+
key=lambda x: x[1])
|
129
|
+
proportion_final_layer_having_same_label = most_common_label_count / n_per_layer
|
130
|
+
|
131
|
+
if proportion_final_layer_having_same_label > 0.9:
|
132
|
+
unified_final_layer_count += 1
|
133
|
+
elif proportion_final_layer_having_same_label < 0.5:
|
134
|
+
split_final_layer_count += 1
|
135
|
+
|
136
|
+
self.assertGreater(unified_final_layer_count, 0)
|
137
|
+
self.assertGreater(split_final_layer_count, 0)
|
138
|
+
|
139
|
+
def test_plot_estimates_example(self):
|
140
|
+
"""
|
141
|
+
This is taken (almost) verbatim from plotting_examples.rst.
|
142
|
+
|
143
|
+
The first call to plt.rc() has usetex=False (instead of True) to avoid requiring a full LaTeX installation.
|
144
|
+
"""
|
145
|
+
# get Karate Club graph in igraph
|
146
|
+
G = ig.Graph.Famous("Zachary")
|
147
|
+
|
148
|
+
# run leiden 100K times on this graph from gamma=0 to gamma=2 (takes ~2-3 seconds)
|
149
|
+
partitions = repeated_parallel_leiden_from_gammas(G, np.linspace(0, 2, 10 ** 5))
|
150
|
+
|
151
|
+
# run CHAMP to obtain the dominant partitions along with their regions of optimality
|
152
|
+
ranges = CHAMP_2D(G, partitions, gamma_0=0.0, gamma_f=2.0)
|
153
|
+
|
154
|
+
# append gamma estimate for each dominant partition onto the CHAMP domains
|
155
|
+
gamma_estimates = ranges_to_gamma_estimates(G, ranges)
|
156
|
+
|
157
|
+
# plot gamma estimates and domains of optimality
|
158
|
+
plt.rc('text', usetex=False)
|
159
|
+
plt.rc('font', family='serif')
|
160
|
+
plot_estimates(gamma_estimates)
|
161
|
+
plt.title(r"Karate Club CHAMP Domains of Optimality and $\gamma$ Estimates", fontsize=14)
|
162
|
+
plt.xlabel(r"$\gamma$", fontsize=14)
|
163
|
+
plt.ylabel("Number of communities", fontsize=14)
|
164
|
+
|
165
|
+
def test_plot_2d_domains_examples(self):
|
166
|
+
"""
|
167
|
+
This is taken (almost) verbatim from plotting_examples.rst.
|
168
|
+
|
169
|
+
The first call to plt.rc() has usetex=False (instead of True) to avoid requiring a full LaTeX installation.
|
170
|
+
|
171
|
+
The documentation explicitly shows plot_2d_domains_with_estimates() and describes other, similar functions
|
172
|
+
* plot_2d_domains()
|
173
|
+
* plot_2d_domains_with_ami()
|
174
|
+
* plot_2d_domains_with_num_communities()
|
175
|
+
As such, we test them all here.
|
176
|
+
"""
|
177
|
+
G_intralayer, G_interlayer, layer_vec = self.generate_basic_multilayer_network()
|
178
|
+
# run leiden on a uniform grid (10K samples) of gamma and omega (takes ~3 seconds)
|
179
|
+
gamma_range = (0.5, 1.5)
|
180
|
+
omega_range = (0, 2)
|
181
|
+
parts = repeated_parallel_leiden_from_gammas_omegas(G_intralayer, G_interlayer, layer_vec,
|
182
|
+
gammas=np.linspace(*gamma_range, 100),
|
183
|
+
omegas=np.linspace(*omega_range, 100))
|
184
|
+
|
185
|
+
# run CHAMP to obtain the dominant partitions along with their regions of optimality
|
186
|
+
domains = CHAMP_3D(G_intralayer, G_interlayer, layer_vec, parts,
|
187
|
+
gamma_0=gamma_range[0], gamma_f=gamma_range[1],
|
188
|
+
omega_0=omega_range[0], omega_f=omega_range[1])
|
189
|
+
|
190
|
+
# append resolution parameter estimates for each dominant partition onto the CHAMP domains
|
191
|
+
domains_with_estimates = domains_to_gamma_omega_estimates(G_intralayer, G_interlayer, layer_vec,
|
192
|
+
domains, model='temporal')
|
193
|
+
|
194
|
+
# plot resolution parameter estimates and domains of optimality
|
195
|
+
plt.rc('text', usetex=False)
|
196
|
+
plt.rc('font', family='serif')
|
197
|
+
plot_2d_domains_with_estimates(domains_with_estimates, xlim=omega_range, ylim=gamma_range)
|
198
|
+
plt.title(r"CHAMP Domains and ($\omega$, $\gamma$) Estimates", fontsize=16)
|
199
|
+
plt.xlabel(r"$\omega$", fontsize=20)
|
200
|
+
plt.ylabel(r"$\gamma$", fontsize=20)
|
201
|
+
plt.gca().tick_params(axis='both', labelsize=12)
|
202
|
+
plt.tight_layout()
|
203
|
+
|
204
|
+
# same plotting code, but with plot_2d_domains()
|
205
|
+
plt.rc('text', usetex=False)
|
206
|
+
plt.rc('font', family='serif')
|
207
|
+
plot_2d_domains(domains, xlim=omega_range, ylim=gamma_range)
|
208
|
+
plt.title(r"CHAMP Domains", fontsize=16)
|
209
|
+
plt.xlabel(r"$\omega$", fontsize=20)
|
210
|
+
plt.ylabel(r"$\gamma$", fontsize=20)
|
211
|
+
plt.gca().tick_params(axis='both', labelsize=12)
|
212
|
+
plt.tight_layout()
|
213
|
+
|
214
|
+
# same plotting code, but with plot_2d_domains_with_ami()
|
215
|
+
plt.rc('text', usetex=False)
|
216
|
+
plt.rc('font', family='serif')
|
217
|
+
ground_truth_partition = ([0] * 10 + [1] * 10 + [2] * 10) * 2 + [0] * 30
|
218
|
+
plot_2d_domains_with_ami(domains_with_estimates, ground_truth=ground_truth_partition,
|
219
|
+
xlim=omega_range, ylim=gamma_range)
|
220
|
+
plt.title(r"CHAMP Domains, Colored by AMI with Ground Truth", fontsize=16)
|
221
|
+
plt.xlabel(r"$\omega$", fontsize=20)
|
222
|
+
plt.ylabel(r"$\gamma$", fontsize=20)
|
223
|
+
plt.gca().tick_params(axis='both', labelsize=12)
|
224
|
+
plt.tight_layout()
|
225
|
+
|
226
|
+
# same plotting code, but with plot_2d_domains_with_num_communities()
|
227
|
+
plt.rc('text', usetex=False)
|
228
|
+
plt.rc('font', family='serif')
|
229
|
+
plot_2d_domains_with_num_communities(domains_with_estimates, xlim=omega_range, ylim=gamma_range)
|
230
|
+
plt.title(r"CHAMP Domains, Colored by Number of Communities", fontsize=16)
|
231
|
+
plt.xlabel(r"$\omega$", fontsize=20)
|
232
|
+
plt.ylabel(r"$\gamma$", fontsize=20)
|
233
|
+
plt.gca().tick_params(axis='both', labelsize=12)
|
234
|
+
plt.tight_layout()
|
235
|
+
plt.close() # closing all these figures instead of showing
|
236
|
+
|
237
|
+
def test_plot_multiplex_community(self):
|
238
|
+
"""
|
239
|
+
This is taken (almost) verbatim from plotting_examples.rst.
|
240
|
+
|
241
|
+
The first call to plt.rc() has usetex=False (instead of True) to avoid requiring a full LaTeX installation.
|
242
|
+
"""
|
243
|
+
num_layers = 3
|
244
|
+
layer_vec = [i // 71 for i in range(num_layers * 71)]
|
245
|
+
membership = [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
246
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
247
|
+
2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
248
|
+
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2,
|
249
|
+
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
|
250
|
+
1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2,
|
251
|
+
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
|
252
|
+
|
253
|
+
plt.rc('text', usetex=False)
|
254
|
+
plt.rc('font', family='serif')
|
255
|
+
ax = plot_multiplex_community(np.array(membership), np.array(layer_vec))
|
256
|
+
ax.set_xticks(np.linspace(0, num_layers, 2 * num_layers + 1))
|
257
|
+
ax.set_xticklabels(["", "Advice", "", "Coworker", "", "Friend", ""], fontsize=14)
|
258
|
+
plt.title(f"Multiplex Communities", fontsize=14)
|
259
|
+
plt.ylabel("Node ID", fontsize=14)
|
260
|
+
plt.close() # closing this these figures instead of showing
|
261
|
+
|
262
|
+
|
263
|
+
if __name__ == "__main__":
|
264
|
+
seed(0)
|
265
|
+
unittest.main()
|
{modularitypruning-1.3.5 → modularitypruning-1.4.0}/tests/test_multiplex_parameter_estimation.py
RENAMED
@@ -49,10 +49,10 @@ class TestMultiplexParameterEstimation(unittest.TestCase):
|
|
49
49
|
model='multiplex')
|
50
50
|
|
51
51
|
# check we converged close to the ground truth "correct" values
|
52
|
-
# the multiplex
|
53
|
-
# the copying probability approximation
|
54
|
-
self.assertLess(abs(true_gamma - gamma), 0.
|
55
|
-
self.assertLess(abs(true_omega - omega), 0.
|
52
|
+
# the multiplex parameter estimation is much less robust and less accurate than in other models,
|
53
|
+
# perhaps due to the copying probability approximation
|
54
|
+
self.assertLess(abs(true_gamma - gamma), 0.1)
|
55
|
+
self.assertLess(abs(true_omega - omega), 0.2)
|
56
56
|
|
57
57
|
def test_multiplex_SBM_correct_convergence_varying_copying_probabilty(self):
|
58
58
|
for eta in [0.25, 0.5, 0.75, 0.9]:
|
@@ -0,0 +1,146 @@
|
|
1
|
+
from .shared_testing_functions import generate_connected_ER, generate_multilayer_intralayer_SBM
|
2
|
+
from modularitypruning.leiden_utilities import (repeated_leiden_from_gammas, repeated_parallel_leiden_from_gammas,
|
3
|
+
repeated_leiden_from_gammas_omegas,
|
4
|
+
repeated_parallel_leiden_from_gammas_omegas)
|
5
|
+
from multiprocessing import Pool, cpu_count
|
6
|
+
from random import seed
|
7
|
+
from time import time, sleep
|
8
|
+
import functools
|
9
|
+
import igraph as ig
|
10
|
+
import numpy as np
|
11
|
+
import psutil
|
12
|
+
import unittest
|
13
|
+
import warnings
|
14
|
+
|
15
|
+
# this set of tests ensures that we achieve >= 75% parallel performance compared to perfect scaling of
|
16
|
+
# single-threaded jobs to multiple cores (with no memory contention). This threshold will be decreased in
|
17
|
+
# determine_target_parallelization_speedup() if the background CPU utilization exceeds 20%.
|
18
|
+
PERFORMANCE_TARGET_RELATIVE_TO_PERFECT_SCALING = 0.75
|
19
|
+
|
20
|
+
|
21
|
+
def mock_calculation(_):
|
22
|
+
"""A mock calculation that provides enough work to make serialization overhead negligible."""
|
23
|
+
return sum(range(10 ** 7))
|
24
|
+
|
25
|
+
|
26
|
+
@functools.lru_cache(maxsize=1)
|
27
|
+
def determine_target_parallelization_speedup(num_calculations=32):
|
28
|
+
"""
|
29
|
+
Calculate the parallelization speedup on mock_calculation to benchmark our implementation against.
|
30
|
+
|
31
|
+
This performs
|
32
|
+
* ``num_calculations`` function calls in the single-threaded case, and
|
33
|
+
* ``num_calculations * cpu_count()`` calls in the multi-processed case
|
34
|
+
|
35
|
+
Due in part to frequency scaling and simple memory contention, leidenalg over multiple processes (completely
|
36
|
+
outside of Python or multiprocessing.Pool) seems to run at around (90% * core count) speedup on modern systems when
|
37
|
+
hyper-threading is disabled.
|
38
|
+
"""
|
39
|
+
global PERFORMANCE_TARGET_RELATIVE_TO_PERFECT_SCALING
|
40
|
+
|
41
|
+
sleep(5) # sleep to increase stability of the CPU utilization check
|
42
|
+
cpu_utilization = psutil.cpu_percent()
|
43
|
+
if cpu_utilization > 20:
|
44
|
+
PERFORMANCE_TARGET_RELATIVE_TO_PERFECT_SCALING = 0.5
|
45
|
+
warnings.warn(f"System CPU utilization is non-negligible during parallel performance test! "
|
46
|
+
f"Dropping performance scaling target to 50%.")
|
47
|
+
|
48
|
+
start_time = time()
|
49
|
+
_ = [mock_calculation(i) for i in range(num_calculations)]
|
50
|
+
base_duration = time() - start_time
|
51
|
+
|
52
|
+
num_pool_calculations = num_calculations * cpu_count()
|
53
|
+
with Pool(processes=cpu_count()) as pool:
|
54
|
+
pool.map(mock_calculation, range(cpu_count())) # force pool initialization and basic burn-in
|
55
|
+
|
56
|
+
start_time = time()
|
57
|
+
pool.map(mock_calculation, range(num_pool_calculations))
|
58
|
+
pool_duration = time() - start_time
|
59
|
+
|
60
|
+
return num_pool_calculations / num_calculations * base_duration / pool_duration
|
61
|
+
|
62
|
+
|
63
|
+
class TestParallelLeidenPerformance(unittest.TestCase):
|
64
|
+
@staticmethod
|
65
|
+
def run_singlelayer_graph_parallelization(G, gammas):
|
66
|
+
target_speedup = determine_target_parallelization_speedup()
|
67
|
+
|
68
|
+
start_time = time()
|
69
|
+
_ = repeated_leiden_from_gammas(G, gammas)
|
70
|
+
duration = time() - start_time
|
71
|
+
|
72
|
+
pool_gammas = np.linspace(min(gammas), max(gammas), len(gammas) * cpu_count())
|
73
|
+
start_time = time()
|
74
|
+
_ = repeated_parallel_leiden_from_gammas(G, pool_gammas)
|
75
|
+
pool_duration = time() - start_time
|
76
|
+
|
77
|
+
speedup = len(pool_gammas) / len(gammas) * duration / pool_duration
|
78
|
+
return speedup / target_speedup
|
79
|
+
|
80
|
+
@staticmethod
|
81
|
+
def run_multilayer_graph_parallelization(G_intralayer, G_interlayer, layer_membership, gammas, omegas):
|
82
|
+
target_speedup = determine_target_parallelization_speedup()
|
83
|
+
|
84
|
+
start_time = time()
|
85
|
+
_ = repeated_leiden_from_gammas_omegas(G_intralayer, G_interlayer, layer_membership, gammas, omegas)
|
86
|
+
duration = time() - start_time
|
87
|
+
|
88
|
+
pool_gammas = np.linspace(min(gammas), max(gammas), int(len(gammas) * np.sqrt(cpu_count())))
|
89
|
+
pool_omegas = np.linspace(min(omegas), max(omegas), int(len(omegas) * np.sqrt(cpu_count())))
|
90
|
+
start_time = time()
|
91
|
+
_ = repeated_parallel_leiden_from_gammas_omegas(
|
92
|
+
G_intralayer, G_interlayer, layer_membership, pool_gammas, pool_omegas
|
93
|
+
)
|
94
|
+
pool_duration = time() - start_time
|
95
|
+
|
96
|
+
speedup = len(pool_gammas) * len(pool_omegas) / len(gammas) / len(omegas) * duration / pool_duration
|
97
|
+
return speedup / target_speedup
|
98
|
+
|
99
|
+
def test_tiny_singlelayer_graph_many_runs(self):
|
100
|
+
"""Single-threaded equivalent is 25k runs on G(n=34, m=78)."""
|
101
|
+
G = ig.Graph.Famous("Zachary")
|
102
|
+
gammas = np.linspace(0.0, 4.0, 25000)
|
103
|
+
parallelization = self.run_singlelayer_graph_parallelization(G, gammas)
|
104
|
+
self.assertGreater(parallelization, PERFORMANCE_TARGET_RELATIVE_TO_PERFECT_SCALING)
|
105
|
+
|
106
|
+
def test_larger_singlelayer_graph_few_runs(self):
|
107
|
+
"""Single-threaded equivalent is 50 runs on G(n=10000, m=40000)."""
|
108
|
+
G = generate_connected_ER(n=10000, m=40000, directed=False)
|
109
|
+
gammas = np.linspace(0.0, 2.0, 50)
|
110
|
+
parallelization = self.run_singlelayer_graph_parallelization(G, gammas)
|
111
|
+
self.assertGreater(parallelization, PERFORMANCE_TARGET_RELATIVE_TO_PERFECT_SCALING)
|
112
|
+
|
113
|
+
def test_tiny_multilayer_graph_many_runs(self):
|
114
|
+
"""Single-threaded equivalent is 10k runs on G(n=50, m=150)."""
|
115
|
+
G_intralayer, layer_membership = generate_multilayer_intralayer_SBM(
|
116
|
+
copying_probability=0.9, p_in=0.8, p_out=0.2, first_layer_membership=[0] * 5 + [1] * 5, num_layers=5
|
117
|
+
)
|
118
|
+
interlayer_edges = [(10 * layer + v, 10 * layer + v + 10)
|
119
|
+
for layer in range(5 - 1) for v in range(10)]
|
120
|
+
G_interlayer = ig.Graph(interlayer_edges, directed=True)
|
121
|
+
|
122
|
+
gammas = np.linspace(0.0, 2.0, 100)
|
123
|
+
omegas = np.linspace(0.0, 2.0, 100)
|
124
|
+
parallelization = self.run_multilayer_graph_parallelization(G_intralayer, G_interlayer,
|
125
|
+
layer_membership, gammas, omegas)
|
126
|
+
self.assertGreater(parallelization, PERFORMANCE_TARGET_RELATIVE_TO_PERFECT_SCALING)
|
127
|
+
|
128
|
+
def test_larger_multilayer_graph_few_runs(self):
|
129
|
+
"""Single-threaded equivalent is 49 runs on approximately G(n=2500, m=15000)."""
|
130
|
+
G_intralayer, layer_membership = generate_multilayer_intralayer_SBM(
|
131
|
+
copying_probability=0.9, p_in=0.15, p_out=0.05, first_layer_membership=[0] * 50 + [1] * 50, num_layers=25
|
132
|
+
)
|
133
|
+
interlayer_edges = [(100 * layer + v, 100 * layer + v + 100)
|
134
|
+
for layer in range(25 - 1) for v in range(100)]
|
135
|
+
G_interlayer = ig.Graph(interlayer_edges, directed=True)
|
136
|
+
|
137
|
+
gammas = np.linspace(0.0, 2.0, 7)
|
138
|
+
omegas = np.linspace(0.0, 2.0, 7)
|
139
|
+
parallelization = self.run_multilayer_graph_parallelization(G_intralayer, G_interlayer,
|
140
|
+
layer_membership, gammas, omegas)
|
141
|
+
self.assertGreater(parallelization, PERFORMANCE_TARGET_RELATIVE_TO_PERFECT_SCALING)
|
142
|
+
|
143
|
+
|
144
|
+
if __name__ == "__main__":
|
145
|
+
seed(0)
|
146
|
+
unittest.main()
|
@@ -1,12 +1,11 @@
|
|
1
|
-
from .progress import Progress
|
2
1
|
import functools
|
3
2
|
import igraph as ig
|
4
3
|
import leidenalg
|
5
4
|
from math import ceil
|
6
5
|
from multiprocessing import Pool, cpu_count
|
6
|
+
from tqdm import tqdm
|
7
7
|
import numpy as np
|
8
8
|
import psutil
|
9
|
-
import warnings
|
10
9
|
|
11
10
|
LOW_MEMORY_THRESHOLD = 1e9 # 1 GB
|
12
11
|
|
@@ -50,6 +49,11 @@ def singlelayer_leiden(G, gamma, return_partition=False):
|
|
50
49
|
return tuple(partition.membership)
|
51
50
|
|
52
51
|
|
52
|
+
def _wrapped_singlelayer_leiden(args):
|
53
|
+
"""Wrapped singlelayer_leiden() for use in multiprocessing.Pool.imap_unordered."""
|
54
|
+
return singlelayer_leiden(*args)
|
55
|
+
|
56
|
+
|
53
57
|
def leiden_part(G):
|
54
58
|
return leidenalg.RBConfigurationVertexPartition(G)
|
55
59
|
|
@@ -68,8 +72,6 @@ def split_intralayer_leiden_graph(G_intralayer, layer_membership):
|
|
68
72
|
|
69
73
|
This is needed since leidenalg lacks support for faster multilayer optimization.
|
70
74
|
|
71
|
-
WARNING: Optimization can be EXTREMELY slow! Leidenalg does not properly implement multilayer optimization.
|
72
|
-
|
73
75
|
:param G_intralayer: intralayer graph of interest
|
74
76
|
:type G_intralayer: igraph.Graph
|
75
77
|
:param layer_vec: list of each vertex's layer membership
|
@@ -77,9 +79,6 @@ def split_intralayer_leiden_graph(G_intralayer, layer_membership):
|
|
77
79
|
:return: list of intralayer networks
|
78
80
|
:rtype: list[igraph.Graph]
|
79
81
|
"""
|
80
|
-
warnings.warn("You are using Leiden multilayer modularity optimization. THIS CAN BE EXTREMELY SLOW! "
|
81
|
-
"leidenalg's implementation is inefficient, especially when there are many layers.")
|
82
|
-
|
83
82
|
# internally use hashable objects for memoization
|
84
83
|
return _split_leiden_graph_layers_cached(n=G_intralayer.vcount(), G_es=tuple(G_intralayer.es),
|
85
84
|
is_directed=G_intralayer.is_directed(),
|
@@ -108,7 +107,8 @@ def _split_leiden_graph_layers_cached(n, G_es, is_directed, layer_membership):
|
|
108
107
|
def multilayer_leiden(G_intralayer, G_interlayer, layer_vec, gamma, omega, optimiser=None, return_partition=False):
|
109
108
|
r"""Run the Leiden modularity maximization algorithm at a single (:math:`\gamma, \omega`) value.
|
110
109
|
|
111
|
-
WARNING: Optimization can be EXTREMELY slow! Leidenalg does not properly implement
|
110
|
+
WARNING: Optimization can be EXTREMELY slow for large numbers of layers! Leidenalg does not properly implement
|
111
|
+
multilayer optimization.
|
112
112
|
|
113
113
|
:param G_intralayer: intralayer graph of interest
|
114
114
|
:type G_intralayer: igraph.Graph
|
@@ -150,6 +150,11 @@ def multilayer_leiden(G_intralayer, G_interlayer, layer_vec, gamma, omega, optim
|
|
150
150
|
return tuple(intralayer_parts[0].membership)
|
151
151
|
|
152
152
|
|
153
|
+
def _wrapped_multilayer_leiden(args):
|
154
|
+
"""Wrapped multilayer_leiden() for use in multiprocessing.Pool.imap_unordered."""
|
155
|
+
return multilayer_leiden(*args)
|
156
|
+
|
157
|
+
|
153
158
|
def multilayer_leiden_part(G_intralayer, G_interlayer, layer_membership):
|
154
159
|
if 'weight' not in G_intralayer.es:
|
155
160
|
G_intralayer.es['weight'] = [1.0] * G_intralayer.ecount()
|
@@ -178,51 +183,29 @@ def repeated_leiden_from_gammas(G, gammas):
|
|
178
183
|
return {sorted_tuple(singlelayer_leiden(G, gamma)) for gamma in gammas}
|
179
184
|
|
180
185
|
|
181
|
-
def repeated_parallel_leiden_from_gammas(G, gammas, show_progress=True
|
186
|
+
def repeated_parallel_leiden_from_gammas(G, gammas, show_progress=True):
|
182
187
|
r"""Runs the Leiden modularity maximization algorithm at each provided :math:`\gamma` value, using all CPU cores.
|
183
188
|
|
184
189
|
:param G: graph of interest
|
185
190
|
:type G: igraph.Graph
|
186
191
|
:param gammas: list of gammas (resolution parameters) to run Leiden at
|
187
192
|
:type gammas: list[float]
|
188
|
-
:param show_progress: if True, render a progress bar
|
193
|
+
:param show_progress: if True, render a progress bar
|
189
194
|
:type show_progress: bool
|
190
|
-
:param chunk_dispatch: if True, dispatch parallel work in chunks. Setting this to False may increase performance,
|
191
|
-
but can lead to out-of-memory issues
|
192
|
-
:type chunk_dispatch: bool
|
193
195
|
:return: a set of all unique partitions returned by the Leiden algorithm
|
194
196
|
:rtype: set of tuple[int]
|
195
197
|
"""
|
196
|
-
|
197
|
-
pool = Pool(processes=cpu_count())
|
198
198
|
total = set()
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
else:
|
204
|
-
chunk_params = [[(G, g) for g in gammas]]
|
205
|
-
chunk_size = len(gammas)
|
206
|
-
|
207
|
-
if show_progress:
|
208
|
-
progress = Progress(ceil(len(gammas) / chunk_size))
|
209
|
-
|
210
|
-
for chunk in chunk_params:
|
211
|
-
for partition in pool.starmap(singlelayer_leiden, chunk):
|
212
|
-
total.add(sorted_tuple(partition))
|
213
|
-
|
199
|
+
pool_chunk_size = max(1, len(gammas) // (cpu_count() * 100))
|
200
|
+
with Pool(processes=cpu_count()) as pool:
|
201
|
+
pool_iterator = pool.imap_unordered(_wrapped_singlelayer_leiden, [(G, g) for g in gammas],
|
202
|
+
chunksize=pool_chunk_size)
|
214
203
|
if show_progress:
|
215
|
-
|
216
|
-
|
217
|
-
if psutil.virtual_memory().available < LOW_MEMORY_THRESHOLD:
|
218
|
-
# Reinitialize pool to get around an apparent memory leak in multiprocessing
|
219
|
-
pool.close()
|
220
|
-
pool = Pool(processes=cpu_count())
|
204
|
+
pool_iterator = tqdm(pool_iterator, total=len(gammas))
|
221
205
|
|
222
|
-
|
223
|
-
|
206
|
+
for partition in pool_iterator:
|
207
|
+
total.add(sorted_tuple(partition))
|
224
208
|
|
225
|
-
pool.close()
|
226
209
|
return total
|
227
210
|
|
228
211
|
|
@@ -232,10 +215,13 @@ def repeated_leiden_from_gammas_omegas(G_intralayer, G_interlayer, layer_vec, ga
|
|
232
215
|
|
233
216
|
|
234
217
|
def repeated_parallel_leiden_from_gammas_omegas(G_intralayer, G_interlayer, layer_vec, gammas, omegas,
|
235
|
-
show_progress=True
|
218
|
+
show_progress=True):
|
236
219
|
"""
|
237
220
|
Runs leidenalg at each gamma and omega in ``gammas`` and ``omegas``, using all CPU cores available.
|
238
221
|
|
222
|
+
WARNING: Optimization can be EXTREMELY slow for large numbers of layers! Leidenalg does not properly implement
|
223
|
+
multilayer optimization.
|
224
|
+
|
239
225
|
:param G_intralayer: intralayer graph of interest
|
240
226
|
:type G_intralayer: igraph.Graph
|
241
227
|
:param G_interlayer: interlayer graph of interest
|
@@ -248,44 +234,23 @@ def repeated_parallel_leiden_from_gammas_omegas(G_intralayer, G_interlayer, laye
|
|
248
234
|
:type omegas: list[float]
|
249
235
|
:param show_progress: if True, render a progress bar
|
250
236
|
:type show_progress: bool
|
251
|
-
:param chunk_dispatch: if True, dispatch parallel work in chunks. Setting this to False may increase performance,
|
252
|
-
but can lead to out-of-memory issues
|
253
|
-
:type chunk_dispatch: bool
|
254
237
|
:return: a set of all unique partitions encountered
|
255
238
|
:rtype: set of tuple[int]
|
256
239
|
"""
|
257
240
|
resolution_parameter_points = [(gamma, omega) for gamma in gammas for omega in omegas]
|
258
241
|
|
259
|
-
pool = Pool(processes=cpu_count())
|
260
242
|
total = set()
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
chunk_params = [[(G_intralayer, G_interlayer, layer_vec, gamma, omega)
|
269
|
-
for gamma, omega in resolution_parameter_points]]
|
270
|
-
chunk_size = len(gammas)
|
271
|
-
|
272
|
-
if show_progress:
|
273
|
-
progress = Progress(ceil(len(resolution_parameter_points) / chunk_size))
|
274
|
-
|
275
|
-
for chunk in chunk_params:
|
276
|
-
for partition in pool.starmap(multilayer_leiden, chunk):
|
277
|
-
total.add(sorted_tuple(partition))
|
278
|
-
|
243
|
+
pool_chunk_size = max(1, len(resolution_parameter_points) // (cpu_count() * 100))
|
244
|
+
with Pool(processes=cpu_count()) as pool:
|
245
|
+
pool_iterator = pool.imap_unordered(
|
246
|
+
_wrapped_multilayer_leiden,
|
247
|
+
[(G_intralayer, G_interlayer, layer_vec, gamma, omega) for gamma, omega in resolution_parameter_points],
|
248
|
+
chunksize=pool_chunk_size
|
249
|
+
)
|
279
250
|
if show_progress:
|
280
|
-
|
281
|
-
|
282
|
-
if psutil.virtual_memory().available < LOW_MEMORY_THRESHOLD:
|
283
|
-
# Reinitialize pool to get around an apparent memory leak in multiprocessing
|
284
|
-
pool.close()
|
285
|
-
pool = Pool(processes=cpu_count())
|
251
|
+
pool_iterator = tqdm(pool_iterator, total=len(resolution_parameter_points))
|
286
252
|
|
287
|
-
|
288
|
-
|
253
|
+
for partition in pool_iterator:
|
254
|
+
total.add(sorted_tuple(partition))
|
289
255
|
|
290
|
-
pool.close()
|
291
256
|
return total
|
{modularitypruning-1.3.5 → modularitypruning-1.4.0}/utilities/parameter_estimation_utilities.py
RENAMED
@@ -534,12 +534,25 @@ def prune_to_multilayer_stable_partitions(G_intralayer, G_interlayer, layer_vec,
|
|
534
534
|
parameter estimates are within the provided ``gamma_start``, ``gamma_end``, ``omega_start``, and ``omega_end``
|
535
535
|
bounds.
|
536
536
|
|
537
|
+
There are three network layer topology models available, all from Pamfil et al.
|
538
|
+
|
539
|
+
* **"temporal"**: Interlayer edges always connect copies of a node from one layer to the next, often representing
|
540
|
+
interactions that change over time.
|
541
|
+
* **"multilevel"**: Interlayer edges connect a hierarchy of monolayer networks from one layer to the next. This is
|
542
|
+
more general than temporal networks, as nodes can connect arbitrarily to nodes in the next layer. These often
|
543
|
+
represent inclusion relationships, such as cities to counties, counties to states, and states to countries.
|
544
|
+
* **"multiplex"**: Each layer represents a type of interaction, making the entire multilayer network akin to an
|
545
|
+
edge-colored multigraph (each type of edge has its own layer). This model is unique in that there is no natural
|
546
|
+
ordering of layers, and the resulting theory requires some analytical simplifications, making the resulting
|
547
|
+
parameter estimation the least robust of the three models.
|
548
|
+
|
537
549
|
See https://doi.org/10.1038/s41598-022-20142-6 for more details.
|
538
550
|
|
539
|
-
NOTE: This method
|
540
|
-
with infinite interlayer coupling estimates (e.g
|
541
|
-
``omega_end`` is set too low,
|
542
|
-
|
551
|
+
NOTE: This method will truncate omega estimates to ``omega_end - 1e-3`` (and raise a warning) if needed to properly
|
552
|
+
identify stable partitions with very large or infinite interlayer coupling estimates (e.g., when all membership
|
553
|
+
labels persist across layers). If ``omega_end`` is set too low, these partitions may be incorrectly identified as
|
554
|
+
stable. Conversely, some partitions with large omega estimates might be misclassified as not stable. Therefore, be
|
555
|
+
cautious of returned partitions with little or no community structure differences across layers.
|
543
556
|
|
544
557
|
:param G_intralayer: intralayer graph of interest
|
545
558
|
:type G_intralayer: igraph.Graph
|
@@ -599,6 +612,11 @@ def prune_to_multilayer_stable_partitions(G_intralayer, G_interlayer, layer_vec,
|
|
599
612
|
omega_start, omega_end)
|
600
613
|
domains_with_estimates = domains_to_gamma_omega_estimates(G_intralayer, G_interlayer, layer_vec, domains, model)
|
601
614
|
|
615
|
+
if any(o_est >= omega_end for _, _, g_est, o_est in domains_with_estimates if g_est is not None):
|
616
|
+
warnings.warn(f"We are truncating some omega estimates to your choice of omega_end={omega_end}. You should "
|
617
|
+
f"check that this accurately captures the high-omega behavior of the partition domains. "
|
618
|
+
f"Be cautious of partitions with little or no community structure differences across layers!")
|
619
|
+
|
602
620
|
# Truncate infinite omega solutions to our maximum omega
|
603
621
|
domains_with_estimates = [(polyverts, membership, g_est, min(o_est, omega_end - 1e-3))
|
604
622
|
for polyverts, membership, g_est, o_est in domains_with_estimates
|
@@ -18,7 +18,7 @@ def plot_estimates(gamma_estimates):
|
|
18
18
|
"""Plot partition dominance ranges with gamma estimates.
|
19
19
|
|
20
20
|
:param gamma_estimates: gamma estimates as returned from
|
21
|
-
:meth:`~modularitypruning.
|
21
|
+
:meth:`~modularitypruning.parameter_estimation_utilities.ranges_to_gamma_estimates`
|
22
22
|
"""
|
23
23
|
|
24
24
|
ax = plt.gca()
|
@@ -69,7 +69,7 @@ def plot_estimates(gamma_estimates):
|
|
69
69
|
# length_includes_head=True, alpha=0.5, zorder=2, **{"overhang": 0.5})
|
70
70
|
|
71
71
|
|
72
|
-
def plot_2d_domains(domains, xlim, ylim, flip_axes=
|
72
|
+
def plot_2d_domains(domains, xlim, ylim, flip_axes=True, use_current_axes=False):
|
73
73
|
"""Plot partition dominance ranges in the (gamma, omega) plane, using the domains from CHAMP_3D.
|
74
74
|
|
75
75
|
Limits output to xlim and ylim dimensions. Note that the plotting here has x=gamma and y=omega.
|
@@ -91,7 +91,7 @@ def plot_2d_domains(domains, xlim, ylim, flip_axes=False, use_current_axes=False
|
|
91
91
|
patches.append(polygon)
|
92
92
|
|
93
93
|
cnorm = matplotlib.colors.Normalize(vmin=0, vmax=len(domains))
|
94
|
-
cmap =
|
94
|
+
cmap = plt.get_cmap("Set1")
|
95
95
|
available_colors = {cmap(cnorm(i)) for i in range(len(domains))}
|
96
96
|
|
97
97
|
if len(available_colors) == len(domains):
|
@@ -207,7 +207,7 @@ def plot_2d_domains_with_num_communities(domains_with_estimates, xlim, ylim, fli
|
|
207
207
|
plt.ylim(ylim)
|
208
208
|
|
209
209
|
|
210
|
-
def plot_2d_domains_with_ami(domains_with_estimates, ground_truth, xlim, ylim, flip_axes=
|
210
|
+
def plot_2d_domains_with_ami(domains_with_estimates, ground_truth, xlim, ylim, flip_axes=True):
|
211
211
|
"""Plot partition dominance ranges in the (gamma, omega) plane, using the domains from CHAMP_3D and coloring by the
|
212
212
|
AMI between the partitions and ground truth.
|
213
213
|
|
File without changes
|
File without changes
|
{modularitypruning-1.3.5 → modularitypruning-1.4.0}/modularitypruning.egg-info/dependency_links.txt
RENAMED
File without changes
|
{modularitypruning-1.3.5 → modularitypruning-1.4.0}/modularitypruning.egg-info/top_level.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{modularitypruning-1.3.5 → modularitypruning-1.4.0}/tests/test_monolayer_parameter_estimation.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|