modularitypruning 2.0.0__tar.gz → 2.0.2__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-2.0.0 → modularitypruning-2.0.2}/PKG-INFO +4 -4
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/README.md +3 -3
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/modularitypruning.egg-info/PKG-INFO +4 -4
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/modularitypruning.egg-info/SOURCES.txt +1 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/pyproject.toml +12 -2
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_monolayer_parameter_estimation.py +2 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_multiplex_parameter_estimation.py +3 -1
- modularitypruning-2.0.2/tests/test_prune_stable_partitions.py +55 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_temporal_multilevel_parameter_estimation.py +3 -1
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/utilities/champ_utilities.py +62 -61
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/utilities/parameter_estimation_utilities.py +26 -21
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/utilities/plotting.py +1 -1
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/utilities/types.py +4 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/LICENSE +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/modularitypruning.egg-info/dependency_links.txt +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/modularitypruning.egg-info/requires.txt +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/modularitypruning.egg-info/top_level.txt +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/setup.cfg +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_champ_coefficients_2D.py +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_champ_coefficients_3D.py +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_champ_usage_2D.py +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_champ_usage_3D.py +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_deprecated_louvain_names.py +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_documentation_examples.py +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_parallel_leiden_performance.py +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/utilities/__init__.py +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/utilities/leiden_utilities.py +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/utilities/louvain_utilities.py +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/utilities/parameter_estimation.py +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/utilities/partition_utilities.py +0 -0
- {modularitypruning-2.0.0 → modularitypruning-2.0.2}/utilities/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modularitypruning
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
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
|
Author-email: Ryan Gibson <ryan.alex.gibson@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -105,6 +105,6 @@ which is the stable 4-community split of the Karate Club network.
|
|
|
105
105
|
|
|
106
106
|
## More Information
|
|
107
107
|
|
|
108
|
-
The [issues
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
The [issues](https://github.com/ragibson/ModularityPruning/issues) and
|
|
109
|
+
[figure generation runtimes README](figure_generation_runtimes.md) may also be
|
|
110
|
+
of interest.
|
|
@@ -74,6 +74,6 @@ which is the stable 4-community split of the Karate Club network.
|
|
|
74
74
|
|
|
75
75
|
## More Information
|
|
76
76
|
|
|
77
|
-
The [issues
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
The [issues](https://github.com/ragibson/ModularityPruning/issues) and
|
|
78
|
+
[figure generation runtimes README](figure_generation_runtimes.md) may also be
|
|
79
|
+
of interest.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modularitypruning
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
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
|
Author-email: Ryan Gibson <ryan.alex.gibson@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -105,6 +105,6 @@ which is the stable 4-community split of the Karate Club network.
|
|
|
105
105
|
|
|
106
106
|
## More Information
|
|
107
107
|
|
|
108
|
-
The [issues
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
The [issues](https://github.com/ragibson/ModularityPruning/issues) and
|
|
109
|
+
[figure generation runtimes README](figure_generation_runtimes.md) may also be
|
|
110
|
+
of interest.
|
|
@@ -15,6 +15,7 @@ tests/test_documentation_examples.py
|
|
|
15
15
|
tests/test_monolayer_parameter_estimation.py
|
|
16
16
|
tests/test_multiplex_parameter_estimation.py
|
|
17
17
|
tests/test_parallel_leiden_performance.py
|
|
18
|
+
tests/test_prune_stable_partitions.py
|
|
18
19
|
tests/test_temporal_multilevel_parameter_estimation.py
|
|
19
20
|
utilities/__init__.py
|
|
20
21
|
utilities/champ_utilities.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "modularitypruning"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.2"
|
|
8
8
|
description = "Pruning tool to identify small subsets of network partitions that are significant from the perspective of stochastic block model inference."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -48,4 +48,14 @@ modularitypruning = "utilities"
|
|
|
48
48
|
modularitypruning = ["py.typed"]
|
|
49
49
|
|
|
50
50
|
[tool.autopep8]
|
|
51
|
-
max_line_length = 120
|
|
51
|
+
max_line_length = 120
|
|
52
|
+
|
|
53
|
+
[tool.coverage.run]
|
|
54
|
+
# a bit strange to include tests, but this is recommended by coverage.py
|
|
55
|
+
source = ["modularitypruning", "tests"]
|
|
56
|
+
concurrency = ["multiprocessing"]
|
|
57
|
+
branch = true
|
|
58
|
+
|
|
59
|
+
[tool.coverage.report]
|
|
60
|
+
fail_under = 90
|
|
61
|
+
show_missing = true
|
{modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_monolayer_parameter_estimation.py
RENAMED
|
@@ -98,6 +98,8 @@ class TestMonolayerParameterEstimation(unittest.TestCase):
|
|
|
98
98
|
G_undirected.es['weight'] = weights
|
|
99
99
|
gamma_undirected_treatment = gamma_estimate(G_undirected, membership)
|
|
100
100
|
|
|
101
|
+
assert gamma_directed is not None
|
|
102
|
+
assert gamma_undirected_treatment is not None
|
|
101
103
|
self.assertGreater(abs(gamma_directed - gamma_undirected_treatment), 1e-2)
|
|
102
104
|
|
|
103
105
|
|
{modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_multiplex_parameter_estimation.py
RENAMED
|
@@ -114,7 +114,7 @@ class TestMultiplexParameterEstimation(unittest.TestCase):
|
|
|
114
114
|
one_way_edges = [(0, 1), (2, 3), (0, 2), (0, 3), (1, 2), (1, 3)]
|
|
115
115
|
edge_list = one_way_edges + [(u + 4, v + 4) for (u, v) in one_way_edges]
|
|
116
116
|
weights = [1.0, 2.0, 3.0, 3.0, 3.0, 3.0, 1.0, 2.0, 3.0, 3.0, 3.0, 3.0]
|
|
117
|
-
membership =
|
|
117
|
+
membership = (0, 0, 1, 1, 0, 0, 1, 1)
|
|
118
118
|
layer_membership = [0, 0, 0, 0, 1, 1, 1, 1]
|
|
119
119
|
G_interlayer = ig.Graph([(0, 4), (4, 0), (1, 5), (5, 1), (2, 6), (6, 2), (3, 7), (7, 3)], directed=True)
|
|
120
120
|
|
|
@@ -128,6 +128,8 @@ class TestMultiplexParameterEstimation(unittest.TestCase):
|
|
|
128
128
|
G_undirected.es['weight'] = weights
|
|
129
129
|
gamma_undirected_treatment, _ = gamma_omega_estimate(G_undirected, G_interlayer, layer_membership,
|
|
130
130
|
membership, model='multiplex')
|
|
131
|
+
assert gamma_directed is not None
|
|
132
|
+
assert gamma_undirected_treatment is not None
|
|
131
133
|
self.assertGreater(abs(gamma_directed - gamma_undirected_treatment), 1e-2)
|
|
132
134
|
|
|
133
135
|
def test_directed_consistency_multiplex_SBM_leiden(self):
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Simple tests of main pruning functions beyond the other testing scripts.
|
|
2
|
+
|
|
3
|
+
These cover:
|
|
4
|
+
- prune_to_stable_partitions
|
|
5
|
+
- prune_to_multilayer_stable_partitions
|
|
6
|
+
"""
|
|
7
|
+
import unittest
|
|
8
|
+
from random import seed
|
|
9
|
+
|
|
10
|
+
from modularitypruning import prune_to_multilayer_stable_partitions, prune_to_stable_partitions
|
|
11
|
+
from modularitypruning.leiden_utilities import multilayer_leiden, singlelayer_leiden
|
|
12
|
+
|
|
13
|
+
from .shared_testing_functions import generate_connected_ER, generate_temporal_SBM
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestPruneToStablePartitions(unittest.TestCase):
|
|
17
|
+
def test_prune_leiden_partitions_and_membership_tuples_agree(self):
|
|
18
|
+
"""Check that passing a list of leidenalg partitions operates the same as a list of membership tuples."""
|
|
19
|
+
G = generate_connected_ER(n=50, m=200, directed=False)
|
|
20
|
+
partitions = [singlelayer_leiden(G, gamma, return_partition=True)
|
|
21
|
+
for gamma in [0.25, 0.5, 0.75, 1.0, 1.25, 1.5]]
|
|
22
|
+
membership_tuples = [tuple(part.membership) for part in partitions]
|
|
23
|
+
|
|
24
|
+
stable_from_tuples = prune_to_stable_partitions(G, membership_tuples, 0, 2)
|
|
25
|
+
stable_from_partitions = prune_to_stable_partitions(G, partitions, 0, 2)
|
|
26
|
+
|
|
27
|
+
self.assertEqual(stable_from_partitions, stable_from_tuples)
|
|
28
|
+
self.assertTrue(stable_from_tuples) # non-empty
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestPruneToMultilayerStablePartitions(unittest.TestCase):
|
|
32
|
+
def test_prune_leiden_partitions_and_membership_tuples_agree(self):
|
|
33
|
+
"""Check that passing a list of leidenalg partitions operates the same as a list of membership tuples."""
|
|
34
|
+
G_intralayer, G_interlayer, layer_vec = generate_temporal_SBM(
|
|
35
|
+
copying_probability=0.75, p_in=0.6, p_out=0.05,
|
|
36
|
+
first_layer_membership=[0] * 10 + [1] * 10, num_layers=2)
|
|
37
|
+
|
|
38
|
+
partition_runs = [multilayer_leiden(G_intralayer, G_interlayer, layer_vec, gamma, omega, return_partition=True)
|
|
39
|
+
for gamma in [0.5, 1.0, 1.5, 2.0] for omega in [0.5, 1.0, 1.5, 2.0]]
|
|
40
|
+
|
|
41
|
+
partitions = [run[0] for run in partition_runs]
|
|
42
|
+
membership_tuples = [tuple(part.membership) for part in partitions]
|
|
43
|
+
|
|
44
|
+
stable_from_tuples = prune_to_multilayer_stable_partitions(
|
|
45
|
+
G_intralayer, G_interlayer, layer_vec, 'temporal', membership_tuples, 0, 2, 0, 2)
|
|
46
|
+
stable_from_partitions = prune_to_multilayer_stable_partitions(
|
|
47
|
+
G_intralayer, G_interlayer, layer_vec, 'temporal', partitions, 0, 2, 0, 2)
|
|
48
|
+
|
|
49
|
+
self.assertEqual(stable_from_partitions, stable_from_tuples)
|
|
50
|
+
self.assertTrue(stable_from_tuples) # non-empty
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
seed(0)
|
|
55
|
+
unittest.main()
|
|
@@ -159,7 +159,7 @@ class TestTemporalAndMultilevelParameterEstimation(unittest.TestCase):
|
|
|
159
159
|
one_way_edges = [(0, 1), (2, 3), (0, 2), (0, 3), (1, 2), (1, 3)]
|
|
160
160
|
edge_list = one_way_edges + [(u + 4, v + 4) for (u, v) in one_way_edges]
|
|
161
161
|
weights = [1.0, 2.0, 3.0, 3.0, 3.0, 3.0, 1.0, 2.0, 3.0, 3.0, 3.0, 3.0]
|
|
162
|
-
membership =
|
|
162
|
+
membership = (0, 0, 1, 1, 0, 0, 1, 1)
|
|
163
163
|
layer_membership = [0, 0, 0, 0, 1, 1, 1, 1]
|
|
164
164
|
G_interlayer = ig.Graph([(0, 4), (1, 5), (2, 6), (3, 7)], directed=True)
|
|
165
165
|
|
|
@@ -174,6 +174,8 @@ class TestTemporalAndMultilevelParameterEstimation(unittest.TestCase):
|
|
|
174
174
|
G_undirected.es['weight'] = weights
|
|
175
175
|
gamma_undirected_treatment, _ = gamma_omega_estimate(G_undirected, G_interlayer, layer_membership,
|
|
176
176
|
membership, model=model)
|
|
177
|
+
assert gamma_directed is not None
|
|
178
|
+
assert gamma_undirected_treatment is not None
|
|
177
179
|
self.assertGreater(abs(gamma_directed - gamma_undirected_treatment), 1e-2)
|
|
178
180
|
|
|
179
181
|
def test_directed_consistency_temporal_SBM_leiden(self):
|
|
@@ -35,68 +35,69 @@ def get_interior_point(
|
|
|
35
35
|
# We suppress these two warnings to avoid cluttering output, some of these warnings are expected as the result is
|
|
36
36
|
# converged to and we've checked the consistency of results in our own tests. Moreover, we explicitly check the
|
|
37
37
|
# interior point's validity prior to returning.
|
|
38
|
-
warnings.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
raise ValueError(f"get_interior_point received unhandled dimension {normals.shape[1]}!")
|
|
51
|
-
|
|
52
|
-
num_retries: int = 0
|
|
53
|
-
|
|
54
|
-
while True: # retry until success
|
|
55
|
-
# randomly sample some of the halfspaces
|
|
56
|
-
sample_len: int = min(initial_num_sampled, len(interior_hs)) # len(interior_hs)
|
|
57
|
-
sampled_hs: FloatArray = np.vstack((
|
|
58
|
-
interior_hs[choice(interior_hs.shape[0], sample_len, replace=False)], boundaries))
|
|
59
|
-
|
|
60
|
-
# compute the Chebyshev center of the sampled halfspaces' intersection
|
|
61
|
-
norm_vector: FloatArray = np.reshape(
|
|
62
|
-
np.linalg.norm(sampled_hs[:, :-1], axis=1), (sampled_hs.shape[0], 1))
|
|
63
|
-
c: FloatArray = np.zeros((sampled_hs.shape[1],))
|
|
64
|
-
c[-1] = -1
|
|
65
|
-
A: FloatArray = np.hstack((sampled_hs[:, :-1], norm_vector))
|
|
66
|
-
b: FloatArray = -sampled_hs[:, -1:]
|
|
67
|
-
|
|
68
|
-
res = linprog(c, A_ub=A, b_ub=b, bounds=(-np.inf, np.inf), method='highs-ipm')
|
|
69
|
-
|
|
70
|
-
# check solution if optimization succeeded, even with difficulties
|
|
71
|
-
# res.status codes
|
|
72
|
-
# 0 : Optimization terminated successfully.
|
|
73
|
-
# 1 : Iteration limit reached.
|
|
74
|
-
# 2 : Problem appears to be infeasible.
|
|
75
|
-
# 3 : Problem appears to be unbounded.
|
|
76
|
-
# 4 : Numerical difficulties encountered.
|
|
77
|
-
if res.success or res.status in {0, 1, 4}:
|
|
78
|
-
if res.x is None:
|
|
79
|
-
# satisfying mypy, this should never occur
|
|
80
|
-
raise ValueError(f"Scipy's linprog claimed success but returned no solution! {res}")
|
|
81
|
-
intpt: FloatArray = res.x[:-1] # res.x contains [interior_point, distance to enclosing polyhedron]
|
|
82
|
-
|
|
83
|
-
# ensure that the computed point is actually interior to all halfspaces
|
|
84
|
-
if (np.dot(normals, intpt) + np.transpose(offsets) < 0).all():
|
|
85
|
-
break
|
|
86
|
-
elif res.status in {2, 3}:
|
|
87
|
-
# if we ever fail, the linear program seems impossible
|
|
88
|
-
raise ValueError("get_interior_point problem is impossible or degenerate!")
|
|
89
|
-
|
|
90
|
-
if num_retries > full_retry_limit:
|
|
91
|
-
raise ValueError("get_interior_point is unable to find a well-conditioned solution, "
|
|
92
|
-
"but the problem does not appear impossible or degenerate?")
|
|
93
|
-
|
|
94
|
-
if initial_num_sampled >= len(interior_hs):
|
|
95
|
-
num_retries += 1
|
|
38
|
+
with warnings.catch_warnings(): # revert our filters once we're done
|
|
39
|
+
warnings.filterwarnings("ignore", category=LinAlgWarning)
|
|
40
|
+
warnings.filterwarnings("ignore", category=OptimizeWarning)
|
|
41
|
+
|
|
42
|
+
normals, offsets = np.split(halfspaces, [-1], axis=1)
|
|
43
|
+
|
|
44
|
+
# in our singlelayer case, the last four halfspaces are boundary halfspaces (a square)
|
|
45
|
+
# for multilayer, the last six are boundaries (a cube)
|
|
46
|
+
if normals.shape[1] == 2:
|
|
47
|
+
interior_hs, boundaries = np.split(halfspaces, [-4], axis=0)
|
|
48
|
+
elif normals.shape[1] == 3:
|
|
49
|
+
interior_hs, boundaries = np.split(halfspaces, [-6], axis=0)
|
|
96
50
|
else:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
51
|
+
raise ValueError(f"get_interior_point received unhandled dimension {normals.shape[1]}!")
|
|
52
|
+
|
|
53
|
+
num_retries: int = 0
|
|
54
|
+
|
|
55
|
+
while True: # retry until success
|
|
56
|
+
# randomly sample some of the halfspaces
|
|
57
|
+
sample_len: int = min(initial_num_sampled, len(interior_hs)) # len(interior_hs)
|
|
58
|
+
sampled_hs: FloatArray = np.vstack((
|
|
59
|
+
interior_hs[choice(interior_hs.shape[0], sample_len, replace=False)], boundaries))
|
|
60
|
+
|
|
61
|
+
# compute the Chebyshev center of the sampled halfspaces' intersection
|
|
62
|
+
norm_vector: FloatArray = np.reshape(
|
|
63
|
+
np.linalg.norm(sampled_hs[:, :-1], axis=1), (sampled_hs.shape[0], 1))
|
|
64
|
+
c: FloatArray = np.zeros((sampled_hs.shape[1],))
|
|
65
|
+
c[-1] = -1
|
|
66
|
+
A: FloatArray = np.hstack((sampled_hs[:, :-1], norm_vector))
|
|
67
|
+
b: FloatArray = -sampled_hs[:, -1:]
|
|
68
|
+
|
|
69
|
+
res = linprog(c, A_ub=A, b_ub=b, bounds=(-np.inf, np.inf), method='highs-ipm')
|
|
70
|
+
|
|
71
|
+
# check solution if optimization succeeded, even with difficulties
|
|
72
|
+
# res.status codes
|
|
73
|
+
# 0 : Optimization terminated successfully.
|
|
74
|
+
# 1 : Iteration limit reached.
|
|
75
|
+
# 2 : Problem appears to be infeasible.
|
|
76
|
+
# 3 : Problem appears to be unbounded.
|
|
77
|
+
# 4 : Numerical difficulties encountered.
|
|
78
|
+
if res.success or res.status in {0, 1, 4}:
|
|
79
|
+
if res.x is None:
|
|
80
|
+
# satisfying mypy, this should never occur
|
|
81
|
+
raise ValueError(f"Scipy's linprog claimed success but returned no solution! {res}")
|
|
82
|
+
intpt: FloatArray = res.x[:-1] # res.x contains [interior_point, distance to enclosing polyhedron]
|
|
83
|
+
|
|
84
|
+
# ensure that the computed point is actually interior to all halfspaces
|
|
85
|
+
if (np.dot(normals, intpt) + np.transpose(offsets) < 0).all():
|
|
86
|
+
break
|
|
87
|
+
elif res.status in {2, 3}:
|
|
88
|
+
# if we ever fail, the linear program seems impossible
|
|
89
|
+
raise ValueError("get_interior_point problem is impossible or degenerate!")
|
|
90
|
+
|
|
91
|
+
if num_retries > full_retry_limit:
|
|
92
|
+
raise ValueError("get_interior_point is unable to find a well-conditioned solution, "
|
|
93
|
+
"but the problem does not appear impossible or degenerate?")
|
|
94
|
+
|
|
95
|
+
if initial_num_sampled >= len(interior_hs):
|
|
96
|
+
num_retries += 1
|
|
97
|
+
else:
|
|
98
|
+
initial_num_sampled *= 2 # try again and sample more halfspaces this time
|
|
99
|
+
|
|
100
|
+
return intpt
|
|
100
101
|
|
|
101
102
|
|
|
102
103
|
def CHAMP_2D(
|
{modularitypruning-2.0.0 → modularitypruning-2.0.2}/utilities/parameter_estimation_utilities.py
RENAMED
|
@@ -13,7 +13,7 @@ from .partition_utilities import num_communities
|
|
|
13
13
|
from .types import (Domain, DomainWithEstimates, FloatArray, GammaEstimate,
|
|
14
14
|
GammaEstimateValue, GammaOmegaEstimate, GammaRange, GammaUpdateFunc, LayerVector,
|
|
15
15
|
LeidenPartition, Membership, MultilayerSBMParameters, OmegaUpdateFunc,
|
|
16
|
-
PersistenceFunc, SinglelayerSBMParameters)
|
|
16
|
+
PersistenceFunc, PrunablePartitions, SinglelayerSBMParameters)
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def estimate_singlelayer_SBM_parameters(
|
|
@@ -353,7 +353,7 @@ def omega_function_from_model(
|
|
|
353
353
|
:type model: str
|
|
354
354
|
:param omega_max: maximum allowed value for omega
|
|
355
355
|
:type omega_max: float
|
|
356
|
-
:param T: number of layers in input graph
|
|
356
|
+
:param T: number of layers in input graph
|
|
357
357
|
:type T: int
|
|
358
358
|
:return: an "update_omega" function
|
|
359
359
|
:rtype: function
|
|
@@ -575,7 +575,6 @@ def gamma_omega_estimates_to_stable_partitions(
|
|
|
575
575
|
|
|
576
576
|
for polyverts, membership, gamma_est, omega_est in domains_with_estimates:
|
|
577
577
|
if gamma_est is None or omega_est is None:
|
|
578
|
-
print(gamma_est, omega_est)
|
|
579
578
|
continue
|
|
580
579
|
|
|
581
580
|
centroid_x: float = np.mean([x[0] for x in polyverts])
|
|
@@ -604,7 +603,7 @@ def gamma_omega_estimates_to_stable_partitions(
|
|
|
604
603
|
|
|
605
604
|
|
|
606
605
|
def prune_to_stable_partitions(
|
|
607
|
-
G: ig.Graph, parts:
|
|
606
|
+
G: ig.Graph, parts: PrunablePartitions,
|
|
608
607
|
gamma_start: float, gamma_end: float,
|
|
609
608
|
restrict_num_communities: int | None = None,
|
|
610
609
|
single_threaded: bool = False
|
|
@@ -620,7 +619,7 @@ def prune_to_stable_partitions(
|
|
|
620
619
|
:param G: graph of interest
|
|
621
620
|
:type G: igraph.Graph
|
|
622
621
|
:param parts: partitions to prune
|
|
623
|
-
:type parts: set
|
|
622
|
+
:type parts: list or set of membership tuples or leidenalg.RBConfigurationVertexPartitions
|
|
624
623
|
:param gamma_start: starting gamma value for CHAMP
|
|
625
624
|
:type gamma_start: float
|
|
626
625
|
:param gamma_end: ending gamma value for CHAMP
|
|
@@ -633,7 +632,7 @@ def prune_to_stable_partitions(
|
|
|
633
632
|
:rtype: list[tuple[int, ...]]
|
|
634
633
|
"""
|
|
635
634
|
|
|
636
|
-
if not G.is_connected():
|
|
635
|
+
if not G.is_connected(mode='weak'):
|
|
637
636
|
warnings.warn("The pruning pipeline has not been thoroughly tested on disconnected graphs. If you run into "
|
|
638
637
|
"problems, consider using the largest connected component of your graph.")
|
|
639
638
|
|
|
@@ -643,12 +642,15 @@ def prune_to_stable_partitions(
|
|
|
643
642
|
if sum(G.es['weight']) <= 0:
|
|
644
643
|
raise ValueError("Total edge weight must be positive.")
|
|
645
644
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
645
|
+
canonical_parts: set[Membership] = set()
|
|
646
|
+
for part in parts:
|
|
647
|
+
if isinstance(part, leidenalg.RBConfigurationVertexPartition):
|
|
648
|
+
# convert to (canonically represented) membership vectors if necessary
|
|
649
|
+
canonical_parts.add(sorted_tuple(tuple(part.membership)))
|
|
650
|
+
else:
|
|
651
|
+
# assume parts contains membership vectors
|
|
652
|
+
canonical_parts.add(sorted_tuple(part))
|
|
653
|
+
parts = canonical_parts
|
|
652
654
|
|
|
653
655
|
if restrict_num_communities is not None:
|
|
654
656
|
parts = {part for part in parts if num_communities(part) == restrict_num_communities}
|
|
@@ -668,7 +670,7 @@ def prune_to_stable_partitions(
|
|
|
668
670
|
def prune_to_multilayer_stable_partitions(
|
|
669
671
|
G_intralayer: ig.Graph, G_interlayer: ig.Graph,
|
|
670
672
|
layer_vec: LayerVector, model: str,
|
|
671
|
-
parts:
|
|
673
|
+
parts: PrunablePartitions, gamma_start: float, gamma_end: float,
|
|
672
674
|
omega_start: float, omega_end: float,
|
|
673
675
|
restrict_num_communities: int | None = None, single_threaded: bool = False
|
|
674
676
|
) -> list[Membership]:
|
|
@@ -713,7 +715,7 @@ def prune_to_multilayer_stable_partitions(
|
|
|
713
715
|
:param model: network layer topology (temporal, multilevel, multiplex)
|
|
714
716
|
:type model: str
|
|
715
717
|
:param parts: partitions to prune
|
|
716
|
-
:type parts: set
|
|
718
|
+
:type parts: list or set of membership tuples or leidenalg.RBConfigurationVertexPartitions
|
|
717
719
|
:param gamma_start: starting gamma value for CHAMP
|
|
718
720
|
:type gamma_start: float
|
|
719
721
|
:param gamma_end: ending gamma value for CHAMP
|
|
@@ -736,7 +738,7 @@ def prune_to_multilayer_stable_partitions(
|
|
|
736
738
|
# check graph is connected (cannot just check intra/interlayer graphs since they will both be disconnected)
|
|
737
739
|
G_combined: ig.Graph = ig.Graph(edges=[(e.source, e.target) for e in G_intralayer.es] +
|
|
738
740
|
[(e.source, e.target) for e in G_interlayer.es])
|
|
739
|
-
if not G_combined.is_connected() or G_combined.vcount() != G_interlayer.vcount():
|
|
741
|
+
if not G_combined.is_connected(mode='weak') or G_combined.vcount() != G_interlayer.vcount():
|
|
740
742
|
warnings.warn("The pruning pipeline has not been thoroughly tested on disconnected graphs. If you run into "
|
|
741
743
|
"problems, consider using the largest connected component of your graph.")
|
|
742
744
|
|
|
@@ -751,12 +753,15 @@ def prune_to_multilayer_stable_partitions(
|
|
|
751
753
|
warnings.warn("Parameter estimation treats interlayer edges as unweighted and will proceed as though all "
|
|
752
754
|
"interlayer weights are 1.0, but CHAMP will still use the actual interlayer weights!")
|
|
753
755
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
756
|
+
canonical_parts: set[Membership] = set()
|
|
757
|
+
for part in parts:
|
|
758
|
+
if isinstance(part, leidenalg.RBConfigurationVertexPartition):
|
|
759
|
+
# convert to (canonically represented) membership vectors if necessary
|
|
760
|
+
canonical_parts.add(sorted_tuple(tuple(part.membership)))
|
|
761
|
+
else:
|
|
762
|
+
# assume parts contains membership vectors
|
|
763
|
+
canonical_parts.add(sorted_tuple(part))
|
|
764
|
+
parts = canonical_parts
|
|
760
765
|
|
|
761
766
|
if restrict_num_communities is not None:
|
|
762
767
|
parts = {part for part in parts if num_communities(part) == restrict_num_communities}
|
|
@@ -89,7 +89,7 @@ def plot_2d_domains(
|
|
|
89
89
|
) -> None:
|
|
90
90
|
"""Plot partition dominance ranges in the (gamma, omega) plane, using the domains from CHAMP_3D.
|
|
91
91
|
|
|
92
|
-
Limits output to xlim and ylim dimensions. Note that the plotting here has x=
|
|
92
|
+
Limits output to xlim and ylim dimensions. Note that the plotting here has x=omega and y=gamma by default.
|
|
93
93
|
|
|
94
94
|
:param domains: CHAMP domains as returned from :meth:`~modularitypruning.champ_utilities.CHAMP_3D`.
|
|
95
95
|
:param xlim: plotting x limits
|
|
@@ -16,6 +16,7 @@ IntSequence = Sequence[int] | IntArray
|
|
|
16
16
|
FloatSequence = Sequence[float] | FloatArray
|
|
17
17
|
|
|
18
18
|
# community membership vector and layer membership vector
|
|
19
|
+
# note: some parts of the code do actually support list[int] for membership, but this is simpler and more consistent
|
|
19
20
|
Membership = tuple[int, ...]
|
|
20
21
|
LayerVector = list[int]
|
|
21
22
|
LayerTuple = tuple[int, ...] # needed for hashing
|
|
@@ -29,6 +30,9 @@ MultilayerLeidenParts: TypeAlias = tuple[list[LeidenPartition], leidenalg.CPMVer
|
|
|
29
30
|
SingleLayerLeidenResult: TypeAlias = LeidenPartition | Membership
|
|
30
31
|
MultilayerLeidenResult: TypeAlias = list[LeidenPartition] | Membership
|
|
31
32
|
|
|
33
|
+
# collections of partitions for pruning (membership vectors or leiden partitions)
|
|
34
|
+
PrunablePartitions = Sequence[LeidenPartition | Membership] | set[LeidenPartition | Membership]
|
|
35
|
+
|
|
32
36
|
# CHAMP 2D domains and parameter estimates
|
|
33
37
|
GammaEstimateValue = np.float64 | None
|
|
34
38
|
GammaEstimate = tuple[np.float64, np.float64, Membership, GammaEstimateValue]
|
|
File without changes
|
{modularitypruning-2.0.0 → modularitypruning-2.0.2}/modularitypruning.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{modularitypruning-2.0.0 → modularitypruning-2.0.2}/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
|
|
File without changes
|
{modularitypruning-2.0.0 → modularitypruning-2.0.2}/tests/test_parallel_leiden_performance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|