modularitypruning 1.5.1__tar.gz → 2.0.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.5.1 → modularitypruning-2.0.0}/PKG-INFO +21 -10
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/README.md +18 -7
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/modularitypruning.egg-info/PKG-INFO +21 -10
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/modularitypruning.egg-info/SOURCES.txt +2 -1
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/pyproject.toml +7 -4
- modularitypruning-2.0.0/tests/test_champ_coefficients_2D.py +122 -0
- modularitypruning-2.0.0/tests/test_champ_coefficients_3D.py +177 -0
- modularitypruning-2.0.0/tests/test_champ_usage_2D.py +125 -0
- modularitypruning-2.0.0/tests/test_champ_usage_3D.py +184 -0
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/tests/test_documentation_examples.py +1 -1
- modularitypruning-2.0.0/tests/test_monolayer_parameter_estimation.py +106 -0
- modularitypruning-2.0.0/tests/test_multiplex_parameter_estimation.py +161 -0
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/tests/test_parallel_leiden_performance.py +10 -11
- modularitypruning-2.0.0/tests/test_temporal_multilevel_parameter_estimation.py +220 -0
- modularitypruning-2.0.0/utilities/__init__.py +7 -0
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/utilities/champ_utilities.py +136 -97
- modularitypruning-2.0.0/utilities/leiden_utilities.py +338 -0
- modularitypruning-2.0.0/utilities/louvain_utilities.py +25 -0
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/utilities/parameter_estimation.py +92 -66
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/utilities/parameter_estimation_utilities.py +286 -126
- modularitypruning-2.0.0/utilities/partition_utilities.py +61 -0
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/utilities/plotting.py +78 -48
- modularitypruning-2.0.0/utilities/py.typed +0 -0
- modularitypruning-2.0.0/utilities/types.py +62 -0
- modularitypruning-1.5.1/tests/test_champ_coefficients_2D.py +0 -89
- modularitypruning-1.5.1/tests/test_champ_coefficients_3D.py +0 -126
- modularitypruning-1.5.1/tests/test_champ_usage_2D.py +0 -100
- modularitypruning-1.5.1/tests/test_champ_usage_3D.py +0 -147
- modularitypruning-1.5.1/tests/test_monolayer_parameter_estimation.py +0 -56
- modularitypruning-1.5.1/tests/test_multiplex_parameter_estimation.py +0 -112
- modularitypruning-1.5.1/tests/test_temporal_multilevel_parameter_estimation.py +0 -163
- modularitypruning-1.5.1/utilities/__init__.py +0 -2
- modularitypruning-1.5.1/utilities/leiden_utilities.py +0 -255
- modularitypruning-1.5.1/utilities/louvain_utilities.py +0 -185
- modularitypruning-1.5.1/utilities/partition_utilities.py +0 -43
- modularitypruning-1.5.1/utilities/progress.py +0 -31
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/LICENSE +0 -0
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/modularitypruning.egg-info/dependency_links.txt +0 -0
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/modularitypruning.egg-info/requires.txt +0 -0
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/modularitypruning.egg-info/top_level.txt +0 -0
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/setup.cfg +0 -0
- {modularitypruning-1.5.1 → modularitypruning-2.0.0}/tests/test_deprecated_louvain_names.py +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modularitypruning
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.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
|
Author-email: Ryan Gibson <ryan.alex.gibson@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Classifier: Development Status :: 5 - Production/Stable
|
|
8
8
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -15,7 +14,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.15
|
|
17
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
-
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
21
|
Requires-Dist: leidenalg
|
|
@@ -35,17 +35,23 @@ Dynamic: license-file
|
|
|
35
35
|
|
|
36
36
|
ModularityPruning is a pruning tool to identify small subsets of network
|
|
37
37
|
partitions that are significant from the perspective of stochastic block model
|
|
38
|
-
inference.
|
|
39
|
-
|
|
38
|
+
inference. It:
|
|
39
|
+
|
|
40
|
+
- Works for single-layer and multi-layer networks
|
|
41
|
+
- Supports both directed and undirected networks
|
|
42
|
+
- Supports weighted networks via the `weight` edge attribute
|
|
43
|
+
- Restricts focus to a fixed number of communities when desired
|
|
44
|
+
- Contains a faster and more robust implementation of the CHAMP (Convex Hull of
|
|
45
|
+
Admissible Modularity Partitions) algorithm (see `CHAMP_2D` and `CHAMP_3D`)
|
|
40
46
|
|
|
41
47
|
See the [documentation](https://modularitypruning.readthedocs.io/en/latest/)
|
|
42
48
|
or the journal article at https://doi.org/10.1038/s41598-022-20142-6 for more
|
|
43
49
|
information.
|
|
44
50
|
|
|
45
51
|
Significantly more details can be found in the article's
|
|
46
|
-
[Supplementary Information](
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
[Supplementary Information](https://static-content.springer.com/esm/art%3A10.1038%2Fs41598-022-20142-6/MediaObjects/41598_2022_20142_MOESM1_ESM.pdf).
|
|
53
|
+
|
|
54
|
+
Note: support for weighted and directed networks is an extension beyond the original publication.
|
|
49
55
|
|
|
50
56
|
## Installation
|
|
51
57
|
|
|
@@ -60,7 +66,12 @@ Alternatively, you can install it from this repository directly:
|
|
|
60
66
|
|
|
61
67
|
git clone https://github.com/ragibson/ModularityPruning
|
|
62
68
|
cd ModularityPruning
|
|
63
|
-
|
|
69
|
+
pip install .
|
|
70
|
+
|
|
71
|
+
For modularitypruning development, it is best to use a strict editable install
|
|
72
|
+
so that `pytest` and `mypy` can pick up changes immediately.
|
|
73
|
+
|
|
74
|
+
pip install --editable . --config-settings editable_mode=strict
|
|
64
75
|
|
|
65
76
|
<a name = "Basic Usage"></a>
|
|
66
77
|
|
|
@@ -84,7 +95,7 @@ partitions = repeated_leiden_from_gammas(G, np.linspace(0, 2, 1000))
|
|
|
84
95
|
# prune to the stable partitions from gamma=0 to gamma=2
|
|
85
96
|
stable_partitions = prune_to_stable_partitions(G, partitions, 0, 2)
|
|
86
97
|
print(stable_partitions)
|
|
87
|
-
```
|
|
98
|
+
```
|
|
88
99
|
|
|
89
100
|
This prints
|
|
90
101
|
|
|
@@ -4,17 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
ModularityPruning is a pruning tool to identify small subsets of network
|
|
6
6
|
partitions that are significant from the perspective of stochastic block model
|
|
7
|
-
inference.
|
|
8
|
-
|
|
7
|
+
inference. It:
|
|
8
|
+
|
|
9
|
+
- Works for single-layer and multi-layer networks
|
|
10
|
+
- Supports both directed and undirected networks
|
|
11
|
+
- Supports weighted networks via the `weight` edge attribute
|
|
12
|
+
- Restricts focus to a fixed number of communities when desired
|
|
13
|
+
- Contains a faster and more robust implementation of the CHAMP (Convex Hull of
|
|
14
|
+
Admissible Modularity Partitions) algorithm (see `CHAMP_2D` and `CHAMP_3D`)
|
|
9
15
|
|
|
10
16
|
See the [documentation](https://modularitypruning.readthedocs.io/en/latest/)
|
|
11
17
|
or the journal article at https://doi.org/10.1038/s41598-022-20142-6 for more
|
|
12
18
|
information.
|
|
13
19
|
|
|
14
20
|
Significantly more details can be found in the article's
|
|
15
|
-
[Supplementary Information](
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
[Supplementary Information](https://static-content.springer.com/esm/art%3A10.1038%2Fs41598-022-20142-6/MediaObjects/41598_2022_20142_MOESM1_ESM.pdf).
|
|
22
|
+
|
|
23
|
+
Note: support for weighted and directed networks is an extension beyond the original publication.
|
|
18
24
|
|
|
19
25
|
## Installation
|
|
20
26
|
|
|
@@ -29,7 +35,12 @@ Alternatively, you can install it from this repository directly:
|
|
|
29
35
|
|
|
30
36
|
git clone https://github.com/ragibson/ModularityPruning
|
|
31
37
|
cd ModularityPruning
|
|
32
|
-
|
|
38
|
+
pip install .
|
|
39
|
+
|
|
40
|
+
For modularitypruning development, it is best to use a strict editable install
|
|
41
|
+
so that `pytest` and `mypy` can pick up changes immediately.
|
|
42
|
+
|
|
43
|
+
pip install --editable . --config-settings editable_mode=strict
|
|
33
44
|
|
|
34
45
|
<a name = "Basic Usage"></a>
|
|
35
46
|
|
|
@@ -53,7 +64,7 @@ partitions = repeated_leiden_from_gammas(G, np.linspace(0, 2, 1000))
|
|
|
53
64
|
# prune to the stable partitions from gamma=0 to gamma=2
|
|
54
65
|
stable_partitions = prune_to_stable_partitions(G, partitions, 0, 2)
|
|
55
66
|
print(stable_partitions)
|
|
56
|
-
```
|
|
67
|
+
```
|
|
57
68
|
|
|
58
69
|
This prints
|
|
59
70
|
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modularitypruning
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.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
|
Author-email: Ryan Gibson <ryan.alex.gibson@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Classifier: Development Status :: 5 - Production/Stable
|
|
8
8
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -15,7 +14,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.15
|
|
17
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
-
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
21
|
Requires-Dist: leidenalg
|
|
@@ -35,17 +35,23 @@ Dynamic: license-file
|
|
|
35
35
|
|
|
36
36
|
ModularityPruning is a pruning tool to identify small subsets of network
|
|
37
37
|
partitions that are significant from the perspective of stochastic block model
|
|
38
|
-
inference.
|
|
39
|
-
|
|
38
|
+
inference. It:
|
|
39
|
+
|
|
40
|
+
- Works for single-layer and multi-layer networks
|
|
41
|
+
- Supports both directed and undirected networks
|
|
42
|
+
- Supports weighted networks via the `weight` edge attribute
|
|
43
|
+
- Restricts focus to a fixed number of communities when desired
|
|
44
|
+
- Contains a faster and more robust implementation of the CHAMP (Convex Hull of
|
|
45
|
+
Admissible Modularity Partitions) algorithm (see `CHAMP_2D` and `CHAMP_3D`)
|
|
40
46
|
|
|
41
47
|
See the [documentation](https://modularitypruning.readthedocs.io/en/latest/)
|
|
42
48
|
or the journal article at https://doi.org/10.1038/s41598-022-20142-6 for more
|
|
43
49
|
information.
|
|
44
50
|
|
|
45
51
|
Significantly more details can be found in the article's
|
|
46
|
-
[Supplementary Information](
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
[Supplementary Information](https://static-content.springer.com/esm/art%3A10.1038%2Fs41598-022-20142-6/MediaObjects/41598_2022_20142_MOESM1_ESM.pdf).
|
|
53
|
+
|
|
54
|
+
Note: support for weighted and directed networks is an extension beyond the original publication.
|
|
49
55
|
|
|
50
56
|
## Installation
|
|
51
57
|
|
|
@@ -60,7 +66,12 @@ Alternatively, you can install it from this repository directly:
|
|
|
60
66
|
|
|
61
67
|
git clone https://github.com/ragibson/ModularityPruning
|
|
62
68
|
cd ModularityPruning
|
|
63
|
-
|
|
69
|
+
pip install .
|
|
70
|
+
|
|
71
|
+
For modularitypruning development, it is best to use a strict editable install
|
|
72
|
+
so that `pytest` and `mypy` can pick up changes immediately.
|
|
73
|
+
|
|
74
|
+
pip install --editable . --config-settings editable_mode=strict
|
|
64
75
|
|
|
65
76
|
<a name = "Basic Usage"></a>
|
|
66
77
|
|
|
@@ -84,7 +95,7 @@ partitions = repeated_leiden_from_gammas(G, np.linspace(0, 2, 1000))
|
|
|
84
95
|
# prune to the stable partitions from gamma=0 to gamma=2
|
|
85
96
|
stable_partitions = prune_to_stable_partitions(G, partitions, 0, 2)
|
|
86
97
|
print(stable_partitions)
|
|
87
|
-
```
|
|
98
|
+
```
|
|
88
99
|
|
|
89
100
|
This prints
|
|
90
101
|
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "modularitypruning"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.0"
|
|
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
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
authors = [
|
|
13
13
|
{ name = "Ryan Gibson", email = "ryan.alex.gibson@gmail.com" }
|
|
@@ -17,14 +17,14 @@ classifiers = [
|
|
|
17
17
|
"Development Status :: 5 - Production/Stable",
|
|
18
18
|
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
|
-
"Programming Language :: Python :: 3.9",
|
|
21
20
|
"Programming Language :: Python :: 3.10",
|
|
22
21
|
"Programming Language :: Python :: 3.11",
|
|
23
22
|
"Programming Language :: Python :: 3.12",
|
|
24
23
|
"Programming Language :: Python :: 3.13",
|
|
25
24
|
"Programming Language :: Python :: 3.14",
|
|
26
25
|
"Programming Language :: Python :: 3.15",
|
|
27
|
-
"Programming Language :: Python :: 3 :: Only"
|
|
26
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
27
|
+
"Typing :: Typed"
|
|
28
28
|
]
|
|
29
29
|
dependencies = [
|
|
30
30
|
"leidenalg",
|
|
@@ -44,5 +44,8 @@ packages = ["modularitypruning"]
|
|
|
44
44
|
[tool.setuptools.package-dir]
|
|
45
45
|
modularitypruning = "utilities"
|
|
46
46
|
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
modularitypruning = ["py.typed"]
|
|
49
|
+
|
|
47
50
|
[tool.autopep8]
|
|
48
51
|
max_line_length = 120
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from random import seed
|
|
3
|
+
|
|
4
|
+
from modularitypruning.champ_utilities import partition_coefficients_2D
|
|
5
|
+
from modularitypruning.leiden_utilities import leiden_part_with_membership, repeated_leiden_from_gammas
|
|
6
|
+
|
|
7
|
+
from .shared_testing_functions import generate_connected_ER, generate_SBM, \
|
|
8
|
+
generate_random_values, generate_random_partitions, generate_igraph_famous
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestCHAMPCoefficients2D(unittest.TestCase):
|
|
12
|
+
def assert_partition_coefficient_correctness(self, G, partitions, coefficients):
|
|
13
|
+
A_hats, P_hats = coefficients
|
|
14
|
+
|
|
15
|
+
for membership, A_hat, P_hat in zip(partitions, A_hats, P_hats):
|
|
16
|
+
leiden_part = leiden_part_with_membership(G, membership)
|
|
17
|
+
|
|
18
|
+
# Q(gamma=0) = sum_{ij} A_{ij} delta(c_i, c_j) = A_hat
|
|
19
|
+
leiden_A_hat = leiden_part.quality(resolution_parameter=0)
|
|
20
|
+
|
|
21
|
+
# Q(gamma=0) - Q(gamma=1)
|
|
22
|
+
# = sum_{ij} (A_{ij} - gamma*P_{ij} - A_{ij}) delta(c_i, c_j)
|
|
23
|
+
# = sum_{ij} P_{ij} delta(c_i, c_j)
|
|
24
|
+
# = P_hat
|
|
25
|
+
leiden_P_hat = leiden_A_hat - leiden_part.quality(resolution_parameter=1)
|
|
26
|
+
|
|
27
|
+
self.assertAlmostEqual(A_hat, leiden_A_hat, places=8)
|
|
28
|
+
self.assertAlmostEqual(P_hat, leiden_P_hat, places=8)
|
|
29
|
+
|
|
30
|
+
def assert_partition_coefficient_correctness_ER(self, n=100, m=500, directed=False,
|
|
31
|
+
num_partitions=10, K_max=5, weighted=False):
|
|
32
|
+
G = generate_connected_ER(n=n, m=m, directed=directed, weighted=weighted)
|
|
33
|
+
partitions = generate_random_partitions(num_nodes=n, num_partitions=num_partitions, K_max=K_max)
|
|
34
|
+
coefficients = partition_coefficients_2D(G, partitions)
|
|
35
|
+
self.assert_partition_coefficient_correctness(G, partitions, coefficients)
|
|
36
|
+
|
|
37
|
+
def test_partition_coefficient_correctness_undirected_varying_n(self):
|
|
38
|
+
for n in [50, 100, 250, 500]:
|
|
39
|
+
for weighted in [False, True]:
|
|
40
|
+
with self.subTest(n=n, weighted=weighted):
|
|
41
|
+
self.assert_partition_coefficient_correctness_ER(n=n, m=5 * n, weighted=weighted)
|
|
42
|
+
|
|
43
|
+
def test_partition_coefficient_correctness_undirected_varying_m(self):
|
|
44
|
+
for m in [200, 500, 1000]:
|
|
45
|
+
for weighted in [False, True]:
|
|
46
|
+
with self.subTest(m=m, weighted=weighted):
|
|
47
|
+
self.assert_partition_coefficient_correctness_ER(m=m, weighted=weighted)
|
|
48
|
+
|
|
49
|
+
def test_partition_coefficient_correctness_undirected_varying_num_partitions(self):
|
|
50
|
+
for num_partitions in [10, 100, 1000]:
|
|
51
|
+
for weighted in [False, True]:
|
|
52
|
+
with self.subTest(num_partitions=num_partitions, weighted=weighted):
|
|
53
|
+
self.assert_partition_coefficient_correctness_ER(num_partitions=num_partitions,
|
|
54
|
+
weighted=weighted)
|
|
55
|
+
|
|
56
|
+
def test_partition_coefficient_correctness_undirected_varying_K_max(self):
|
|
57
|
+
for K_max in [2, 5, 10, 20]:
|
|
58
|
+
for weighted in [False, True]:
|
|
59
|
+
with self.subTest(K_max=K_max, weighted=weighted):
|
|
60
|
+
self.assert_partition_coefficient_correctness_ER(num_partitions=100, K_max=K_max,
|
|
61
|
+
weighted=weighted)
|
|
62
|
+
|
|
63
|
+
def test_partition_coefficient_correctness_directed_varying_n(self):
|
|
64
|
+
for n in [50, 100, 250, 500]:
|
|
65
|
+
for weighted in [False, True]:
|
|
66
|
+
with self.subTest(n=n, weighted=weighted):
|
|
67
|
+
self.assert_partition_coefficient_correctness_ER(n=n, m=10 * n, directed=True,
|
|
68
|
+
weighted=weighted)
|
|
69
|
+
|
|
70
|
+
def test_partition_coefficient_correctness_directed_varying_m(self):
|
|
71
|
+
for m in [400, 1000, 2000]:
|
|
72
|
+
for weighted in [False, True]:
|
|
73
|
+
with self.subTest(m=m, weighted=weighted):
|
|
74
|
+
self.assert_partition_coefficient_correctness_ER(m=m, directed=True, weighted=weighted)
|
|
75
|
+
|
|
76
|
+
def test_partition_coefficient_correctness_directed_varying_num_partitions(self):
|
|
77
|
+
for num_partitions in [10, 100, 1000]:
|
|
78
|
+
for weighted in [False, True]:
|
|
79
|
+
with self.subTest(num_partitions=num_partitions, weighted=weighted):
|
|
80
|
+
self.assert_partition_coefficient_correctness_ER(directed=True, num_partitions=num_partitions,
|
|
81
|
+
weighted=weighted)
|
|
82
|
+
|
|
83
|
+
def test_partition_coefficient_correctness_directed_varying_K_max(self):
|
|
84
|
+
for K_max in [2, 5, 10, 20]:
|
|
85
|
+
for weighted in [False, True]:
|
|
86
|
+
with self.subTest(K_max=K_max, weighted=weighted):
|
|
87
|
+
self.assert_partition_coefficient_correctness_ER(directed=True, num_partitions=100, K_max=K_max,
|
|
88
|
+
weighted=weighted)
|
|
89
|
+
|
|
90
|
+
def test_partition_coefficient_correctness_directed_SBM(self):
|
|
91
|
+
"""Test on asymmetric directed SBMs, unweighted and weighted."""
|
|
92
|
+
for q in [2, 3, 5]:
|
|
93
|
+
for weighted in [False, True]:
|
|
94
|
+
with self.subTest(q=q, weighted=weighted):
|
|
95
|
+
G, _, _ = generate_SBM(q, directed=True, weighted=weighted)
|
|
96
|
+
partitions = generate_random_partitions(num_nodes=G.vcount(), num_partitions=100, K_max=q)
|
|
97
|
+
coefficients = partition_coefficients_2D(G, partitions)
|
|
98
|
+
self.assert_partition_coefficient_correctness(G, partitions, coefficients)
|
|
99
|
+
|
|
100
|
+
def test_partition_coefficient_correctness_igraph_famous_leiden(self):
|
|
101
|
+
"""Test partition coefficient correctness on various famous graphs while obtaining partitions via Leiden.
|
|
102
|
+
|
|
103
|
+
The correctness is checked for the original undirected and (symmetric) directed variants.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
for name, G in generate_igraph_famous():
|
|
107
|
+
gammas = generate_random_values(100, start_value=0, end_value=5)
|
|
108
|
+
with self.subTest(graph=name, directed=False):
|
|
109
|
+
partitions = repeated_leiden_from_gammas(G, gammas)
|
|
110
|
+
coefficients = partition_coefficients_2D(G, partitions)
|
|
111
|
+
self.assert_partition_coefficient_correctness(G, partitions, coefficients)
|
|
112
|
+
|
|
113
|
+
G.to_directed() # check the directed version of the graph as well
|
|
114
|
+
with self.subTest(graph=name, directed=True):
|
|
115
|
+
partitions = repeated_leiden_from_gammas(G, gammas)
|
|
116
|
+
coefficients = partition_coefficients_2D(G, partitions)
|
|
117
|
+
self.assert_partition_coefficient_correctness(G, partitions, coefficients)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
seed(0)
|
|
122
|
+
unittest.main()
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from random import seed
|
|
3
|
+
|
|
4
|
+
from modularitypruning.champ_utilities import partition_coefficients_3D
|
|
5
|
+
from modularitypruning.leiden_utilities import multilayer_leiden_part_with_membership, leiden_part_with_membership
|
|
6
|
+
|
|
7
|
+
from .shared_testing_functions import generate_connected_multilayer_ER, generate_random_partitions, \
|
|
8
|
+
generate_temporal_SBM
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestCHAMPCoefficients3D(unittest.TestCase):
|
|
12
|
+
def assert_partition_coefficient_correctness(self, G_intralayer, G_interlayer, layer_membership,
|
|
13
|
+
partitions, coefficients):
|
|
14
|
+
A_hats, P_hats, C_hats = coefficients
|
|
15
|
+
|
|
16
|
+
for membership, A_hat, P_hat, C_hat in zip(partitions, A_hats, P_hats, C_hats):
|
|
17
|
+
intralayer_parts, interlayer_part = multilayer_leiden_part_with_membership(G_intralayer, G_interlayer,
|
|
18
|
+
layer_membership,
|
|
19
|
+
community_membership=membership)
|
|
20
|
+
|
|
21
|
+
# Q_intralayer(gamma=0) = sum_{ij} A_{ij} delta(c_i, c_j) = A_hat
|
|
22
|
+
leiden_A_hat = sum(p.quality(resolution_parameter=0) for p in intralayer_parts)
|
|
23
|
+
|
|
24
|
+
# Q_intralayer(gamma=0) - Q_intralayer(gamma=1)
|
|
25
|
+
# = sum_{ij} (A_{ij} - gamma*P_{ij} - A_{ij}) delta(c_i, c_j)
|
|
26
|
+
# = sum_{ij} P_{ij} delta(c_i, c_j)
|
|
27
|
+
# = P_hat
|
|
28
|
+
leiden_P_hat = leiden_A_hat - sum(p.quality(resolution_parameter=1) for p in intralayer_parts)
|
|
29
|
+
|
|
30
|
+
# Q_interlayer(omega=0)
|
|
31
|
+
# = sum_{ij} (C_{ij} - omega*P{ij}) delta(c_i, c_j)
|
|
32
|
+
# = sum_{ij} C_{ij} delta(c_i, c_j)
|
|
33
|
+
# = C_hat
|
|
34
|
+
leiden_C_hat = interlayer_part.quality(resolution_parameter=0)
|
|
35
|
+
|
|
36
|
+
self.assertAlmostEqual(A_hat, leiden_A_hat, places=8)
|
|
37
|
+
self.assertAlmostEqual(P_hat, leiden_P_hat, places=8)
|
|
38
|
+
self.assertAlmostEqual(C_hat, leiden_C_hat, places=8)
|
|
39
|
+
|
|
40
|
+
# Also test against an alternate, per-layer calculation of P_hat
|
|
41
|
+
alternate_P_hat = 0
|
|
42
|
+
for layer in set(layer_membership):
|
|
43
|
+
this_layer_indices = [i for i, l in enumerate(layer_membership) if layer == l]
|
|
44
|
+
layer_subgraph = G_intralayer.subgraph(this_layer_indices)
|
|
45
|
+
layer_community_membership = [community for i, community in enumerate(membership)
|
|
46
|
+
if layer_membership[i] == layer]
|
|
47
|
+
layer_part = leiden_part_with_membership(layer_subgraph, layer_community_membership)
|
|
48
|
+
alternate_P_hat += (layer_part.quality(resolution_parameter=0.0) -
|
|
49
|
+
layer_part.quality(resolution_parameter=1.0))
|
|
50
|
+
self.assertAlmostEqual(alternate_P_hat, P_hat, places=8)
|
|
51
|
+
|
|
52
|
+
def assert_partition_coefficient_correctness_ER(self, num_nodes_per_layer=100, m=25000, num_layers=10,
|
|
53
|
+
directed=False, num_partitions=10, K_max=10, weighted=False):
|
|
54
|
+
G_intralayer, G_interlayer, layer_membership = generate_connected_multilayer_ER(
|
|
55
|
+
num_nodes_per_layer=num_nodes_per_layer, m=m, num_layers=num_layers, directed=directed,
|
|
56
|
+
weighted=weighted)
|
|
57
|
+
partitions = generate_random_partitions(num_nodes=G_intralayer.vcount(), num_partitions=num_partitions,
|
|
58
|
+
K_max=K_max)
|
|
59
|
+
coefficients = partition_coefficients_3D(G_intralayer, G_interlayer, layer_membership, partitions)
|
|
60
|
+
self.assert_partition_coefficient_correctness(G_intralayer, G_interlayer, layer_membership, partitions,
|
|
61
|
+
coefficients)
|
|
62
|
+
|
|
63
|
+
def test_partition_coefficient_correctness_undirected_varying_num_nodes_per_layer(self):
|
|
64
|
+
for num_nodes_per_layer in [50, 100, 250, 500]:
|
|
65
|
+
for weighted in [False, True]:
|
|
66
|
+
with self.subTest(num_nodes_per_layer=num_nodes_per_layer, weighted=weighted):
|
|
67
|
+
self.assert_partition_coefficient_correctness_ER(num_nodes_per_layer=num_nodes_per_layer,
|
|
68
|
+
m=50 * num_nodes_per_layer,
|
|
69
|
+
weighted=weighted)
|
|
70
|
+
|
|
71
|
+
def test_partition_coefficient_correctness_undirected_varying_m(self):
|
|
72
|
+
for m in [5000, 10000, 15000, 20000]:
|
|
73
|
+
for weighted in [False, True]:
|
|
74
|
+
with self.subTest(m=m, weighted=weighted):
|
|
75
|
+
self.assert_partition_coefficient_correctness_ER(m=m, weighted=weighted)
|
|
76
|
+
|
|
77
|
+
def test_partition_coefficient_correctness_undirected_varying_num_layers(self):
|
|
78
|
+
for num_layers in [5, 10, 20, 30]:
|
|
79
|
+
for weighted in [False, True]:
|
|
80
|
+
with self.subTest(num_layers=num_layers, weighted=weighted):
|
|
81
|
+
self.assert_partition_coefficient_correctness_ER(num_layers=num_layers, weighted=weighted)
|
|
82
|
+
|
|
83
|
+
def test_partition_coefficient_correctness_undirected_varying_num_partitions(self):
|
|
84
|
+
for num_partitions in [5, 10, 100, 250]:
|
|
85
|
+
for weighted in [False, True]:
|
|
86
|
+
with self.subTest(num_partitions=num_partitions, weighted=weighted):
|
|
87
|
+
self.assert_partition_coefficient_correctness_ER(num_partitions=num_partitions,
|
|
88
|
+
weighted=weighted)
|
|
89
|
+
|
|
90
|
+
def test_partition_coefficient_correctness_undirected_varying_K_max(self):
|
|
91
|
+
for K_max in [2, 5, 10, 25]:
|
|
92
|
+
for weighted in [False, True]:
|
|
93
|
+
with self.subTest(K_max=K_max, weighted=weighted):
|
|
94
|
+
self.assert_partition_coefficient_correctness_ER(K_max=K_max, weighted=weighted)
|
|
95
|
+
|
|
96
|
+
def test_partition_coefficient_correctness_directed_varying_num_nodes_per_layer(self):
|
|
97
|
+
for num_nodes_per_layer in [50, 100, 250, 500]:
|
|
98
|
+
for weighted in [False, True]:
|
|
99
|
+
with self.subTest(num_nodes_per_layer=num_nodes_per_layer, weighted=weighted):
|
|
100
|
+
self.assert_partition_coefficient_correctness_ER(num_nodes_per_layer=num_nodes_per_layer,
|
|
101
|
+
m=100 * num_nodes_per_layer, directed=True,
|
|
102
|
+
weighted=weighted)
|
|
103
|
+
|
|
104
|
+
def test_partition_coefficient_correctness_directed_varying_m(self):
|
|
105
|
+
for m in [10000, 20000, 30000, 40000]:
|
|
106
|
+
for weighted in [False, True]:
|
|
107
|
+
with self.subTest(m=m, weighted=weighted):
|
|
108
|
+
self.assert_partition_coefficient_correctness_ER(m=m, directed=True, weighted=weighted)
|
|
109
|
+
|
|
110
|
+
def test_partition_coefficient_correctness_directed_varying_num_layers(self):
|
|
111
|
+
for num_layers in [5, 10, 20, 30]:
|
|
112
|
+
for weighted in [False, True]:
|
|
113
|
+
with self.subTest(num_layers=num_layers, weighted=weighted):
|
|
114
|
+
self.assert_partition_coefficient_correctness_ER(num_layers=num_layers, directed=True,
|
|
115
|
+
weighted=weighted)
|
|
116
|
+
|
|
117
|
+
def test_partition_coefficient_correctness_directed_varying_num_partitions(self):
|
|
118
|
+
for num_partitions in [5, 10, 100, 250]:
|
|
119
|
+
for weighted in [False, True]:
|
|
120
|
+
with self.subTest(num_partitions=num_partitions, weighted=weighted):
|
|
121
|
+
self.assert_partition_coefficient_correctness_ER(directed=True, num_partitions=num_partitions,
|
|
122
|
+
weighted=weighted)
|
|
123
|
+
|
|
124
|
+
def test_partition_coefficient_correctness_directed_varying_K_max(self):
|
|
125
|
+
for K_max in [2, 5, 10, 25]:
|
|
126
|
+
for weighted in [False, True]:
|
|
127
|
+
with self.subTest(K_max=K_max, weighted=weighted):
|
|
128
|
+
self.assert_partition_coefficient_correctness_ER(directed=True, K_max=K_max, weighted=weighted)
|
|
129
|
+
|
|
130
|
+
def assert_partition_coefficient_correctness_SBM(self, num_layers, weighted):
|
|
131
|
+
num_nodes_per_layer = 100
|
|
132
|
+
first_layer_membership = [i // (num_nodes_per_layer // 3) for i in range(num_nodes_per_layer)]
|
|
133
|
+
|
|
134
|
+
G_intralayer, G_interlayer, layer_membership = generate_temporal_SBM(
|
|
135
|
+
copying_probability=0.9, p_in=0.25, p_out=0.05, first_layer_membership=first_layer_membership,
|
|
136
|
+
num_layers=num_layers, weighted=weighted, directed=True)
|
|
137
|
+
|
|
138
|
+
partitions = generate_random_partitions(num_nodes=G_intralayer.vcount(), num_partitions=100, K_max=3)
|
|
139
|
+
coefficients = partition_coefficients_3D(G_intralayer, G_interlayer, layer_membership, partitions)
|
|
140
|
+
|
|
141
|
+
self.assert_partition_coefficient_correctness(G_intralayer, G_interlayer, layer_membership, partitions,
|
|
142
|
+
coefficients)
|
|
143
|
+
|
|
144
|
+
def test_partition_coefficient_correctness_directed_SBM(self):
|
|
145
|
+
"""Test on asymmetric directed SBMs, unweighted and weighted."""
|
|
146
|
+
for num_layers in [4, 5]:
|
|
147
|
+
for weighted in [False, True]:
|
|
148
|
+
with self.subTest(num_layers=num_layers, weighted=weighted):
|
|
149
|
+
self.assert_partition_coefficient_correctness_SBM(num_layers=num_layers, weighted=weighted)
|
|
150
|
+
|
|
151
|
+
def test_partition_coefficient_correctness_interleaved_directedness(self):
|
|
152
|
+
"""Test partition coefficient correctness when directedness of interlayer and intralayer edges do not match."""
|
|
153
|
+
for weighted in [False, True]:
|
|
154
|
+
# Intralayer directed edges, but interlayer undirected ones
|
|
155
|
+
with self.subTest(intralayer_directed=True, weighted=weighted):
|
|
156
|
+
G_intralayer, G_interlayer, layer_membership = generate_connected_multilayer_ER(
|
|
157
|
+
num_nodes_per_layer=100, m=5000, num_layers=10, directed=False, weighted=weighted)
|
|
158
|
+
G_intralayer.to_directed()
|
|
159
|
+
partitions = generate_random_partitions(num_nodes=G_intralayer.vcount(), num_partitions=10, K_max=10)
|
|
160
|
+
coefficients = partition_coefficients_3D(G_intralayer, G_interlayer, layer_membership, partitions)
|
|
161
|
+
self.assert_partition_coefficient_correctness(G_intralayer, G_interlayer, layer_membership, partitions,
|
|
162
|
+
coefficients)
|
|
163
|
+
|
|
164
|
+
# Interlayer directed edges, but intralayer undirected ones
|
|
165
|
+
with self.subTest(intralayer_directed=False, weighted=weighted):
|
|
166
|
+
G_intralayer, G_interlayer, layer_membership = generate_connected_multilayer_ER(
|
|
167
|
+
num_nodes_per_layer=100, m=5000, num_layers=10, directed=False, weighted=weighted)
|
|
168
|
+
G_interlayer.to_directed()
|
|
169
|
+
partitions = generate_random_partitions(num_nodes=G_intralayer.vcount(), num_partitions=10, K_max=10)
|
|
170
|
+
coefficients = partition_coefficients_3D(G_intralayer, G_interlayer, layer_membership, partitions)
|
|
171
|
+
self.assert_partition_coefficient_correctness(G_intralayer, G_interlayer, layer_membership, partitions,
|
|
172
|
+
coefficients)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
seed(0)
|
|
177
|
+
unittest.main()
|