Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +9 -0
- .venv/lib/python3.11/site-packages/OpenSSL/__pycache__/SSL.cpython-311.pyc +3 -0
- .venv/lib/python3.11/site-packages/OpenSSL/__pycache__/crypto.cpython-311.pyc +3 -0
- .venv/lib/python3.11/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-311.pyc +3 -0
- .venv/lib/python3.11/site-packages/dill/__pycache__/_dill.cpython-311.pyc +3 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/__pycache__/asyn_fluid.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__init__.py +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_asyn_fluid.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_centrality.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_divisive.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_kclique.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_kernighan_lin.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_label_propagation.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_louvain.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_lukes.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_modularity_max.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_quality.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_utils.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_asyn_fluid.py +136 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_centrality.py +85 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_divisive.py +106 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_kclique.py +91 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_kernighan_lin.py +92 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_label_propagation.py +241 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_louvain.py +264 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_lukes.py +152 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_modularity_max.py +340 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_quality.py +139 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_utils.py +26 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/__init__.py +6 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/__pycache__/connected.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/__pycache__/semiconnected.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/attracting.py +115 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/biconnected.py +394 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/connected.py +216 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/semiconnected.py +71 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/strongly_connected.py +351 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__init__.py +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/test_attracting.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/test_biconnected.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/test_connected.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/test_semiconnected.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/test_strongly_connected.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/test_weakly_connected.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/test_attracting.py +70 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/test_biconnected.py +248 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/test_connected.py +138 -0
.gitattributes
CHANGED
|
@@ -315,3 +315,12 @@ tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia/cudnn/lib/
|
|
| 315 |
.venv/lib/python3.11/site-packages/torio/lib/_torio_ffmpeg4.so filter=lfs diff=lfs merge=lfs -text
|
| 316 |
.venv/lib/python3.11/site-packages/torio/lib/libtorio_ffmpeg5.so filter=lfs diff=lfs merge=lfs -text
|
| 317 |
.venv/lib/python3.11/site-packages/torio/lib/libtorio_ffmpeg4.so filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
.venv/lib/python3.11/site-packages/torio/lib/_torio_ffmpeg4.so filter=lfs diff=lfs merge=lfs -text
|
| 316 |
.venv/lib/python3.11/site-packages/torio/lib/libtorio_ffmpeg5.so filter=lfs diff=lfs merge=lfs -text
|
| 317 |
.venv/lib/python3.11/site-packages/torio/lib/libtorio_ffmpeg4.so filter=lfs diff=lfs merge=lfs -text
|
| 318 |
+
.venv/lib/python3.11/site-packages/torio/lib/_torio_ffmpeg5.so filter=lfs diff=lfs merge=lfs -text
|
| 319 |
+
.venv/lib/python3.11/site-packages/pyparsing/__pycache__/core.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
| 320 |
+
.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/embed/setuptools-75.8.0-py3-none-any.whl filter=lfs diff=lfs merge=lfs -text
|
| 321 |
+
.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/embed/pip-24.3.1-py3-none-any.whl filter=lfs diff=lfs merge=lfs -text
|
| 322 |
+
.venv/lib/python3.11/site-packages/virtualenv/seed/wheels/embed/setuptools-75.3.0-py3-none-any.whl filter=lfs diff=lfs merge=lfs -text
|
| 323 |
+
.venv/lib/python3.11/site-packages/dill/__pycache__/_dill.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
| 324 |
+
.venv/lib/python3.11/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
| 325 |
+
.venv/lib/python3.11/site-packages/OpenSSL/__pycache__/crypto.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
| 326 |
+
.venv/lib/python3.11/site-packages/OpenSSL/__pycache__/SSL.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
.venv/lib/python3.11/site-packages/OpenSSL/__pycache__/SSL.cpython-311.pyc
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a493cb641bc908b30044a1fc6fb728bbc91c0f852e49bef13ba491d6d755cf86
|
| 3 |
+
size 134204
|
.venv/lib/python3.11/site-packages/OpenSSL/__pycache__/crypto.cpython-311.pyc
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:59ec647782b61d4ba40e7dfec9a092995869a21af2d0da0a013f4ebd9fc8a6ea
|
| 3 |
+
size 136411
|
.venv/lib/python3.11/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-311.pyc
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bd6324004edddf538ea21e765ac2acffea44f407979e9ae8fb425a6fd776b679
|
| 3 |
+
size 145998
|
.venv/lib/python3.11/site-packages/dill/__pycache__/_dill.cpython-311.pyc
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bb74df91c650121f76e6a66835e9ee767f18baf206ada57a05dbcf50297c43cf
|
| 3 |
+
size 112760
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (1.48 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/__pycache__/asyn_fluid.cpython-311.pyc
ADDED
|
Binary file (6.61 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (208 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_asyn_fluid.cpython-311.pyc
ADDED
|
Binary file (7.53 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_centrality.cpython-311.pyc
ADDED
|
Binary file (6.19 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_divisive.cpython-311.pyc
ADDED
|
Binary file (9.27 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_kclique.cpython-311.pyc
ADDED
|
Binary file (5.58 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_kernighan_lin.cpython-311.pyc
ADDED
|
Binary file (5.53 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_label_propagation.cpython-311.pyc
ADDED
|
Binary file (19.1 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_louvain.cpython-311.pyc
ADDED
|
Binary file (14.3 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_lukes.cpython-311.pyc
ADDED
|
Binary file (6.9 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_modularity_max.cpython-311.pyc
ADDED
|
Binary file (15 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_quality.cpython-311.pyc
ADDED
|
Binary file (9.56 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/__pycache__/test_utils.cpython-311.pyc
ADDED
|
Binary file (1.97 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_asyn_fluid.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx import Graph, NetworkXError
|
| 5 |
+
from networkx.algorithms.community import asyn_fluidc
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@pytest.mark.parametrize("graph_constructor", (nx.DiGraph, nx.MultiGraph))
|
| 9 |
+
def test_raises_on_directed_and_multigraphs(graph_constructor):
|
| 10 |
+
G = graph_constructor([(0, 1), (1, 2)])
|
| 11 |
+
with pytest.raises(nx.NetworkXNotImplemented):
|
| 12 |
+
nx.community.asyn_fluidc(G, 1)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def test_exceptions():
|
| 16 |
+
test = Graph()
|
| 17 |
+
test.add_node("a")
|
| 18 |
+
pytest.raises(NetworkXError, asyn_fluidc, test, "hi")
|
| 19 |
+
pytest.raises(NetworkXError, asyn_fluidc, test, -1)
|
| 20 |
+
pytest.raises(NetworkXError, asyn_fluidc, test, 3)
|
| 21 |
+
test.add_node("b")
|
| 22 |
+
pytest.raises(NetworkXError, asyn_fluidc, test, 1)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def test_single_node():
|
| 26 |
+
test = Graph()
|
| 27 |
+
|
| 28 |
+
test.add_node("a")
|
| 29 |
+
|
| 30 |
+
# ground truth
|
| 31 |
+
ground_truth = {frozenset(["a"])}
|
| 32 |
+
|
| 33 |
+
communities = asyn_fluidc(test, 1)
|
| 34 |
+
result = {frozenset(c) for c in communities}
|
| 35 |
+
assert result == ground_truth
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def test_two_nodes():
|
| 39 |
+
test = Graph()
|
| 40 |
+
|
| 41 |
+
test.add_edge("a", "b")
|
| 42 |
+
|
| 43 |
+
# ground truth
|
| 44 |
+
ground_truth = {frozenset(["a"]), frozenset(["b"])}
|
| 45 |
+
|
| 46 |
+
communities = asyn_fluidc(test, 2)
|
| 47 |
+
result = {frozenset(c) for c in communities}
|
| 48 |
+
assert result == ground_truth
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def test_two_clique_communities():
|
| 52 |
+
test = Graph()
|
| 53 |
+
|
| 54 |
+
# c1
|
| 55 |
+
test.add_edge("a", "b")
|
| 56 |
+
test.add_edge("a", "c")
|
| 57 |
+
test.add_edge("b", "c")
|
| 58 |
+
|
| 59 |
+
# connection
|
| 60 |
+
test.add_edge("c", "d")
|
| 61 |
+
|
| 62 |
+
# c2
|
| 63 |
+
test.add_edge("d", "e")
|
| 64 |
+
test.add_edge("d", "f")
|
| 65 |
+
test.add_edge("f", "e")
|
| 66 |
+
|
| 67 |
+
# ground truth
|
| 68 |
+
ground_truth = {frozenset(["a", "c", "b"]), frozenset(["e", "d", "f"])}
|
| 69 |
+
|
| 70 |
+
communities = asyn_fluidc(test, 2, seed=7)
|
| 71 |
+
result = {frozenset(c) for c in communities}
|
| 72 |
+
assert result == ground_truth
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def test_five_clique_ring():
|
| 76 |
+
test = Graph()
|
| 77 |
+
|
| 78 |
+
# c1
|
| 79 |
+
test.add_edge("1a", "1b")
|
| 80 |
+
test.add_edge("1a", "1c")
|
| 81 |
+
test.add_edge("1a", "1d")
|
| 82 |
+
test.add_edge("1b", "1c")
|
| 83 |
+
test.add_edge("1b", "1d")
|
| 84 |
+
test.add_edge("1c", "1d")
|
| 85 |
+
|
| 86 |
+
# c2
|
| 87 |
+
test.add_edge("2a", "2b")
|
| 88 |
+
test.add_edge("2a", "2c")
|
| 89 |
+
test.add_edge("2a", "2d")
|
| 90 |
+
test.add_edge("2b", "2c")
|
| 91 |
+
test.add_edge("2b", "2d")
|
| 92 |
+
test.add_edge("2c", "2d")
|
| 93 |
+
|
| 94 |
+
# c3
|
| 95 |
+
test.add_edge("3a", "3b")
|
| 96 |
+
test.add_edge("3a", "3c")
|
| 97 |
+
test.add_edge("3a", "3d")
|
| 98 |
+
test.add_edge("3b", "3c")
|
| 99 |
+
test.add_edge("3b", "3d")
|
| 100 |
+
test.add_edge("3c", "3d")
|
| 101 |
+
|
| 102 |
+
# c4
|
| 103 |
+
test.add_edge("4a", "4b")
|
| 104 |
+
test.add_edge("4a", "4c")
|
| 105 |
+
test.add_edge("4a", "4d")
|
| 106 |
+
test.add_edge("4b", "4c")
|
| 107 |
+
test.add_edge("4b", "4d")
|
| 108 |
+
test.add_edge("4c", "4d")
|
| 109 |
+
|
| 110 |
+
# c5
|
| 111 |
+
test.add_edge("5a", "5b")
|
| 112 |
+
test.add_edge("5a", "5c")
|
| 113 |
+
test.add_edge("5a", "5d")
|
| 114 |
+
test.add_edge("5b", "5c")
|
| 115 |
+
test.add_edge("5b", "5d")
|
| 116 |
+
test.add_edge("5c", "5d")
|
| 117 |
+
|
| 118 |
+
# connections
|
| 119 |
+
test.add_edge("1a", "2c")
|
| 120 |
+
test.add_edge("2a", "3c")
|
| 121 |
+
test.add_edge("3a", "4c")
|
| 122 |
+
test.add_edge("4a", "5c")
|
| 123 |
+
test.add_edge("5a", "1c")
|
| 124 |
+
|
| 125 |
+
# ground truth
|
| 126 |
+
ground_truth = {
|
| 127 |
+
frozenset(["1a", "1b", "1c", "1d"]),
|
| 128 |
+
frozenset(["2a", "2b", "2c", "2d"]),
|
| 129 |
+
frozenset(["3a", "3b", "3c", "3d"]),
|
| 130 |
+
frozenset(["4a", "4b", "4c", "4d"]),
|
| 131 |
+
frozenset(["5a", "5b", "5c", "5d"]),
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
communities = asyn_fluidc(test, 5, seed=9)
|
| 135 |
+
result = {frozenset(c) for c in communities}
|
| 136 |
+
assert result == ground_truth
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_centrality.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit tests for the :mod:`networkx.algorithms.community.centrality`
|
| 2 |
+
module.
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from operator import itemgetter
|
| 7 |
+
|
| 8 |
+
import networkx as nx
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def set_of_sets(iterable):
|
| 12 |
+
return set(map(frozenset, iterable))
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def validate_communities(result, expected):
|
| 16 |
+
assert set_of_sets(result) == set_of_sets(expected)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def validate_possible_communities(result, *expected):
|
| 20 |
+
assert any(set_of_sets(result) == set_of_sets(p) for p in expected)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class TestGirvanNewman:
|
| 24 |
+
"""Unit tests for the
|
| 25 |
+
:func:`networkx.algorithms.community.centrality.girvan_newman`
|
| 26 |
+
function.
|
| 27 |
+
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def test_no_edges(self):
|
| 31 |
+
G = nx.empty_graph(3)
|
| 32 |
+
communities = list(nx.community.girvan_newman(G))
|
| 33 |
+
assert len(communities) == 1
|
| 34 |
+
validate_communities(communities[0], [{0}, {1}, {2}])
|
| 35 |
+
|
| 36 |
+
def test_undirected(self):
|
| 37 |
+
# Start with the graph .-.-.-.
|
| 38 |
+
G = nx.path_graph(4)
|
| 39 |
+
communities = list(nx.community.girvan_newman(G))
|
| 40 |
+
assert len(communities) == 3
|
| 41 |
+
# After one removal, we get the graph .-. .-.
|
| 42 |
+
validate_communities(communities[0], [{0, 1}, {2, 3}])
|
| 43 |
+
# After the next, we get the graph .-. . ., but there are two
|
| 44 |
+
# symmetric possible versions.
|
| 45 |
+
validate_possible_communities(
|
| 46 |
+
communities[1], [{0}, {1}, {2, 3}], [{0, 1}, {2}, {3}]
|
| 47 |
+
)
|
| 48 |
+
# After the last removal, we always get the empty graph.
|
| 49 |
+
validate_communities(communities[2], [{0}, {1}, {2}, {3}])
|
| 50 |
+
|
| 51 |
+
def test_directed(self):
|
| 52 |
+
G = nx.DiGraph(nx.path_graph(4))
|
| 53 |
+
communities = list(nx.community.girvan_newman(G))
|
| 54 |
+
assert len(communities) == 3
|
| 55 |
+
validate_communities(communities[0], [{0, 1}, {2, 3}])
|
| 56 |
+
validate_possible_communities(
|
| 57 |
+
communities[1], [{0}, {1}, {2, 3}], [{0, 1}, {2}, {3}]
|
| 58 |
+
)
|
| 59 |
+
validate_communities(communities[2], [{0}, {1}, {2}, {3}])
|
| 60 |
+
|
| 61 |
+
def test_selfloops(self):
|
| 62 |
+
G = nx.path_graph(4)
|
| 63 |
+
G.add_edge(0, 0)
|
| 64 |
+
G.add_edge(2, 2)
|
| 65 |
+
communities = list(nx.community.girvan_newman(G))
|
| 66 |
+
assert len(communities) == 3
|
| 67 |
+
validate_communities(communities[0], [{0, 1}, {2, 3}])
|
| 68 |
+
validate_possible_communities(
|
| 69 |
+
communities[1], [{0}, {1}, {2, 3}], [{0, 1}, {2}, {3}]
|
| 70 |
+
)
|
| 71 |
+
validate_communities(communities[2], [{0}, {1}, {2}, {3}])
|
| 72 |
+
|
| 73 |
+
def test_most_valuable_edge(self):
|
| 74 |
+
G = nx.Graph()
|
| 75 |
+
G.add_weighted_edges_from([(0, 1, 3), (1, 2, 2), (2, 3, 1)])
|
| 76 |
+
# Let the most valuable edge be the one with the highest weight.
|
| 77 |
+
|
| 78 |
+
def heaviest(G):
|
| 79 |
+
return max(G.edges(data="weight"), key=itemgetter(2))[:2]
|
| 80 |
+
|
| 81 |
+
communities = list(nx.community.girvan_newman(G, heaviest))
|
| 82 |
+
assert len(communities) == 3
|
| 83 |
+
validate_communities(communities[0], [{0}, {1, 2, 3}])
|
| 84 |
+
validate_communities(communities[1], [{0}, {1}, {2, 3}])
|
| 85 |
+
validate_communities(communities[2], [{0}, {1}, {2}, {3}])
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_divisive.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def test_edge_betweenness_partition():
|
| 7 |
+
G = nx.barbell_graph(3, 0)
|
| 8 |
+
C = nx.community.edge_betweenness_partition(G, 2)
|
| 9 |
+
answer = [{0, 1, 2}, {3, 4, 5}]
|
| 10 |
+
assert len(C) == len(answer)
|
| 11 |
+
for s in answer:
|
| 12 |
+
assert s in C
|
| 13 |
+
|
| 14 |
+
G = nx.barbell_graph(3, 1)
|
| 15 |
+
C = nx.community.edge_betweenness_partition(G, 3)
|
| 16 |
+
answer = [{0, 1, 2}, {4, 5, 6}, {3}]
|
| 17 |
+
assert len(C) == len(answer)
|
| 18 |
+
for s in answer:
|
| 19 |
+
assert s in C
|
| 20 |
+
|
| 21 |
+
C = nx.community.edge_betweenness_partition(G, 7)
|
| 22 |
+
answer = [{n} for n in G]
|
| 23 |
+
assert len(C) == len(answer)
|
| 24 |
+
for s in answer:
|
| 25 |
+
assert s in C
|
| 26 |
+
|
| 27 |
+
C = nx.community.edge_betweenness_partition(G, 1)
|
| 28 |
+
assert C == [set(G)]
|
| 29 |
+
|
| 30 |
+
C = nx.community.edge_betweenness_partition(G, 1, weight="weight")
|
| 31 |
+
assert C == [set(G)]
|
| 32 |
+
|
| 33 |
+
with pytest.raises(nx.NetworkXError):
|
| 34 |
+
nx.community.edge_betweenness_partition(G, 0)
|
| 35 |
+
|
| 36 |
+
with pytest.raises(nx.NetworkXError):
|
| 37 |
+
nx.community.edge_betweenness_partition(G, -1)
|
| 38 |
+
|
| 39 |
+
with pytest.raises(nx.NetworkXError):
|
| 40 |
+
nx.community.edge_betweenness_partition(G, 10)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def test_edge_current_flow_betweenness_partition():
|
| 44 |
+
pytest.importorskip("scipy")
|
| 45 |
+
|
| 46 |
+
G = nx.barbell_graph(3, 0)
|
| 47 |
+
C = nx.community.edge_current_flow_betweenness_partition(G, 2)
|
| 48 |
+
answer = [{0, 1, 2}, {3, 4, 5}]
|
| 49 |
+
assert len(C) == len(answer)
|
| 50 |
+
for s in answer:
|
| 51 |
+
assert s in C
|
| 52 |
+
|
| 53 |
+
G = nx.barbell_graph(3, 1)
|
| 54 |
+
C = nx.community.edge_current_flow_betweenness_partition(G, 2)
|
| 55 |
+
answers = [[{0, 1, 2, 3}, {4, 5, 6}], [{0, 1, 2}, {3, 4, 5, 6}]]
|
| 56 |
+
assert len(C) == len(answers[0])
|
| 57 |
+
assert any(all(s in answer for s in C) for answer in answers)
|
| 58 |
+
|
| 59 |
+
C = nx.community.edge_current_flow_betweenness_partition(G, 3)
|
| 60 |
+
answer = [{0, 1, 2}, {4, 5, 6}, {3}]
|
| 61 |
+
assert len(C) == len(answer)
|
| 62 |
+
for s in answer:
|
| 63 |
+
assert s in C
|
| 64 |
+
|
| 65 |
+
C = nx.community.edge_current_flow_betweenness_partition(G, 4)
|
| 66 |
+
answers = [[{1, 2}, {4, 5, 6}, {3}, {0}], [{0, 1, 2}, {5, 6}, {3}, {4}]]
|
| 67 |
+
assert len(C) == len(answers[0])
|
| 68 |
+
assert any(all(s in answer for s in C) for answer in answers)
|
| 69 |
+
|
| 70 |
+
C = nx.community.edge_current_flow_betweenness_partition(G, 5)
|
| 71 |
+
answer = [{1, 2}, {5, 6}, {3}, {0}, {4}]
|
| 72 |
+
assert len(C) == len(answer)
|
| 73 |
+
for s in answer:
|
| 74 |
+
assert s in C
|
| 75 |
+
|
| 76 |
+
C = nx.community.edge_current_flow_betweenness_partition(G, 6)
|
| 77 |
+
answers = [[{2}, {5, 6}, {3}, {0}, {4}, {1}], [{1, 2}, {6}, {3}, {0}, {4}, {5}]]
|
| 78 |
+
assert len(C) == len(answers[0])
|
| 79 |
+
assert any(all(s in answer for s in C) for answer in answers)
|
| 80 |
+
|
| 81 |
+
C = nx.community.edge_current_flow_betweenness_partition(G, 7)
|
| 82 |
+
answer = [{n} for n in G]
|
| 83 |
+
assert len(C) == len(answer)
|
| 84 |
+
for s in answer:
|
| 85 |
+
assert s in C
|
| 86 |
+
|
| 87 |
+
C = nx.community.edge_current_flow_betweenness_partition(G, 1)
|
| 88 |
+
assert C == [set(G)]
|
| 89 |
+
|
| 90 |
+
C = nx.community.edge_current_flow_betweenness_partition(G, 1, weight="weight")
|
| 91 |
+
assert C == [set(G)]
|
| 92 |
+
|
| 93 |
+
with pytest.raises(nx.NetworkXError):
|
| 94 |
+
nx.community.edge_current_flow_betweenness_partition(G, 0)
|
| 95 |
+
|
| 96 |
+
with pytest.raises(nx.NetworkXError):
|
| 97 |
+
nx.community.edge_current_flow_betweenness_partition(G, -1)
|
| 98 |
+
|
| 99 |
+
with pytest.raises(nx.NetworkXError):
|
| 100 |
+
nx.community.edge_current_flow_betweenness_partition(G, 10)
|
| 101 |
+
|
| 102 |
+
N = 10
|
| 103 |
+
G = nx.empty_graph(N)
|
| 104 |
+
for i in range(2, N - 1):
|
| 105 |
+
C = nx.community.edge_current_flow_betweenness_partition(G, i)
|
| 106 |
+
assert C == [{n} for n in G]
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_kclique.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from itertools import combinations
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_overlapping_K5():
|
| 9 |
+
G = nx.Graph()
|
| 10 |
+
G.add_edges_from(combinations(range(5), 2)) # Add a five clique
|
| 11 |
+
G.add_edges_from(combinations(range(2, 7), 2)) # Add another five clique
|
| 12 |
+
c = list(nx.community.k_clique_communities(G, 4))
|
| 13 |
+
assert c == [frozenset(range(7))]
|
| 14 |
+
c = set(nx.community.k_clique_communities(G, 5))
|
| 15 |
+
assert c == {frozenset(range(5)), frozenset(range(2, 7))}
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_isolated_K5():
|
| 19 |
+
G = nx.Graph()
|
| 20 |
+
G.add_edges_from(combinations(range(5), 2)) # Add a five clique
|
| 21 |
+
G.add_edges_from(combinations(range(5, 10), 2)) # Add another five clique
|
| 22 |
+
c = set(nx.community.k_clique_communities(G, 5))
|
| 23 |
+
assert c == {frozenset(range(5)), frozenset(range(5, 10))}
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class TestZacharyKarateClub:
|
| 27 |
+
def setup_method(self):
|
| 28 |
+
self.G = nx.karate_club_graph()
|
| 29 |
+
|
| 30 |
+
def _check_communities(self, k, expected):
|
| 31 |
+
communities = set(nx.community.k_clique_communities(self.G, k))
|
| 32 |
+
assert communities == expected
|
| 33 |
+
|
| 34 |
+
def test_k2(self):
|
| 35 |
+
# clique percolation with k=2 is just connected components
|
| 36 |
+
expected = {frozenset(self.G)}
|
| 37 |
+
self._check_communities(2, expected)
|
| 38 |
+
|
| 39 |
+
def test_k3(self):
|
| 40 |
+
comm1 = [
|
| 41 |
+
0,
|
| 42 |
+
1,
|
| 43 |
+
2,
|
| 44 |
+
3,
|
| 45 |
+
7,
|
| 46 |
+
8,
|
| 47 |
+
12,
|
| 48 |
+
13,
|
| 49 |
+
14,
|
| 50 |
+
15,
|
| 51 |
+
17,
|
| 52 |
+
18,
|
| 53 |
+
19,
|
| 54 |
+
20,
|
| 55 |
+
21,
|
| 56 |
+
22,
|
| 57 |
+
23,
|
| 58 |
+
26,
|
| 59 |
+
27,
|
| 60 |
+
28,
|
| 61 |
+
29,
|
| 62 |
+
30,
|
| 63 |
+
31,
|
| 64 |
+
32,
|
| 65 |
+
33,
|
| 66 |
+
]
|
| 67 |
+
comm2 = [0, 4, 5, 6, 10, 16]
|
| 68 |
+
comm3 = [24, 25, 31]
|
| 69 |
+
expected = {frozenset(comm1), frozenset(comm2), frozenset(comm3)}
|
| 70 |
+
self._check_communities(3, expected)
|
| 71 |
+
|
| 72 |
+
def test_k4(self):
|
| 73 |
+
expected = {
|
| 74 |
+
frozenset([0, 1, 2, 3, 7, 13]),
|
| 75 |
+
frozenset([8, 32, 30, 33]),
|
| 76 |
+
frozenset([32, 33, 29, 23]),
|
| 77 |
+
}
|
| 78 |
+
self._check_communities(4, expected)
|
| 79 |
+
|
| 80 |
+
def test_k5(self):
|
| 81 |
+
expected = {frozenset([0, 1, 2, 3, 7, 13])}
|
| 82 |
+
self._check_communities(5, expected)
|
| 83 |
+
|
| 84 |
+
def test_k6(self):
|
| 85 |
+
expected = set()
|
| 86 |
+
self._check_communities(6, expected)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def test_bad_k():
|
| 90 |
+
with pytest.raises(nx.NetworkXError):
|
| 91 |
+
list(nx.community.k_clique_communities(nx.Graph(), 1))
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_kernighan_lin.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit tests for the :mod:`networkx.algorithms.community.kernighan_lin`
|
| 2 |
+
module.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from itertools import permutations
|
| 6 |
+
|
| 7 |
+
import pytest
|
| 8 |
+
|
| 9 |
+
import networkx as nx
|
| 10 |
+
from networkx.algorithms.community import kernighan_lin_bisection
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def assert_partition_equal(x, y):
|
| 14 |
+
assert set(map(frozenset, x)) == set(map(frozenset, y))
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def test_partition():
|
| 18 |
+
G = nx.barbell_graph(3, 0)
|
| 19 |
+
C = kernighan_lin_bisection(G)
|
| 20 |
+
assert_partition_equal(C, [{0, 1, 2}, {3, 4, 5}])
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def test_partition_argument():
|
| 24 |
+
G = nx.barbell_graph(3, 0)
|
| 25 |
+
partition = [{0, 1, 2}, {3, 4, 5}]
|
| 26 |
+
C = kernighan_lin_bisection(G, partition)
|
| 27 |
+
assert_partition_equal(C, partition)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def test_partition_argument_non_integer_nodes():
|
| 31 |
+
G = nx.Graph([("A", "B"), ("A", "C"), ("B", "C"), ("C", "D")])
|
| 32 |
+
partition = ({"A", "B"}, {"C", "D"})
|
| 33 |
+
C = kernighan_lin_bisection(G, partition)
|
| 34 |
+
assert_partition_equal(C, partition)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def test_seed_argument():
|
| 38 |
+
G = nx.barbell_graph(3, 0)
|
| 39 |
+
C = kernighan_lin_bisection(G, seed=1)
|
| 40 |
+
assert_partition_equal(C, [{0, 1, 2}, {3, 4, 5}])
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def test_non_disjoint_partition():
|
| 44 |
+
with pytest.raises(nx.NetworkXError):
|
| 45 |
+
G = nx.barbell_graph(3, 0)
|
| 46 |
+
partition = ({0, 1, 2}, {2, 3, 4, 5})
|
| 47 |
+
kernighan_lin_bisection(G, partition)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def test_too_many_blocks():
|
| 51 |
+
with pytest.raises(nx.NetworkXError):
|
| 52 |
+
G = nx.barbell_graph(3, 0)
|
| 53 |
+
partition = ({0, 1}, {2}, {3, 4, 5})
|
| 54 |
+
kernighan_lin_bisection(G, partition)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def test_multigraph():
|
| 58 |
+
G = nx.cycle_graph(4)
|
| 59 |
+
M = nx.MultiGraph(G.edges())
|
| 60 |
+
M.add_edges_from(G.edges())
|
| 61 |
+
M.remove_edge(1, 2)
|
| 62 |
+
for labels in permutations(range(4)):
|
| 63 |
+
mapping = dict(zip(M, labels))
|
| 64 |
+
A, B = kernighan_lin_bisection(nx.relabel_nodes(M, mapping), seed=0)
|
| 65 |
+
assert_partition_equal(
|
| 66 |
+
[A, B], [{mapping[0], mapping[1]}, {mapping[2], mapping[3]}]
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def test_max_iter_argument():
|
| 71 |
+
G = nx.Graph(
|
| 72 |
+
[
|
| 73 |
+
("A", "B", {"weight": 1}),
|
| 74 |
+
("A", "C", {"weight": 2}),
|
| 75 |
+
("A", "D", {"weight": 3}),
|
| 76 |
+
("A", "E", {"weight": 2}),
|
| 77 |
+
("A", "F", {"weight": 4}),
|
| 78 |
+
("B", "C", {"weight": 1}),
|
| 79 |
+
("B", "D", {"weight": 4}),
|
| 80 |
+
("B", "E", {"weight": 2}),
|
| 81 |
+
("B", "F", {"weight": 1}),
|
| 82 |
+
("C", "D", {"weight": 3}),
|
| 83 |
+
("C", "E", {"weight": 2}),
|
| 84 |
+
("C", "F", {"weight": 1}),
|
| 85 |
+
("D", "E", {"weight": 4}),
|
| 86 |
+
("D", "F", {"weight": 3}),
|
| 87 |
+
("E", "F", {"weight": 2}),
|
| 88 |
+
]
|
| 89 |
+
)
|
| 90 |
+
partition = ({"A", "B", "C"}, {"D", "E", "F"})
|
| 91 |
+
C = kernighan_lin_bisection(G, partition, max_iter=1)
|
| 92 |
+
assert_partition_equal(C, ({"A", "F", "C"}, {"D", "E", "B"}))
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_label_propagation.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from itertools import chain, combinations
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_directed_not_supported():
|
| 9 |
+
with pytest.raises(nx.NetworkXNotImplemented):
|
| 10 |
+
# not supported for directed graphs
|
| 11 |
+
test = nx.DiGraph()
|
| 12 |
+
test.add_edge("a", "b")
|
| 13 |
+
test.add_edge("a", "c")
|
| 14 |
+
test.add_edge("b", "d")
|
| 15 |
+
result = nx.community.label_propagation_communities(test)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_iterator_vs_iterable():
|
| 19 |
+
G = nx.empty_graph("a")
|
| 20 |
+
assert list(nx.community.label_propagation_communities(G)) == [{"a"}]
|
| 21 |
+
for community in nx.community.label_propagation_communities(G):
|
| 22 |
+
assert community == {"a"}
|
| 23 |
+
pytest.raises(TypeError, next, nx.community.label_propagation_communities(G))
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def test_one_node():
|
| 27 |
+
test = nx.Graph()
|
| 28 |
+
test.add_node("a")
|
| 29 |
+
|
| 30 |
+
# The expected communities are:
|
| 31 |
+
ground_truth = {frozenset(["a"])}
|
| 32 |
+
|
| 33 |
+
communities = nx.community.label_propagation_communities(test)
|
| 34 |
+
result = {frozenset(c) for c in communities}
|
| 35 |
+
assert result == ground_truth
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def test_unconnected_communities():
|
| 39 |
+
test = nx.Graph()
|
| 40 |
+
# community 1
|
| 41 |
+
test.add_edge("a", "c")
|
| 42 |
+
test.add_edge("a", "d")
|
| 43 |
+
test.add_edge("d", "c")
|
| 44 |
+
# community 2
|
| 45 |
+
test.add_edge("b", "e")
|
| 46 |
+
test.add_edge("e", "f")
|
| 47 |
+
test.add_edge("f", "b")
|
| 48 |
+
|
| 49 |
+
# The expected communities are:
|
| 50 |
+
ground_truth = {frozenset(["a", "c", "d"]), frozenset(["b", "e", "f"])}
|
| 51 |
+
|
| 52 |
+
communities = nx.community.label_propagation_communities(test)
|
| 53 |
+
result = {frozenset(c) for c in communities}
|
| 54 |
+
assert result == ground_truth
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def test_connected_communities():
|
| 58 |
+
test = nx.Graph()
|
| 59 |
+
# community 1
|
| 60 |
+
test.add_edge("a", "b")
|
| 61 |
+
test.add_edge("c", "a")
|
| 62 |
+
test.add_edge("c", "b")
|
| 63 |
+
test.add_edge("d", "a")
|
| 64 |
+
test.add_edge("d", "b")
|
| 65 |
+
test.add_edge("d", "c")
|
| 66 |
+
test.add_edge("e", "a")
|
| 67 |
+
test.add_edge("e", "b")
|
| 68 |
+
test.add_edge("e", "c")
|
| 69 |
+
test.add_edge("e", "d")
|
| 70 |
+
# community 2
|
| 71 |
+
test.add_edge("1", "2")
|
| 72 |
+
test.add_edge("3", "1")
|
| 73 |
+
test.add_edge("3", "2")
|
| 74 |
+
test.add_edge("4", "1")
|
| 75 |
+
test.add_edge("4", "2")
|
| 76 |
+
test.add_edge("4", "3")
|
| 77 |
+
test.add_edge("5", "1")
|
| 78 |
+
test.add_edge("5", "2")
|
| 79 |
+
test.add_edge("5", "3")
|
| 80 |
+
test.add_edge("5", "4")
|
| 81 |
+
# edge between community 1 and 2
|
| 82 |
+
test.add_edge("a", "1")
|
| 83 |
+
# community 3
|
| 84 |
+
test.add_edge("x", "y")
|
| 85 |
+
# community 4 with only a single node
|
| 86 |
+
test.add_node("z")
|
| 87 |
+
|
| 88 |
+
# The expected communities are:
|
| 89 |
+
ground_truth1 = {
|
| 90 |
+
frozenset(["a", "b", "c", "d", "e"]),
|
| 91 |
+
frozenset(["1", "2", "3", "4", "5"]),
|
| 92 |
+
frozenset(["x", "y"]),
|
| 93 |
+
frozenset(["z"]),
|
| 94 |
+
}
|
| 95 |
+
ground_truth2 = {
|
| 96 |
+
frozenset(["a", "b", "c", "d", "e", "1", "2", "3", "4", "5"]),
|
| 97 |
+
frozenset(["x", "y"]),
|
| 98 |
+
frozenset(["z"]),
|
| 99 |
+
}
|
| 100 |
+
ground_truth = (ground_truth1, ground_truth2)
|
| 101 |
+
|
| 102 |
+
communities = nx.community.label_propagation_communities(test)
|
| 103 |
+
result = {frozenset(c) for c in communities}
|
| 104 |
+
assert result in ground_truth
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def test_termination():
|
| 108 |
+
# ensure termination of asyn_lpa_communities in two cases
|
| 109 |
+
# that led to an endless loop in a previous version
|
| 110 |
+
test1 = nx.karate_club_graph()
|
| 111 |
+
test2 = nx.caveman_graph(2, 10)
|
| 112 |
+
test2.add_edges_from([(0, 20), (20, 10)])
|
| 113 |
+
nx.community.asyn_lpa_communities(test1)
|
| 114 |
+
nx.community.asyn_lpa_communities(test2)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
class TestAsynLpaCommunities:
|
| 118 |
+
def _check_communities(self, G, expected):
|
| 119 |
+
"""Checks that the communities computed from the given graph ``G``
|
| 120 |
+
using the :func:`~networkx.asyn_lpa_communities` function match
|
| 121 |
+
the set of nodes given in ``expected``.
|
| 122 |
+
|
| 123 |
+
``expected`` must be a :class:`set` of :class:`frozenset`
|
| 124 |
+
instances, each element of which is a node in the graph.
|
| 125 |
+
|
| 126 |
+
"""
|
| 127 |
+
communities = nx.community.asyn_lpa_communities(G)
|
| 128 |
+
result = {frozenset(c) for c in communities}
|
| 129 |
+
assert result == expected
|
| 130 |
+
|
| 131 |
+
def test_null_graph(self):
|
| 132 |
+
G = nx.null_graph()
|
| 133 |
+
ground_truth = set()
|
| 134 |
+
self._check_communities(G, ground_truth)
|
| 135 |
+
|
| 136 |
+
def test_single_node(self):
|
| 137 |
+
G = nx.empty_graph(1)
|
| 138 |
+
ground_truth = {frozenset([0])}
|
| 139 |
+
self._check_communities(G, ground_truth)
|
| 140 |
+
|
| 141 |
+
def test_simple_communities(self):
|
| 142 |
+
# This graph is the disjoint union of two triangles.
|
| 143 |
+
G = nx.Graph(["ab", "ac", "bc", "de", "df", "fe"])
|
| 144 |
+
ground_truth = {frozenset("abc"), frozenset("def")}
|
| 145 |
+
self._check_communities(G, ground_truth)
|
| 146 |
+
|
| 147 |
+
def test_seed_argument(self):
|
| 148 |
+
G = nx.Graph(["ab", "ac", "bc", "de", "df", "fe"])
|
| 149 |
+
ground_truth = {frozenset("abc"), frozenset("def")}
|
| 150 |
+
communities = nx.community.asyn_lpa_communities(G, seed=1)
|
| 151 |
+
result = {frozenset(c) for c in communities}
|
| 152 |
+
assert result == ground_truth
|
| 153 |
+
|
| 154 |
+
def test_several_communities(self):
|
| 155 |
+
# This graph is the disjoint union of five triangles.
|
| 156 |
+
ground_truth = {frozenset(range(3 * i, 3 * (i + 1))) for i in range(5)}
|
| 157 |
+
edges = chain.from_iterable(combinations(c, 2) for c in ground_truth)
|
| 158 |
+
G = nx.Graph(edges)
|
| 159 |
+
self._check_communities(G, ground_truth)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
class TestFastLabelPropagationCommunities:
|
| 163 |
+
N = 100 # number of nodes
|
| 164 |
+
K = 15 # average node degree
|
| 165 |
+
|
| 166 |
+
def _check_communities(self, G, truth, weight=None, seed=42):
|
| 167 |
+
C = nx.community.fast_label_propagation_communities(G, weight=weight, seed=seed)
|
| 168 |
+
assert {frozenset(c) for c in C} == truth
|
| 169 |
+
|
| 170 |
+
def test_null_graph(self):
|
| 171 |
+
G = nx.null_graph()
|
| 172 |
+
truth = set()
|
| 173 |
+
self._check_communities(G, truth)
|
| 174 |
+
|
| 175 |
+
def test_empty_graph(self):
|
| 176 |
+
G = nx.empty_graph(self.N)
|
| 177 |
+
truth = {frozenset([i]) for i in G}
|
| 178 |
+
self._check_communities(G, truth)
|
| 179 |
+
|
| 180 |
+
def test_star_graph(self):
|
| 181 |
+
G = nx.star_graph(self.N)
|
| 182 |
+
truth = {frozenset(G)}
|
| 183 |
+
self._check_communities(G, truth)
|
| 184 |
+
|
| 185 |
+
def test_complete_graph(self):
|
| 186 |
+
G = nx.complete_graph(self.N)
|
| 187 |
+
truth = {frozenset(G)}
|
| 188 |
+
self._check_communities(G, truth)
|
| 189 |
+
|
| 190 |
+
def test_bipartite_graph(self):
|
| 191 |
+
G = nx.complete_bipartite_graph(self.N // 2, self.N // 2)
|
| 192 |
+
truth = {frozenset(G)}
|
| 193 |
+
self._check_communities(G, truth)
|
| 194 |
+
|
| 195 |
+
def test_random_graph(self):
|
| 196 |
+
G = nx.gnm_random_graph(self.N, self.N * self.K // 2, seed=42)
|
| 197 |
+
truth = {frozenset(G)}
|
| 198 |
+
self._check_communities(G, truth)
|
| 199 |
+
|
| 200 |
+
def test_disjoin_cliques(self):
|
| 201 |
+
G = nx.Graph(["ab", "AB", "AC", "BC", "12", "13", "14", "23", "24", "34"])
|
| 202 |
+
truth = {frozenset("ab"), frozenset("ABC"), frozenset("1234")}
|
| 203 |
+
self._check_communities(G, truth)
|
| 204 |
+
|
| 205 |
+
def test_ring_of_cliques(self):
|
| 206 |
+
N, K = self.N, self.K
|
| 207 |
+
G = nx.ring_of_cliques(N, K)
|
| 208 |
+
truth = {frozenset([K * i + k for k in range(K)]) for i in range(N)}
|
| 209 |
+
self._check_communities(G, truth)
|
| 210 |
+
|
| 211 |
+
def test_larger_graph(self):
|
| 212 |
+
G = nx.gnm_random_graph(100 * self.N, 50 * self.N * self.K, seed=42)
|
| 213 |
+
nx.community.fast_label_propagation_communities(G)
|
| 214 |
+
|
| 215 |
+
def test_graph_type(self):
|
| 216 |
+
G1 = nx.complete_graph(self.N, nx.MultiDiGraph())
|
| 217 |
+
G2 = nx.MultiGraph(G1)
|
| 218 |
+
G3 = nx.DiGraph(G1)
|
| 219 |
+
G4 = nx.Graph(G1)
|
| 220 |
+
truth = {frozenset(G1)}
|
| 221 |
+
self._check_communities(G1, truth)
|
| 222 |
+
self._check_communities(G2, truth)
|
| 223 |
+
self._check_communities(G3, truth)
|
| 224 |
+
self._check_communities(G4, truth)
|
| 225 |
+
|
| 226 |
+
def test_weight_argument(self):
|
| 227 |
+
G = nx.MultiDiGraph()
|
| 228 |
+
G.add_edge(1, 2, weight=1.41)
|
| 229 |
+
G.add_edge(2, 1, weight=1.41)
|
| 230 |
+
G.add_edge(2, 3)
|
| 231 |
+
G.add_edge(3, 4, weight=3.14)
|
| 232 |
+
truth = {frozenset({1, 2}), frozenset({3, 4})}
|
| 233 |
+
self._check_communities(G, truth, weight="weight")
|
| 234 |
+
|
| 235 |
+
def test_seed_argument(self):
|
| 236 |
+
G = nx.karate_club_graph()
|
| 237 |
+
C = nx.community.fast_label_propagation_communities(G, seed=2023)
|
| 238 |
+
truth = {frozenset(c) for c in C}
|
| 239 |
+
self._check_communities(G, truth, seed=2023)
|
| 240 |
+
# smoke test that seed=None works
|
| 241 |
+
C = nx.community.fast_label_propagation_communities(G, seed=None)
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_louvain.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def test_modularity_increase():
|
| 7 |
+
G = nx.LFR_benchmark_graph(
|
| 8 |
+
250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10
|
| 9 |
+
)
|
| 10 |
+
partition = [{u} for u in G.nodes()]
|
| 11 |
+
mod = nx.community.modularity(G, partition)
|
| 12 |
+
partition = nx.community.louvain_communities(G)
|
| 13 |
+
|
| 14 |
+
assert nx.community.modularity(G, partition) > mod
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def test_valid_partition():
|
| 18 |
+
G = nx.LFR_benchmark_graph(
|
| 19 |
+
250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10
|
| 20 |
+
)
|
| 21 |
+
H = G.to_directed()
|
| 22 |
+
partition = nx.community.louvain_communities(G)
|
| 23 |
+
partition2 = nx.community.louvain_communities(H)
|
| 24 |
+
|
| 25 |
+
assert nx.community.is_partition(G, partition)
|
| 26 |
+
assert nx.community.is_partition(H, partition2)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def test_karate_club_partition():
|
| 30 |
+
G = nx.karate_club_graph()
|
| 31 |
+
part = [
|
| 32 |
+
{0, 1, 2, 3, 7, 9, 11, 12, 13, 17, 19, 21},
|
| 33 |
+
{16, 4, 5, 6, 10},
|
| 34 |
+
{23, 25, 27, 28, 24, 31},
|
| 35 |
+
{32, 33, 8, 14, 15, 18, 20, 22, 26, 29, 30},
|
| 36 |
+
]
|
| 37 |
+
partition = nx.community.louvain_communities(G, seed=2, weight=None)
|
| 38 |
+
|
| 39 |
+
assert part == partition
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def test_partition_iterator():
|
| 43 |
+
G = nx.path_graph(15)
|
| 44 |
+
parts_iter = nx.community.louvain_partitions(G, seed=42)
|
| 45 |
+
first_part = next(parts_iter)
|
| 46 |
+
first_copy = [s.copy() for s in first_part]
|
| 47 |
+
|
| 48 |
+
# gh-5901 reports sets changing after next partition is yielded
|
| 49 |
+
assert first_copy[0] == first_part[0]
|
| 50 |
+
second_part = next(parts_iter)
|
| 51 |
+
assert first_copy[0] == first_part[0]
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def test_undirected_selfloops():
|
| 55 |
+
G = nx.karate_club_graph()
|
| 56 |
+
expected_partition = nx.community.louvain_communities(G, seed=2, weight=None)
|
| 57 |
+
part = [
|
| 58 |
+
{0, 1, 2, 3, 7, 9, 11, 12, 13, 17, 19, 21},
|
| 59 |
+
{16, 4, 5, 6, 10},
|
| 60 |
+
{23, 25, 27, 28, 24, 31},
|
| 61 |
+
{32, 33, 8, 14, 15, 18, 20, 22, 26, 29, 30},
|
| 62 |
+
]
|
| 63 |
+
assert expected_partition == part
|
| 64 |
+
|
| 65 |
+
G.add_weighted_edges_from([(i, i, i * 1000) for i in range(9)])
|
| 66 |
+
# large self-loop weight impacts partition
|
| 67 |
+
partition = nx.community.louvain_communities(G, seed=2, weight="weight")
|
| 68 |
+
assert part != partition
|
| 69 |
+
|
| 70 |
+
# small self-loop weights aren't enough to impact partition in this graph
|
| 71 |
+
partition = nx.community.louvain_communities(G, seed=2, weight=None)
|
| 72 |
+
assert part == partition
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def test_directed_selfloops():
|
| 76 |
+
G = nx.DiGraph()
|
| 77 |
+
G.add_nodes_from(range(11))
|
| 78 |
+
G_edges = [
|
| 79 |
+
(0, 2),
|
| 80 |
+
(0, 1),
|
| 81 |
+
(1, 0),
|
| 82 |
+
(2, 1),
|
| 83 |
+
(2, 0),
|
| 84 |
+
(3, 4),
|
| 85 |
+
(4, 3),
|
| 86 |
+
(7, 8),
|
| 87 |
+
(8, 7),
|
| 88 |
+
(9, 10),
|
| 89 |
+
(10, 9),
|
| 90 |
+
]
|
| 91 |
+
G.add_edges_from(G_edges)
|
| 92 |
+
G_expected_partition = nx.community.louvain_communities(G, seed=123, weight=None)
|
| 93 |
+
|
| 94 |
+
G.add_weighted_edges_from([(i, i, i * 1000) for i in range(3)])
|
| 95 |
+
# large self-loop weight impacts partition
|
| 96 |
+
G_partition = nx.community.louvain_communities(G, seed=123, weight="weight")
|
| 97 |
+
assert G_partition != G_expected_partition
|
| 98 |
+
|
| 99 |
+
# small self-loop weights aren't enough to impact partition in this graph
|
| 100 |
+
G_partition = nx.community.louvain_communities(G, seed=123, weight=None)
|
| 101 |
+
assert G_partition == G_expected_partition
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def test_directed_partition():
|
| 105 |
+
"""
|
| 106 |
+
Test 2 cases that were looping infinitely
|
| 107 |
+
from issues #5175 and #5704
|
| 108 |
+
"""
|
| 109 |
+
G = nx.DiGraph()
|
| 110 |
+
H = nx.DiGraph()
|
| 111 |
+
G.add_nodes_from(range(10))
|
| 112 |
+
H.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
|
| 113 |
+
G_edges = [
|
| 114 |
+
(0, 2),
|
| 115 |
+
(0, 1),
|
| 116 |
+
(1, 0),
|
| 117 |
+
(2, 1),
|
| 118 |
+
(2, 0),
|
| 119 |
+
(3, 4),
|
| 120 |
+
(4, 3),
|
| 121 |
+
(7, 8),
|
| 122 |
+
(8, 7),
|
| 123 |
+
(9, 10),
|
| 124 |
+
(10, 9),
|
| 125 |
+
]
|
| 126 |
+
H_edges = [
|
| 127 |
+
(1, 2),
|
| 128 |
+
(1, 6),
|
| 129 |
+
(1, 9),
|
| 130 |
+
(2, 3),
|
| 131 |
+
(2, 4),
|
| 132 |
+
(2, 5),
|
| 133 |
+
(3, 4),
|
| 134 |
+
(4, 3),
|
| 135 |
+
(4, 5),
|
| 136 |
+
(5, 4),
|
| 137 |
+
(6, 7),
|
| 138 |
+
(6, 8),
|
| 139 |
+
(9, 10),
|
| 140 |
+
(9, 11),
|
| 141 |
+
(10, 11),
|
| 142 |
+
(11, 10),
|
| 143 |
+
]
|
| 144 |
+
G.add_edges_from(G_edges)
|
| 145 |
+
H.add_edges_from(H_edges)
|
| 146 |
+
|
| 147 |
+
G_expected_partition = [{0, 1, 2}, {3, 4}, {5}, {6}, {8, 7}, {9, 10}]
|
| 148 |
+
G_partition = nx.community.louvain_communities(G, seed=123, weight=None)
|
| 149 |
+
|
| 150 |
+
H_expected_partition = [{2, 3, 4, 5}, {8, 1, 6, 7}, {9, 10, 11}]
|
| 151 |
+
H_partition = nx.community.louvain_communities(H, seed=123, weight=None)
|
| 152 |
+
|
| 153 |
+
assert G_partition == G_expected_partition
|
| 154 |
+
assert H_partition == H_expected_partition
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def test_none_weight_param():
|
| 158 |
+
G = nx.karate_club_graph()
|
| 159 |
+
nx.set_edge_attributes(
|
| 160 |
+
G, {edge: i * i for i, edge in enumerate(G.edges)}, name="foo"
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
part = [
|
| 164 |
+
{0, 1, 2, 3, 7, 9, 11, 12, 13, 17, 19, 21},
|
| 165 |
+
{16, 4, 5, 6, 10},
|
| 166 |
+
{23, 25, 27, 28, 24, 31},
|
| 167 |
+
{32, 33, 8, 14, 15, 18, 20, 22, 26, 29, 30},
|
| 168 |
+
]
|
| 169 |
+
partition1 = nx.community.louvain_communities(G, weight=None, seed=2)
|
| 170 |
+
partition2 = nx.community.louvain_communities(G, weight="foo", seed=2)
|
| 171 |
+
partition3 = nx.community.louvain_communities(G, weight="weight", seed=2)
|
| 172 |
+
|
| 173 |
+
assert part == partition1
|
| 174 |
+
assert part != partition2
|
| 175 |
+
assert part != partition3
|
| 176 |
+
assert partition2 != partition3
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def test_quality():
|
| 180 |
+
G = nx.LFR_benchmark_graph(
|
| 181 |
+
250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10
|
| 182 |
+
)
|
| 183 |
+
H = nx.gn_graph(200, seed=1234)
|
| 184 |
+
I = nx.MultiGraph(G)
|
| 185 |
+
J = nx.MultiDiGraph(H)
|
| 186 |
+
|
| 187 |
+
partition = nx.community.louvain_communities(G)
|
| 188 |
+
partition2 = nx.community.louvain_communities(H)
|
| 189 |
+
partition3 = nx.community.louvain_communities(I)
|
| 190 |
+
partition4 = nx.community.louvain_communities(J)
|
| 191 |
+
|
| 192 |
+
quality = nx.community.partition_quality(G, partition)[0]
|
| 193 |
+
quality2 = nx.community.partition_quality(H, partition2)[0]
|
| 194 |
+
quality3 = nx.community.partition_quality(I, partition3)[0]
|
| 195 |
+
quality4 = nx.community.partition_quality(J, partition4)[0]
|
| 196 |
+
|
| 197 |
+
assert quality >= 0.65
|
| 198 |
+
assert quality2 >= 0.65
|
| 199 |
+
assert quality3 >= 0.65
|
| 200 |
+
assert quality4 >= 0.65
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def test_multigraph():
|
| 204 |
+
G = nx.karate_club_graph()
|
| 205 |
+
H = nx.MultiGraph(G)
|
| 206 |
+
G.add_edge(0, 1, weight=10)
|
| 207 |
+
H.add_edge(0, 1, weight=9)
|
| 208 |
+
G.add_edge(0, 9, foo=20)
|
| 209 |
+
H.add_edge(0, 9, foo=20)
|
| 210 |
+
|
| 211 |
+
partition1 = nx.community.louvain_communities(G, seed=1234)
|
| 212 |
+
partition2 = nx.community.louvain_communities(H, seed=1234)
|
| 213 |
+
partition3 = nx.community.louvain_communities(H, weight="foo", seed=1234)
|
| 214 |
+
|
| 215 |
+
assert partition1 == partition2 != partition3
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def test_resolution():
|
| 219 |
+
G = nx.LFR_benchmark_graph(
|
| 220 |
+
250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
partition1 = nx.community.louvain_communities(G, resolution=0.5, seed=12)
|
| 224 |
+
partition2 = nx.community.louvain_communities(G, seed=12)
|
| 225 |
+
partition3 = nx.community.louvain_communities(G, resolution=2, seed=12)
|
| 226 |
+
|
| 227 |
+
assert len(partition1) <= len(partition2) <= len(partition3)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def test_threshold():
|
| 231 |
+
G = nx.LFR_benchmark_graph(
|
| 232 |
+
250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10
|
| 233 |
+
)
|
| 234 |
+
partition1 = nx.community.louvain_communities(G, threshold=0.3, seed=2)
|
| 235 |
+
partition2 = nx.community.louvain_communities(G, seed=2)
|
| 236 |
+
mod1 = nx.community.modularity(G, partition1)
|
| 237 |
+
mod2 = nx.community.modularity(G, partition2)
|
| 238 |
+
|
| 239 |
+
assert mod1 <= mod2
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def test_empty_graph():
|
| 243 |
+
G = nx.Graph()
|
| 244 |
+
G.add_nodes_from(range(5))
|
| 245 |
+
expected = [{0}, {1}, {2}, {3}, {4}]
|
| 246 |
+
assert nx.community.louvain_communities(G) == expected
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def test_max_level():
|
| 250 |
+
G = nx.LFR_benchmark_graph(
|
| 251 |
+
250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10
|
| 252 |
+
)
|
| 253 |
+
parts_iter = nx.community.louvain_partitions(G, seed=42)
|
| 254 |
+
for max_level, expected in enumerate(parts_iter, 1):
|
| 255 |
+
partition = nx.community.louvain_communities(G, max_level=max_level, seed=42)
|
| 256 |
+
assert partition == expected
|
| 257 |
+
assert max_level > 1 # Ensure we are actually testing max_level
|
| 258 |
+
# max_level is an upper limit; it's okay if we stop before it's hit.
|
| 259 |
+
partition = nx.community.louvain_communities(G, max_level=max_level + 1, seed=42)
|
| 260 |
+
assert partition == expected
|
| 261 |
+
with pytest.raises(
|
| 262 |
+
ValueError, match="max_level argument must be a positive integer"
|
| 263 |
+
):
|
| 264 |
+
nx.community.louvain_communities(G, max_level=0)
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_lukes.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from itertools import product
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
EWL = "e_weight"
|
| 8 |
+
NWL = "n_weight"
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
# first test from the Lukes original paper
|
| 12 |
+
def paper_1_case(float_edge_wt=False, explicit_node_wt=True, directed=False):
|
| 13 |
+
# problem-specific constants
|
| 14 |
+
limit = 3
|
| 15 |
+
|
| 16 |
+
# configuration
|
| 17 |
+
if float_edge_wt:
|
| 18 |
+
shift = 0.001
|
| 19 |
+
else:
|
| 20 |
+
shift = 0
|
| 21 |
+
|
| 22 |
+
if directed:
|
| 23 |
+
example_1 = nx.DiGraph()
|
| 24 |
+
else:
|
| 25 |
+
example_1 = nx.Graph()
|
| 26 |
+
|
| 27 |
+
# graph creation
|
| 28 |
+
example_1.add_edge(1, 2, **{EWL: 3 + shift})
|
| 29 |
+
example_1.add_edge(1, 4, **{EWL: 2 + shift})
|
| 30 |
+
example_1.add_edge(2, 3, **{EWL: 4 + shift})
|
| 31 |
+
example_1.add_edge(2, 5, **{EWL: 6 + shift})
|
| 32 |
+
|
| 33 |
+
# node weights
|
| 34 |
+
if explicit_node_wt:
|
| 35 |
+
nx.set_node_attributes(example_1, 1, NWL)
|
| 36 |
+
wtu = NWL
|
| 37 |
+
else:
|
| 38 |
+
wtu = None
|
| 39 |
+
|
| 40 |
+
# partitioning
|
| 41 |
+
clusters_1 = {
|
| 42 |
+
frozenset(x)
|
| 43 |
+
for x in nx.community.lukes_partitioning(
|
| 44 |
+
example_1, limit, node_weight=wtu, edge_weight=EWL
|
| 45 |
+
)
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
return clusters_1
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# second test from the Lukes original paper
|
| 52 |
+
def paper_2_case(explicit_edge_wt=True, directed=False):
|
| 53 |
+
# problem specific constants
|
| 54 |
+
byte_block_size = 32
|
| 55 |
+
|
| 56 |
+
# configuration
|
| 57 |
+
if directed:
|
| 58 |
+
example_2 = nx.DiGraph()
|
| 59 |
+
else:
|
| 60 |
+
example_2 = nx.Graph()
|
| 61 |
+
|
| 62 |
+
if explicit_edge_wt:
|
| 63 |
+
edic = {EWL: 1}
|
| 64 |
+
wtu = EWL
|
| 65 |
+
else:
|
| 66 |
+
edic = {}
|
| 67 |
+
wtu = None
|
| 68 |
+
|
| 69 |
+
# graph creation
|
| 70 |
+
example_2.add_edge("name", "home_address", **edic)
|
| 71 |
+
example_2.add_edge("name", "education", **edic)
|
| 72 |
+
example_2.add_edge("education", "bs", **edic)
|
| 73 |
+
example_2.add_edge("education", "ms", **edic)
|
| 74 |
+
example_2.add_edge("education", "phd", **edic)
|
| 75 |
+
example_2.add_edge("name", "telephone", **edic)
|
| 76 |
+
example_2.add_edge("telephone", "home", **edic)
|
| 77 |
+
example_2.add_edge("telephone", "office", **edic)
|
| 78 |
+
example_2.add_edge("office", "no1", **edic)
|
| 79 |
+
example_2.add_edge("office", "no2", **edic)
|
| 80 |
+
|
| 81 |
+
example_2.nodes["name"][NWL] = 20
|
| 82 |
+
example_2.nodes["education"][NWL] = 10
|
| 83 |
+
example_2.nodes["bs"][NWL] = 1
|
| 84 |
+
example_2.nodes["ms"][NWL] = 1
|
| 85 |
+
example_2.nodes["phd"][NWL] = 1
|
| 86 |
+
example_2.nodes["home_address"][NWL] = 8
|
| 87 |
+
example_2.nodes["telephone"][NWL] = 8
|
| 88 |
+
example_2.nodes["home"][NWL] = 8
|
| 89 |
+
example_2.nodes["office"][NWL] = 4
|
| 90 |
+
example_2.nodes["no1"][NWL] = 1
|
| 91 |
+
example_2.nodes["no2"][NWL] = 1
|
| 92 |
+
|
| 93 |
+
# partitioning
|
| 94 |
+
clusters_2 = {
|
| 95 |
+
frozenset(x)
|
| 96 |
+
for x in nx.community.lukes_partitioning(
|
| 97 |
+
example_2, byte_block_size, node_weight=NWL, edge_weight=wtu
|
| 98 |
+
)
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
return clusters_2
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def test_paper_1_case():
|
| 105 |
+
ground_truth = {frozenset([1, 4]), frozenset([2, 3, 5])}
|
| 106 |
+
|
| 107 |
+
tf = (True, False)
|
| 108 |
+
for flt, nwt, drc in product(tf, tf, tf):
|
| 109 |
+
part = paper_1_case(flt, nwt, drc)
|
| 110 |
+
assert part == ground_truth
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def test_paper_2_case():
|
| 114 |
+
ground_truth = {
|
| 115 |
+
frozenset(["education", "bs", "ms", "phd"]),
|
| 116 |
+
frozenset(["name", "home_address"]),
|
| 117 |
+
frozenset(["telephone", "home", "office", "no1", "no2"]),
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
tf = (True, False)
|
| 121 |
+
for ewt, drc in product(tf, tf):
|
| 122 |
+
part = paper_2_case(ewt, drc)
|
| 123 |
+
assert part == ground_truth
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def test_mandatory_tree():
|
| 127 |
+
not_a_tree = nx.complete_graph(4)
|
| 128 |
+
|
| 129 |
+
with pytest.raises(nx.NotATree):
|
| 130 |
+
nx.community.lukes_partitioning(not_a_tree, 5)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def test_mandatory_integrality():
|
| 134 |
+
byte_block_size = 32
|
| 135 |
+
|
| 136 |
+
ex_1_broken = nx.DiGraph()
|
| 137 |
+
|
| 138 |
+
ex_1_broken.add_edge(1, 2, **{EWL: 3.2})
|
| 139 |
+
ex_1_broken.add_edge(1, 4, **{EWL: 2.4})
|
| 140 |
+
ex_1_broken.add_edge(2, 3, **{EWL: 4.0})
|
| 141 |
+
ex_1_broken.add_edge(2, 5, **{EWL: 6.3})
|
| 142 |
+
|
| 143 |
+
ex_1_broken.nodes[1][NWL] = 1.2 # !
|
| 144 |
+
ex_1_broken.nodes[2][NWL] = 1
|
| 145 |
+
ex_1_broken.nodes[3][NWL] = 1
|
| 146 |
+
ex_1_broken.nodes[4][NWL] = 1
|
| 147 |
+
ex_1_broken.nodes[5][NWL] = 2
|
| 148 |
+
|
| 149 |
+
with pytest.raises(TypeError):
|
| 150 |
+
nx.community.lukes_partitioning(
|
| 151 |
+
ex_1_broken, byte_block_size, node_weight=NWL, edge_weight=EWL
|
| 152 |
+
)
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_modularity_max.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.algorithms.community import (
|
| 5 |
+
greedy_modularity_communities,
|
| 6 |
+
naive_greedy_modularity_communities,
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@pytest.mark.parametrize(
|
| 11 |
+
"func", (greedy_modularity_communities, naive_greedy_modularity_communities)
|
| 12 |
+
)
|
| 13 |
+
def test_modularity_communities(func):
|
| 14 |
+
G = nx.karate_club_graph()
|
| 15 |
+
john_a = frozenset(
|
| 16 |
+
[8, 14, 15, 18, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]
|
| 17 |
+
)
|
| 18 |
+
mr_hi = frozenset([0, 4, 5, 6, 10, 11, 16, 19])
|
| 19 |
+
overlap = frozenset([1, 2, 3, 7, 9, 12, 13, 17, 21])
|
| 20 |
+
expected = {john_a, overlap, mr_hi}
|
| 21 |
+
assert set(func(G, weight=None)) == expected
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@pytest.mark.parametrize(
|
| 25 |
+
"func", (greedy_modularity_communities, naive_greedy_modularity_communities)
|
| 26 |
+
)
|
| 27 |
+
def test_modularity_communities_categorical_labels(func):
|
| 28 |
+
# Using other than 0-starting contiguous integers as node-labels.
|
| 29 |
+
G = nx.Graph(
|
| 30 |
+
[
|
| 31 |
+
("a", "b"),
|
| 32 |
+
("a", "c"),
|
| 33 |
+
("b", "c"),
|
| 34 |
+
("b", "d"), # inter-community edge
|
| 35 |
+
("d", "e"),
|
| 36 |
+
("d", "f"),
|
| 37 |
+
("d", "g"),
|
| 38 |
+
("f", "g"),
|
| 39 |
+
("d", "e"),
|
| 40 |
+
("f", "e"),
|
| 41 |
+
]
|
| 42 |
+
)
|
| 43 |
+
expected = {frozenset({"f", "g", "e", "d"}), frozenset({"a", "b", "c"})}
|
| 44 |
+
assert set(func(G)) == expected
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def test_greedy_modularity_communities_components():
|
| 48 |
+
# Test for gh-5530
|
| 49 |
+
G = nx.Graph([(0, 1), (2, 3), (4, 5), (5, 6)])
|
| 50 |
+
# usual case with 3 components
|
| 51 |
+
assert greedy_modularity_communities(G) == [{4, 5, 6}, {0, 1}, {2, 3}]
|
| 52 |
+
# best_n can make the algorithm continue even when modularity goes down
|
| 53 |
+
assert greedy_modularity_communities(G, best_n=3) == [{4, 5, 6}, {0, 1}, {2, 3}]
|
| 54 |
+
assert greedy_modularity_communities(G, best_n=2) == [{0, 1, 4, 5, 6}, {2, 3}]
|
| 55 |
+
assert greedy_modularity_communities(G, best_n=1) == [{0, 1, 2, 3, 4, 5, 6}]
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def test_greedy_modularity_communities_relabeled():
|
| 59 |
+
# Test for gh-4966
|
| 60 |
+
G = nx.balanced_tree(2, 2)
|
| 61 |
+
mapping = {0: "a", 1: "b", 2: "c", 3: "d", 4: "e", 5: "f", 6: "g", 7: "h"}
|
| 62 |
+
G = nx.relabel_nodes(G, mapping)
|
| 63 |
+
expected = [frozenset({"e", "d", "a", "b"}), frozenset({"c", "f", "g"})]
|
| 64 |
+
assert greedy_modularity_communities(G) == expected
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def test_greedy_modularity_communities_directed():
|
| 68 |
+
G = nx.DiGraph(
|
| 69 |
+
[
|
| 70 |
+
("a", "b"),
|
| 71 |
+
("a", "c"),
|
| 72 |
+
("b", "c"),
|
| 73 |
+
("b", "d"), # inter-community edge
|
| 74 |
+
("d", "e"),
|
| 75 |
+
("d", "f"),
|
| 76 |
+
("d", "g"),
|
| 77 |
+
("f", "g"),
|
| 78 |
+
("d", "e"),
|
| 79 |
+
("f", "e"),
|
| 80 |
+
]
|
| 81 |
+
)
|
| 82 |
+
expected = [frozenset({"f", "g", "e", "d"}), frozenset({"a", "b", "c"})]
|
| 83 |
+
assert greedy_modularity_communities(G) == expected
|
| 84 |
+
|
| 85 |
+
# with loops
|
| 86 |
+
G = nx.DiGraph()
|
| 87 |
+
G.add_edges_from(
|
| 88 |
+
[(1, 1), (1, 2), (1, 3), (2, 3), (1, 4), (4, 4), (5, 5), (4, 5), (4, 6), (5, 6)]
|
| 89 |
+
)
|
| 90 |
+
expected = [frozenset({1, 2, 3}), frozenset({4, 5, 6})]
|
| 91 |
+
assert greedy_modularity_communities(G) == expected
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
@pytest.mark.parametrize(
|
| 95 |
+
"func", (greedy_modularity_communities, naive_greedy_modularity_communities)
|
| 96 |
+
)
|
| 97 |
+
def test_modularity_communities_weighted(func):
|
| 98 |
+
G = nx.balanced_tree(2, 3)
|
| 99 |
+
for a, b in G.edges:
|
| 100 |
+
if ((a == 1) or (a == 2)) and (b != 0):
|
| 101 |
+
G[a][b]["weight"] = 10.0
|
| 102 |
+
else:
|
| 103 |
+
G[a][b]["weight"] = 1.0
|
| 104 |
+
|
| 105 |
+
expected = [{0, 1, 3, 4, 7, 8, 9, 10}, {2, 5, 6, 11, 12, 13, 14}]
|
| 106 |
+
|
| 107 |
+
assert func(G, weight="weight") == expected
|
| 108 |
+
assert func(G, weight="weight", resolution=0.9) == expected
|
| 109 |
+
assert func(G, weight="weight", resolution=0.3) == expected
|
| 110 |
+
assert func(G, weight="weight", resolution=1.1) != expected
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def test_modularity_communities_floating_point():
|
| 114 |
+
# check for floating point error when used as key in the mapped_queue dict.
|
| 115 |
+
# Test for gh-4992 and gh-5000
|
| 116 |
+
G = nx.Graph()
|
| 117 |
+
G.add_weighted_edges_from(
|
| 118 |
+
[(0, 1, 12), (1, 4, 71), (2, 3, 15), (2, 4, 10), (3, 6, 13)]
|
| 119 |
+
)
|
| 120 |
+
expected = [{0, 1, 4}, {2, 3, 6}]
|
| 121 |
+
assert greedy_modularity_communities(G, weight="weight") == expected
|
| 122 |
+
assert (
|
| 123 |
+
greedy_modularity_communities(G, weight="weight", resolution=0.99) == expected
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def test_modularity_communities_directed_weighted():
|
| 128 |
+
G = nx.DiGraph()
|
| 129 |
+
G.add_weighted_edges_from(
|
| 130 |
+
[
|
| 131 |
+
(1, 2, 5),
|
| 132 |
+
(1, 3, 3),
|
| 133 |
+
(2, 3, 6),
|
| 134 |
+
(2, 6, 1),
|
| 135 |
+
(1, 4, 1),
|
| 136 |
+
(4, 5, 3),
|
| 137 |
+
(4, 6, 7),
|
| 138 |
+
(5, 6, 2),
|
| 139 |
+
(5, 7, 5),
|
| 140 |
+
(5, 8, 4),
|
| 141 |
+
(6, 8, 3),
|
| 142 |
+
]
|
| 143 |
+
)
|
| 144 |
+
expected = [frozenset({4, 5, 6, 7, 8}), frozenset({1, 2, 3})]
|
| 145 |
+
assert greedy_modularity_communities(G, weight="weight") == expected
|
| 146 |
+
|
| 147 |
+
# A large weight of the edge (2, 6) causes 6 to change group, even if it shares
|
| 148 |
+
# only one connection with the new group and 3 with the old one.
|
| 149 |
+
G[2][6]["weight"] = 20
|
| 150 |
+
expected = [frozenset({1, 2, 3, 6}), frozenset({4, 5, 7, 8})]
|
| 151 |
+
assert greedy_modularity_communities(G, weight="weight") == expected
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def test_greedy_modularity_communities_multigraph():
|
| 155 |
+
G = nx.MultiGraph()
|
| 156 |
+
G.add_edges_from(
|
| 157 |
+
[
|
| 158 |
+
(1, 2),
|
| 159 |
+
(1, 2),
|
| 160 |
+
(1, 3),
|
| 161 |
+
(2, 3),
|
| 162 |
+
(1, 4),
|
| 163 |
+
(2, 4),
|
| 164 |
+
(4, 5),
|
| 165 |
+
(5, 6),
|
| 166 |
+
(5, 7),
|
| 167 |
+
(5, 7),
|
| 168 |
+
(6, 7),
|
| 169 |
+
(7, 8),
|
| 170 |
+
(5, 8),
|
| 171 |
+
]
|
| 172 |
+
)
|
| 173 |
+
expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})]
|
| 174 |
+
assert greedy_modularity_communities(G) == expected
|
| 175 |
+
|
| 176 |
+
# Converting (4, 5) into a multi-edge causes node 4 to change group.
|
| 177 |
+
G.add_edge(4, 5)
|
| 178 |
+
expected = [frozenset({4, 5, 6, 7, 8}), frozenset({1, 2, 3})]
|
| 179 |
+
assert greedy_modularity_communities(G) == expected
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def test_greedy_modularity_communities_multigraph_weighted():
|
| 183 |
+
G = nx.MultiGraph()
|
| 184 |
+
G.add_weighted_edges_from(
|
| 185 |
+
[
|
| 186 |
+
(1, 2, 5),
|
| 187 |
+
(1, 2, 3),
|
| 188 |
+
(1, 3, 6),
|
| 189 |
+
(1, 3, 6),
|
| 190 |
+
(2, 3, 4),
|
| 191 |
+
(1, 4, 1),
|
| 192 |
+
(1, 4, 1),
|
| 193 |
+
(2, 4, 3),
|
| 194 |
+
(2, 4, 3),
|
| 195 |
+
(4, 5, 1),
|
| 196 |
+
(5, 6, 3),
|
| 197 |
+
(5, 6, 7),
|
| 198 |
+
(5, 6, 4),
|
| 199 |
+
(5, 7, 9),
|
| 200 |
+
(5, 7, 9),
|
| 201 |
+
(6, 7, 8),
|
| 202 |
+
(7, 8, 2),
|
| 203 |
+
(7, 8, 2),
|
| 204 |
+
(5, 8, 6),
|
| 205 |
+
(5, 8, 6),
|
| 206 |
+
]
|
| 207 |
+
)
|
| 208 |
+
expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})]
|
| 209 |
+
assert greedy_modularity_communities(G, weight="weight") == expected
|
| 210 |
+
|
| 211 |
+
# Adding multi-edge (4, 5, 16) causes node 4 to change group.
|
| 212 |
+
G.add_edge(4, 5, weight=16)
|
| 213 |
+
expected = [frozenset({4, 5, 6, 7, 8}), frozenset({1, 2, 3})]
|
| 214 |
+
assert greedy_modularity_communities(G, weight="weight") == expected
|
| 215 |
+
|
| 216 |
+
# Increasing the weight of edge (1, 4) causes node 4 to return to the former group.
|
| 217 |
+
G[1][4][1]["weight"] = 3
|
| 218 |
+
expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})]
|
| 219 |
+
assert greedy_modularity_communities(G, weight="weight") == expected
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def test_greed_modularity_communities_multidigraph():
|
| 223 |
+
G = nx.MultiDiGraph()
|
| 224 |
+
G.add_edges_from(
|
| 225 |
+
[
|
| 226 |
+
(1, 2),
|
| 227 |
+
(1, 2),
|
| 228 |
+
(3, 1),
|
| 229 |
+
(2, 3),
|
| 230 |
+
(2, 3),
|
| 231 |
+
(3, 2),
|
| 232 |
+
(1, 4),
|
| 233 |
+
(2, 4),
|
| 234 |
+
(4, 2),
|
| 235 |
+
(4, 5),
|
| 236 |
+
(5, 6),
|
| 237 |
+
(5, 6),
|
| 238 |
+
(6, 5),
|
| 239 |
+
(5, 7),
|
| 240 |
+
(6, 7),
|
| 241 |
+
(7, 8),
|
| 242 |
+
(5, 8),
|
| 243 |
+
(8, 4),
|
| 244 |
+
]
|
| 245 |
+
)
|
| 246 |
+
expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})]
|
| 247 |
+
assert greedy_modularity_communities(G, weight="weight") == expected
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def test_greed_modularity_communities_multidigraph_weighted():
|
| 251 |
+
G = nx.MultiDiGraph()
|
| 252 |
+
G.add_weighted_edges_from(
|
| 253 |
+
[
|
| 254 |
+
(1, 2, 5),
|
| 255 |
+
(1, 2, 3),
|
| 256 |
+
(3, 1, 6),
|
| 257 |
+
(1, 3, 6),
|
| 258 |
+
(3, 2, 4),
|
| 259 |
+
(1, 4, 2),
|
| 260 |
+
(1, 4, 5),
|
| 261 |
+
(2, 4, 3),
|
| 262 |
+
(3, 2, 8),
|
| 263 |
+
(4, 2, 3),
|
| 264 |
+
(4, 3, 5),
|
| 265 |
+
(4, 5, 2),
|
| 266 |
+
(5, 6, 3),
|
| 267 |
+
(5, 6, 7),
|
| 268 |
+
(6, 5, 4),
|
| 269 |
+
(5, 7, 9),
|
| 270 |
+
(5, 7, 9),
|
| 271 |
+
(7, 6, 8),
|
| 272 |
+
(7, 8, 2),
|
| 273 |
+
(8, 7, 2),
|
| 274 |
+
(5, 8, 6),
|
| 275 |
+
(5, 8, 6),
|
| 276 |
+
]
|
| 277 |
+
)
|
| 278 |
+
expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})]
|
| 279 |
+
assert greedy_modularity_communities(G, weight="weight") == expected
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def test_resolution_parameter_impact():
|
| 283 |
+
G = nx.barbell_graph(5, 3)
|
| 284 |
+
|
| 285 |
+
gamma = 1
|
| 286 |
+
expected = [frozenset(range(5)), frozenset(range(8, 13)), frozenset(range(5, 8))]
|
| 287 |
+
assert greedy_modularity_communities(G, resolution=gamma) == expected
|
| 288 |
+
assert naive_greedy_modularity_communities(G, resolution=gamma) == expected
|
| 289 |
+
|
| 290 |
+
gamma = 2.5
|
| 291 |
+
expected = [{0, 1, 2, 3}, {9, 10, 11, 12}, {5, 6, 7}, {4}, {8}]
|
| 292 |
+
assert greedy_modularity_communities(G, resolution=gamma) == expected
|
| 293 |
+
assert naive_greedy_modularity_communities(G, resolution=gamma) == expected
|
| 294 |
+
|
| 295 |
+
gamma = 0.3
|
| 296 |
+
expected = [frozenset(range(8)), frozenset(range(8, 13))]
|
| 297 |
+
assert greedy_modularity_communities(G, resolution=gamma) == expected
|
| 298 |
+
assert naive_greedy_modularity_communities(G, resolution=gamma) == expected
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def test_cutoff_parameter():
|
| 302 |
+
G = nx.circular_ladder_graph(4)
|
| 303 |
+
|
| 304 |
+
# No aggregation:
|
| 305 |
+
expected = [{k} for k in range(8)]
|
| 306 |
+
assert greedy_modularity_communities(G, cutoff=8) == expected
|
| 307 |
+
|
| 308 |
+
# Aggregation to half order (number of nodes)
|
| 309 |
+
expected = [{k, k + 1} for k in range(0, 8, 2)]
|
| 310 |
+
assert greedy_modularity_communities(G, cutoff=4) == expected
|
| 311 |
+
|
| 312 |
+
# Default aggregation case (here, 2 communities emerge)
|
| 313 |
+
expected = [frozenset(range(4)), frozenset(range(4, 8))]
|
| 314 |
+
assert greedy_modularity_communities(G, cutoff=1) == expected
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def test_best_n():
|
| 318 |
+
G = nx.barbell_graph(5, 3)
|
| 319 |
+
|
| 320 |
+
# Same result as without enforcing cutoff:
|
| 321 |
+
best_n = 3
|
| 322 |
+
expected = [frozenset(range(5)), frozenset(range(8, 13)), frozenset(range(5, 8))]
|
| 323 |
+
assert greedy_modularity_communities(G, best_n=best_n) == expected
|
| 324 |
+
|
| 325 |
+
# One additional merging step:
|
| 326 |
+
best_n = 2
|
| 327 |
+
expected = [frozenset(range(8)), frozenset(range(8, 13))]
|
| 328 |
+
assert greedy_modularity_communities(G, best_n=best_n) == expected
|
| 329 |
+
|
| 330 |
+
# Two additional merging steps:
|
| 331 |
+
best_n = 1
|
| 332 |
+
expected = [frozenset(range(13))]
|
| 333 |
+
assert greedy_modularity_communities(G, best_n=best_n) == expected
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def test_greedy_modularity_communities_corner_cases():
|
| 337 |
+
G = nx.empty_graph()
|
| 338 |
+
assert nx.community.greedy_modularity_communities(G) == []
|
| 339 |
+
G.add_nodes_from(range(3))
|
| 340 |
+
assert nx.community.greedy_modularity_communities(G) == [{0}, {1}, {2}]
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_quality.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit tests for the :mod:`networkx.algorithms.community.quality`
|
| 2 |
+
module.
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import pytest
|
| 7 |
+
|
| 8 |
+
import networkx as nx
|
| 9 |
+
from networkx import barbell_graph
|
| 10 |
+
from networkx.algorithms.community import modularity, partition_quality
|
| 11 |
+
from networkx.algorithms.community.quality import inter_community_edges
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class TestPerformance:
|
| 15 |
+
"""Unit tests for the :func:`performance` function."""
|
| 16 |
+
|
| 17 |
+
def test_bad_partition(self):
|
| 18 |
+
"""Tests that a poor partition has a low performance measure."""
|
| 19 |
+
G = barbell_graph(3, 0)
|
| 20 |
+
partition = [{0, 1, 4}, {2, 3, 5}]
|
| 21 |
+
assert 8 / 15 == pytest.approx(partition_quality(G, partition)[1], abs=1e-7)
|
| 22 |
+
|
| 23 |
+
def test_good_partition(self):
|
| 24 |
+
"""Tests that a good partition has a high performance measure."""
|
| 25 |
+
G = barbell_graph(3, 0)
|
| 26 |
+
partition = [{0, 1, 2}, {3, 4, 5}]
|
| 27 |
+
assert 14 / 15 == pytest.approx(partition_quality(G, partition)[1], abs=1e-7)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class TestCoverage:
|
| 31 |
+
"""Unit tests for the :func:`coverage` function."""
|
| 32 |
+
|
| 33 |
+
def test_bad_partition(self):
|
| 34 |
+
"""Tests that a poor partition has a low coverage measure."""
|
| 35 |
+
G = barbell_graph(3, 0)
|
| 36 |
+
partition = [{0, 1, 4}, {2, 3, 5}]
|
| 37 |
+
assert 3 / 7 == pytest.approx(partition_quality(G, partition)[0], abs=1e-7)
|
| 38 |
+
|
| 39 |
+
def test_good_partition(self):
|
| 40 |
+
"""Tests that a good partition has a high coverage measure."""
|
| 41 |
+
G = barbell_graph(3, 0)
|
| 42 |
+
partition = [{0, 1, 2}, {3, 4, 5}]
|
| 43 |
+
assert 6 / 7 == pytest.approx(partition_quality(G, partition)[0], abs=1e-7)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def test_modularity():
|
| 47 |
+
G = nx.barbell_graph(3, 0)
|
| 48 |
+
C = [{0, 1, 4}, {2, 3, 5}]
|
| 49 |
+
assert (-16 / (14**2)) == pytest.approx(modularity(G, C), abs=1e-7)
|
| 50 |
+
C = [{0, 1, 2}, {3, 4, 5}]
|
| 51 |
+
assert (35 * 2) / (14**2) == pytest.approx(modularity(G, C), abs=1e-7)
|
| 52 |
+
|
| 53 |
+
n = 1000
|
| 54 |
+
G = nx.erdos_renyi_graph(n, 0.09, seed=42, directed=True)
|
| 55 |
+
C = [set(range(n // 2)), set(range(n // 2, n))]
|
| 56 |
+
assert 0.00017154251389292754 == pytest.approx(modularity(G, C), abs=1e-7)
|
| 57 |
+
|
| 58 |
+
G = nx.margulis_gabber_galil_graph(10)
|
| 59 |
+
mid_value = G.number_of_nodes() // 2
|
| 60 |
+
nodes = list(G.nodes)
|
| 61 |
+
C = [set(nodes[:mid_value]), set(nodes[mid_value:])]
|
| 62 |
+
assert 0.13 == pytest.approx(modularity(G, C), abs=1e-7)
|
| 63 |
+
|
| 64 |
+
G = nx.DiGraph()
|
| 65 |
+
G.add_edges_from([(2, 1), (2, 3), (3, 4)])
|
| 66 |
+
C = [{1, 2}, {3, 4}]
|
| 67 |
+
assert 2 / 9 == pytest.approx(modularity(G, C), abs=1e-7)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def test_modularity_resolution():
|
| 71 |
+
G = nx.barbell_graph(3, 0)
|
| 72 |
+
C = [{0, 1, 4}, {2, 3, 5}]
|
| 73 |
+
assert modularity(G, C) == pytest.approx(3 / 7 - 100 / 14**2)
|
| 74 |
+
gamma = 2
|
| 75 |
+
result = modularity(G, C, resolution=gamma)
|
| 76 |
+
assert result == pytest.approx(3 / 7 - gamma * 100 / 14**2)
|
| 77 |
+
gamma = 0.2
|
| 78 |
+
result = modularity(G, C, resolution=gamma)
|
| 79 |
+
assert result == pytest.approx(3 / 7 - gamma * 100 / 14**2)
|
| 80 |
+
|
| 81 |
+
C = [{0, 1, 2}, {3, 4, 5}]
|
| 82 |
+
assert modularity(G, C) == pytest.approx(6 / 7 - 98 / 14**2)
|
| 83 |
+
gamma = 2
|
| 84 |
+
result = modularity(G, C, resolution=gamma)
|
| 85 |
+
assert result == pytest.approx(6 / 7 - gamma * 98 / 14**2)
|
| 86 |
+
gamma = 0.2
|
| 87 |
+
result = modularity(G, C, resolution=gamma)
|
| 88 |
+
assert result == pytest.approx(6 / 7 - gamma * 98 / 14**2)
|
| 89 |
+
|
| 90 |
+
G = nx.barbell_graph(5, 3)
|
| 91 |
+
C = [frozenset(range(5)), frozenset(range(8, 13)), frozenset(range(5, 8))]
|
| 92 |
+
gamma = 1
|
| 93 |
+
result = modularity(G, C, resolution=gamma)
|
| 94 |
+
# This C is maximal for gamma=1: modularity = 0.518229
|
| 95 |
+
assert result == pytest.approx((22 / 24) - gamma * (918 / (48**2)))
|
| 96 |
+
gamma = 2
|
| 97 |
+
result = modularity(G, C, resolution=gamma)
|
| 98 |
+
assert result == pytest.approx((22 / 24) - gamma * (918 / (48**2)))
|
| 99 |
+
gamma = 0.2
|
| 100 |
+
result = modularity(G, C, resolution=gamma)
|
| 101 |
+
assert result == pytest.approx((22 / 24) - gamma * (918 / (48**2)))
|
| 102 |
+
|
| 103 |
+
C = [{0, 1, 2, 3}, {9, 10, 11, 12}, {5, 6, 7}, {4}, {8}]
|
| 104 |
+
gamma = 1
|
| 105 |
+
result = modularity(G, C, resolution=gamma)
|
| 106 |
+
assert result == pytest.approx((14 / 24) - gamma * (598 / (48**2)))
|
| 107 |
+
gamma = 2.5
|
| 108 |
+
result = modularity(G, C, resolution=gamma)
|
| 109 |
+
# This C is maximal for gamma=2.5: modularity = -0.06553819
|
| 110 |
+
assert result == pytest.approx((14 / 24) - gamma * (598 / (48**2)))
|
| 111 |
+
gamma = 0.2
|
| 112 |
+
result = modularity(G, C, resolution=gamma)
|
| 113 |
+
assert result == pytest.approx((14 / 24) - gamma * (598 / (48**2)))
|
| 114 |
+
|
| 115 |
+
C = [frozenset(range(8)), frozenset(range(8, 13))]
|
| 116 |
+
gamma = 1
|
| 117 |
+
result = modularity(G, C, resolution=gamma)
|
| 118 |
+
assert result == pytest.approx((23 / 24) - gamma * (1170 / (48**2)))
|
| 119 |
+
gamma = 2
|
| 120 |
+
result = modularity(G, C, resolution=gamma)
|
| 121 |
+
assert result == pytest.approx((23 / 24) - gamma * (1170 / (48**2)))
|
| 122 |
+
gamma = 0.3
|
| 123 |
+
result = modularity(G, C, resolution=gamma)
|
| 124 |
+
# This C is maximal for gamma=0.3: modularity = 0.805990
|
| 125 |
+
assert result == pytest.approx((23 / 24) - gamma * (1170 / (48**2)))
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def test_inter_community_edges_with_digraphs():
|
| 129 |
+
G = nx.complete_graph(2, create_using=nx.DiGraph())
|
| 130 |
+
partition = [{0}, {1}]
|
| 131 |
+
assert inter_community_edges(G, partition) == 2
|
| 132 |
+
|
| 133 |
+
G = nx.complete_graph(10, create_using=nx.DiGraph())
|
| 134 |
+
partition = [{0}, {1, 2}, {3, 4, 5}, {6, 7, 8, 9}]
|
| 135 |
+
assert inter_community_edges(G, partition) == 70
|
| 136 |
+
|
| 137 |
+
G = nx.cycle_graph(4, create_using=nx.DiGraph())
|
| 138 |
+
partition = [{0, 1}, {2, 3}]
|
| 139 |
+
assert inter_community_edges(G, partition) == 2
|
.venv/lib/python3.11/site-packages/networkx/algorithms/community/tests/test_utils.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit tests for the :mod:`networkx.algorithms.community.utils` module."""
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def test_is_partition():
|
| 7 |
+
G = nx.empty_graph(3)
|
| 8 |
+
assert nx.community.is_partition(G, [{0, 1}, {2}])
|
| 9 |
+
assert nx.community.is_partition(G, ({0, 1}, {2}))
|
| 10 |
+
assert nx.community.is_partition(G, ([0, 1], [2]))
|
| 11 |
+
assert nx.community.is_partition(G, [[0, 1], [2]])
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def test_not_covering():
|
| 15 |
+
G = nx.empty_graph(3)
|
| 16 |
+
assert not nx.community.is_partition(G, [{0}, {1}])
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def test_not_disjoint():
|
| 20 |
+
G = nx.empty_graph(3)
|
| 21 |
+
assert not nx.community.is_partition(G, [{0, 1}, {1, 2}])
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def test_not_node():
|
| 25 |
+
G = nx.empty_graph(3)
|
| 26 |
+
assert not nx.community.is_partition(G, [{0, 1}, {3}])
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .connected import *
|
| 2 |
+
from .strongly_connected import *
|
| 3 |
+
from .weakly_connected import *
|
| 4 |
+
from .attracting import *
|
| 5 |
+
from .biconnected import *
|
| 6 |
+
from .semiconnected import *
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/__pycache__/connected.cpython-311.pyc
ADDED
|
Binary file (6.25 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/__pycache__/semiconnected.cpython-311.pyc
ADDED
|
Binary file (2.99 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/attracting.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Attracting components."""
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.utils.decorators import not_implemented_for
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
"number_attracting_components",
|
| 8 |
+
"attracting_components",
|
| 9 |
+
"is_attracting_component",
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@not_implemented_for("undirected")
|
| 14 |
+
@nx._dispatchable
|
| 15 |
+
def attracting_components(G):
|
| 16 |
+
"""Generates the attracting components in `G`.
|
| 17 |
+
|
| 18 |
+
An attracting component in a directed graph `G` is a strongly connected
|
| 19 |
+
component with the property that a random walker on the graph will never
|
| 20 |
+
leave the component, once it enters the component.
|
| 21 |
+
|
| 22 |
+
The nodes in attracting components can also be thought of as recurrent
|
| 23 |
+
nodes. If a random walker enters the attractor containing the node, then
|
| 24 |
+
the node will be visited infinitely often.
|
| 25 |
+
|
| 26 |
+
To obtain induced subgraphs on each component use:
|
| 27 |
+
``(G.subgraph(c).copy() for c in attracting_components(G))``
|
| 28 |
+
|
| 29 |
+
Parameters
|
| 30 |
+
----------
|
| 31 |
+
G : DiGraph, MultiDiGraph
|
| 32 |
+
The graph to be analyzed.
|
| 33 |
+
|
| 34 |
+
Returns
|
| 35 |
+
-------
|
| 36 |
+
attractors : generator of sets
|
| 37 |
+
A generator of sets of nodes, one for each attracting component of G.
|
| 38 |
+
|
| 39 |
+
Raises
|
| 40 |
+
------
|
| 41 |
+
NetworkXNotImplemented
|
| 42 |
+
If the input graph is undirected.
|
| 43 |
+
|
| 44 |
+
See Also
|
| 45 |
+
--------
|
| 46 |
+
number_attracting_components
|
| 47 |
+
is_attracting_component
|
| 48 |
+
|
| 49 |
+
"""
|
| 50 |
+
scc = list(nx.strongly_connected_components(G))
|
| 51 |
+
cG = nx.condensation(G, scc)
|
| 52 |
+
for n in cG:
|
| 53 |
+
if cG.out_degree(n) == 0:
|
| 54 |
+
yield scc[n]
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
@not_implemented_for("undirected")
|
| 58 |
+
@nx._dispatchable
|
| 59 |
+
def number_attracting_components(G):
|
| 60 |
+
"""Returns the number of attracting components in `G`.
|
| 61 |
+
|
| 62 |
+
Parameters
|
| 63 |
+
----------
|
| 64 |
+
G : DiGraph, MultiDiGraph
|
| 65 |
+
The graph to be analyzed.
|
| 66 |
+
|
| 67 |
+
Returns
|
| 68 |
+
-------
|
| 69 |
+
n : int
|
| 70 |
+
The number of attracting components in G.
|
| 71 |
+
|
| 72 |
+
Raises
|
| 73 |
+
------
|
| 74 |
+
NetworkXNotImplemented
|
| 75 |
+
If the input graph is undirected.
|
| 76 |
+
|
| 77 |
+
See Also
|
| 78 |
+
--------
|
| 79 |
+
attracting_components
|
| 80 |
+
is_attracting_component
|
| 81 |
+
|
| 82 |
+
"""
|
| 83 |
+
return sum(1 for ac in attracting_components(G))
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
@not_implemented_for("undirected")
|
| 87 |
+
@nx._dispatchable
|
| 88 |
+
def is_attracting_component(G):
|
| 89 |
+
"""Returns True if `G` consists of a single attracting component.
|
| 90 |
+
|
| 91 |
+
Parameters
|
| 92 |
+
----------
|
| 93 |
+
G : DiGraph, MultiDiGraph
|
| 94 |
+
The graph to be analyzed.
|
| 95 |
+
|
| 96 |
+
Returns
|
| 97 |
+
-------
|
| 98 |
+
attracting : bool
|
| 99 |
+
True if `G` has a single attracting component. Otherwise, False.
|
| 100 |
+
|
| 101 |
+
Raises
|
| 102 |
+
------
|
| 103 |
+
NetworkXNotImplemented
|
| 104 |
+
If the input graph is undirected.
|
| 105 |
+
|
| 106 |
+
See Also
|
| 107 |
+
--------
|
| 108 |
+
attracting_components
|
| 109 |
+
number_attracting_components
|
| 110 |
+
|
| 111 |
+
"""
|
| 112 |
+
ac = list(attracting_components(G))
|
| 113 |
+
if len(ac) == 1:
|
| 114 |
+
return len(ac[0]) == len(G)
|
| 115 |
+
return False
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/biconnected.py
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Biconnected components and articulation points."""
|
| 2 |
+
|
| 3 |
+
from itertools import chain
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
from networkx.utils.decorators import not_implemented_for
|
| 7 |
+
|
| 8 |
+
__all__ = [
|
| 9 |
+
"biconnected_components",
|
| 10 |
+
"biconnected_component_edges",
|
| 11 |
+
"is_biconnected",
|
| 12 |
+
"articulation_points",
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@not_implemented_for("directed")
|
| 17 |
+
@nx._dispatchable
|
| 18 |
+
def is_biconnected(G):
|
| 19 |
+
"""Returns True if the graph is biconnected, False otherwise.
|
| 20 |
+
|
| 21 |
+
A graph is biconnected if, and only if, it cannot be disconnected by
|
| 22 |
+
removing only one node (and all edges incident on that node). If
|
| 23 |
+
removing a node increases the number of disconnected components
|
| 24 |
+
in the graph, that node is called an articulation point, or cut
|
| 25 |
+
vertex. A biconnected graph has no articulation points.
|
| 26 |
+
|
| 27 |
+
Parameters
|
| 28 |
+
----------
|
| 29 |
+
G : NetworkX Graph
|
| 30 |
+
An undirected graph.
|
| 31 |
+
|
| 32 |
+
Returns
|
| 33 |
+
-------
|
| 34 |
+
biconnected : bool
|
| 35 |
+
True if the graph is biconnected, False otherwise.
|
| 36 |
+
|
| 37 |
+
Raises
|
| 38 |
+
------
|
| 39 |
+
NetworkXNotImplemented
|
| 40 |
+
If the input graph is not undirected.
|
| 41 |
+
|
| 42 |
+
Examples
|
| 43 |
+
--------
|
| 44 |
+
>>> G = nx.path_graph(4)
|
| 45 |
+
>>> print(nx.is_biconnected(G))
|
| 46 |
+
False
|
| 47 |
+
>>> G.add_edge(0, 3)
|
| 48 |
+
>>> print(nx.is_biconnected(G))
|
| 49 |
+
True
|
| 50 |
+
|
| 51 |
+
See Also
|
| 52 |
+
--------
|
| 53 |
+
biconnected_components
|
| 54 |
+
articulation_points
|
| 55 |
+
biconnected_component_edges
|
| 56 |
+
is_strongly_connected
|
| 57 |
+
is_weakly_connected
|
| 58 |
+
is_connected
|
| 59 |
+
is_semiconnected
|
| 60 |
+
|
| 61 |
+
Notes
|
| 62 |
+
-----
|
| 63 |
+
The algorithm to find articulation points and biconnected
|
| 64 |
+
components is implemented using a non-recursive depth-first-search
|
| 65 |
+
(DFS) that keeps track of the highest level that back edges reach
|
| 66 |
+
in the DFS tree. A node `n` is an articulation point if, and only
|
| 67 |
+
if, there exists a subtree rooted at `n` such that there is no
|
| 68 |
+
back edge from any successor of `n` that links to a predecessor of
|
| 69 |
+
`n` in the DFS tree. By keeping track of all the edges traversed
|
| 70 |
+
by the DFS we can obtain the biconnected components because all
|
| 71 |
+
edges of a bicomponent will be traversed consecutively between
|
| 72 |
+
articulation points.
|
| 73 |
+
|
| 74 |
+
References
|
| 75 |
+
----------
|
| 76 |
+
.. [1] Hopcroft, J.; Tarjan, R. (1973).
|
| 77 |
+
"Efficient algorithms for graph manipulation".
|
| 78 |
+
Communications of the ACM 16: 372–378. doi:10.1145/362248.362272
|
| 79 |
+
|
| 80 |
+
"""
|
| 81 |
+
bccs = biconnected_components(G)
|
| 82 |
+
try:
|
| 83 |
+
bcc = next(bccs)
|
| 84 |
+
except StopIteration:
|
| 85 |
+
# No bicomponents (empty graph?)
|
| 86 |
+
return False
|
| 87 |
+
try:
|
| 88 |
+
next(bccs)
|
| 89 |
+
except StopIteration:
|
| 90 |
+
# Only one bicomponent
|
| 91 |
+
return len(bcc) == len(G)
|
| 92 |
+
else:
|
| 93 |
+
# Multiple bicomponents
|
| 94 |
+
return False
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
@not_implemented_for("directed")
|
| 98 |
+
@nx._dispatchable
|
| 99 |
+
def biconnected_component_edges(G):
|
| 100 |
+
"""Returns a generator of lists of edges, one list for each biconnected
|
| 101 |
+
component of the input graph.
|
| 102 |
+
|
| 103 |
+
Biconnected components are maximal subgraphs such that the removal of a
|
| 104 |
+
node (and all edges incident on that node) will not disconnect the
|
| 105 |
+
subgraph. Note that nodes may be part of more than one biconnected
|
| 106 |
+
component. Those nodes are articulation points, or cut vertices.
|
| 107 |
+
However, each edge belongs to one, and only one, biconnected component.
|
| 108 |
+
|
| 109 |
+
Notice that by convention a dyad is considered a biconnected component.
|
| 110 |
+
|
| 111 |
+
Parameters
|
| 112 |
+
----------
|
| 113 |
+
G : NetworkX Graph
|
| 114 |
+
An undirected graph.
|
| 115 |
+
|
| 116 |
+
Returns
|
| 117 |
+
-------
|
| 118 |
+
edges : generator of lists
|
| 119 |
+
Generator of lists of edges, one list for each bicomponent.
|
| 120 |
+
|
| 121 |
+
Raises
|
| 122 |
+
------
|
| 123 |
+
NetworkXNotImplemented
|
| 124 |
+
If the input graph is not undirected.
|
| 125 |
+
|
| 126 |
+
Examples
|
| 127 |
+
--------
|
| 128 |
+
>>> G = nx.barbell_graph(4, 2)
|
| 129 |
+
>>> print(nx.is_biconnected(G))
|
| 130 |
+
False
|
| 131 |
+
>>> bicomponents_edges = list(nx.biconnected_component_edges(G))
|
| 132 |
+
>>> len(bicomponents_edges)
|
| 133 |
+
5
|
| 134 |
+
>>> G.add_edge(2, 8)
|
| 135 |
+
>>> print(nx.is_biconnected(G))
|
| 136 |
+
True
|
| 137 |
+
>>> bicomponents_edges = list(nx.biconnected_component_edges(G))
|
| 138 |
+
>>> len(bicomponents_edges)
|
| 139 |
+
1
|
| 140 |
+
|
| 141 |
+
See Also
|
| 142 |
+
--------
|
| 143 |
+
is_biconnected,
|
| 144 |
+
biconnected_components,
|
| 145 |
+
articulation_points,
|
| 146 |
+
|
| 147 |
+
Notes
|
| 148 |
+
-----
|
| 149 |
+
The algorithm to find articulation points and biconnected
|
| 150 |
+
components is implemented using a non-recursive depth-first-search
|
| 151 |
+
(DFS) that keeps track of the highest level that back edges reach
|
| 152 |
+
in the DFS tree. A node `n` is an articulation point if, and only
|
| 153 |
+
if, there exists a subtree rooted at `n` such that there is no
|
| 154 |
+
back edge from any successor of `n` that links to a predecessor of
|
| 155 |
+
`n` in the DFS tree. By keeping track of all the edges traversed
|
| 156 |
+
by the DFS we can obtain the biconnected components because all
|
| 157 |
+
edges of a bicomponent will be traversed consecutively between
|
| 158 |
+
articulation points.
|
| 159 |
+
|
| 160 |
+
References
|
| 161 |
+
----------
|
| 162 |
+
.. [1] Hopcroft, J.; Tarjan, R. (1973).
|
| 163 |
+
"Efficient algorithms for graph manipulation".
|
| 164 |
+
Communications of the ACM 16: 372–378. doi:10.1145/362248.362272
|
| 165 |
+
|
| 166 |
+
"""
|
| 167 |
+
yield from _biconnected_dfs(G, components=True)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
@not_implemented_for("directed")
|
| 171 |
+
@nx._dispatchable
|
| 172 |
+
def biconnected_components(G):
|
| 173 |
+
"""Returns a generator of sets of nodes, one set for each biconnected
|
| 174 |
+
component of the graph
|
| 175 |
+
|
| 176 |
+
Biconnected components are maximal subgraphs such that the removal of a
|
| 177 |
+
node (and all edges incident on that node) will not disconnect the
|
| 178 |
+
subgraph. Note that nodes may be part of more than one biconnected
|
| 179 |
+
component. Those nodes are articulation points, or cut vertices. The
|
| 180 |
+
removal of articulation points will increase the number of connected
|
| 181 |
+
components of the graph.
|
| 182 |
+
|
| 183 |
+
Notice that by convention a dyad is considered a biconnected component.
|
| 184 |
+
|
| 185 |
+
Parameters
|
| 186 |
+
----------
|
| 187 |
+
G : NetworkX Graph
|
| 188 |
+
An undirected graph.
|
| 189 |
+
|
| 190 |
+
Returns
|
| 191 |
+
-------
|
| 192 |
+
nodes : generator
|
| 193 |
+
Generator of sets of nodes, one set for each biconnected component.
|
| 194 |
+
|
| 195 |
+
Raises
|
| 196 |
+
------
|
| 197 |
+
NetworkXNotImplemented
|
| 198 |
+
If the input graph is not undirected.
|
| 199 |
+
|
| 200 |
+
Examples
|
| 201 |
+
--------
|
| 202 |
+
>>> G = nx.lollipop_graph(5, 1)
|
| 203 |
+
>>> print(nx.is_biconnected(G))
|
| 204 |
+
False
|
| 205 |
+
>>> bicomponents = list(nx.biconnected_components(G))
|
| 206 |
+
>>> len(bicomponents)
|
| 207 |
+
2
|
| 208 |
+
>>> G.add_edge(0, 5)
|
| 209 |
+
>>> print(nx.is_biconnected(G))
|
| 210 |
+
True
|
| 211 |
+
>>> bicomponents = list(nx.biconnected_components(G))
|
| 212 |
+
>>> len(bicomponents)
|
| 213 |
+
1
|
| 214 |
+
|
| 215 |
+
You can generate a sorted list of biconnected components, largest
|
| 216 |
+
first, using sort.
|
| 217 |
+
|
| 218 |
+
>>> G.remove_edge(0, 5)
|
| 219 |
+
>>> [len(c) for c in sorted(nx.biconnected_components(G), key=len, reverse=True)]
|
| 220 |
+
[5, 2]
|
| 221 |
+
|
| 222 |
+
If you only want the largest connected component, it's more
|
| 223 |
+
efficient to use max instead of sort.
|
| 224 |
+
|
| 225 |
+
>>> Gc = max(nx.biconnected_components(G), key=len)
|
| 226 |
+
|
| 227 |
+
To create the components as subgraphs use:
|
| 228 |
+
``(G.subgraph(c).copy() for c in biconnected_components(G))``
|
| 229 |
+
|
| 230 |
+
See Also
|
| 231 |
+
--------
|
| 232 |
+
is_biconnected
|
| 233 |
+
articulation_points
|
| 234 |
+
biconnected_component_edges
|
| 235 |
+
k_components : this function is a special case where k=2
|
| 236 |
+
bridge_components : similar to this function, but is defined using
|
| 237 |
+
2-edge-connectivity instead of 2-node-connectivity.
|
| 238 |
+
|
| 239 |
+
Notes
|
| 240 |
+
-----
|
| 241 |
+
The algorithm to find articulation points and biconnected
|
| 242 |
+
components is implemented using a non-recursive depth-first-search
|
| 243 |
+
(DFS) that keeps track of the highest level that back edges reach
|
| 244 |
+
in the DFS tree. A node `n` is an articulation point if, and only
|
| 245 |
+
if, there exists a subtree rooted at `n` such that there is no
|
| 246 |
+
back edge from any successor of `n` that links to a predecessor of
|
| 247 |
+
`n` in the DFS tree. By keeping track of all the edges traversed
|
| 248 |
+
by the DFS we can obtain the biconnected components because all
|
| 249 |
+
edges of a bicomponent will be traversed consecutively between
|
| 250 |
+
articulation points.
|
| 251 |
+
|
| 252 |
+
References
|
| 253 |
+
----------
|
| 254 |
+
.. [1] Hopcroft, J.; Tarjan, R. (1973).
|
| 255 |
+
"Efficient algorithms for graph manipulation".
|
| 256 |
+
Communications of the ACM 16: 372–378. doi:10.1145/362248.362272
|
| 257 |
+
|
| 258 |
+
"""
|
| 259 |
+
for comp in _biconnected_dfs(G, components=True):
|
| 260 |
+
yield set(chain.from_iterable(comp))
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
@not_implemented_for("directed")
|
| 264 |
+
@nx._dispatchable
|
| 265 |
+
def articulation_points(G):
|
| 266 |
+
"""Yield the articulation points, or cut vertices, of a graph.
|
| 267 |
+
|
| 268 |
+
An articulation point or cut vertex is any node whose removal (along with
|
| 269 |
+
all its incident edges) increases the number of connected components of
|
| 270 |
+
a graph. An undirected connected graph without articulation points is
|
| 271 |
+
biconnected. Articulation points belong to more than one biconnected
|
| 272 |
+
component of a graph.
|
| 273 |
+
|
| 274 |
+
Notice that by convention a dyad is considered a biconnected component.
|
| 275 |
+
|
| 276 |
+
Parameters
|
| 277 |
+
----------
|
| 278 |
+
G : NetworkX Graph
|
| 279 |
+
An undirected graph.
|
| 280 |
+
|
| 281 |
+
Yields
|
| 282 |
+
------
|
| 283 |
+
node
|
| 284 |
+
An articulation point in the graph.
|
| 285 |
+
|
| 286 |
+
Raises
|
| 287 |
+
------
|
| 288 |
+
NetworkXNotImplemented
|
| 289 |
+
If the input graph is not undirected.
|
| 290 |
+
|
| 291 |
+
Examples
|
| 292 |
+
--------
|
| 293 |
+
|
| 294 |
+
>>> G = nx.barbell_graph(4, 2)
|
| 295 |
+
>>> print(nx.is_biconnected(G))
|
| 296 |
+
False
|
| 297 |
+
>>> len(list(nx.articulation_points(G)))
|
| 298 |
+
4
|
| 299 |
+
>>> G.add_edge(2, 8)
|
| 300 |
+
>>> print(nx.is_biconnected(G))
|
| 301 |
+
True
|
| 302 |
+
>>> len(list(nx.articulation_points(G)))
|
| 303 |
+
0
|
| 304 |
+
|
| 305 |
+
See Also
|
| 306 |
+
--------
|
| 307 |
+
is_biconnected
|
| 308 |
+
biconnected_components
|
| 309 |
+
biconnected_component_edges
|
| 310 |
+
|
| 311 |
+
Notes
|
| 312 |
+
-----
|
| 313 |
+
The algorithm to find articulation points and biconnected
|
| 314 |
+
components is implemented using a non-recursive depth-first-search
|
| 315 |
+
(DFS) that keeps track of the highest level that back edges reach
|
| 316 |
+
in the DFS tree. A node `n` is an articulation point if, and only
|
| 317 |
+
if, there exists a subtree rooted at `n` such that there is no
|
| 318 |
+
back edge from any successor of `n` that links to a predecessor of
|
| 319 |
+
`n` in the DFS tree. By keeping track of all the edges traversed
|
| 320 |
+
by the DFS we can obtain the biconnected components because all
|
| 321 |
+
edges of a bicomponent will be traversed consecutively between
|
| 322 |
+
articulation points.
|
| 323 |
+
|
| 324 |
+
References
|
| 325 |
+
----------
|
| 326 |
+
.. [1] Hopcroft, J.; Tarjan, R. (1973).
|
| 327 |
+
"Efficient algorithms for graph manipulation".
|
| 328 |
+
Communications of the ACM 16: 372–378. doi:10.1145/362248.362272
|
| 329 |
+
|
| 330 |
+
"""
|
| 331 |
+
seen = set()
|
| 332 |
+
for articulation in _biconnected_dfs(G, components=False):
|
| 333 |
+
if articulation not in seen:
|
| 334 |
+
seen.add(articulation)
|
| 335 |
+
yield articulation
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
@not_implemented_for("directed")
|
| 339 |
+
def _biconnected_dfs(G, components=True):
|
| 340 |
+
# depth-first search algorithm to generate articulation points
|
| 341 |
+
# and biconnected components
|
| 342 |
+
visited = set()
|
| 343 |
+
for start in G:
|
| 344 |
+
if start in visited:
|
| 345 |
+
continue
|
| 346 |
+
discovery = {start: 0} # time of first discovery of node during search
|
| 347 |
+
low = {start: 0}
|
| 348 |
+
root_children = 0
|
| 349 |
+
visited.add(start)
|
| 350 |
+
edge_stack = []
|
| 351 |
+
stack = [(start, start, iter(G[start]))]
|
| 352 |
+
edge_index = {}
|
| 353 |
+
while stack:
|
| 354 |
+
grandparent, parent, children = stack[-1]
|
| 355 |
+
try:
|
| 356 |
+
child = next(children)
|
| 357 |
+
if grandparent == child:
|
| 358 |
+
continue
|
| 359 |
+
if child in visited:
|
| 360 |
+
if discovery[child] <= discovery[parent]: # back edge
|
| 361 |
+
low[parent] = min(low[parent], discovery[child])
|
| 362 |
+
if components:
|
| 363 |
+
edge_index[parent, child] = len(edge_stack)
|
| 364 |
+
edge_stack.append((parent, child))
|
| 365 |
+
else:
|
| 366 |
+
low[child] = discovery[child] = len(discovery)
|
| 367 |
+
visited.add(child)
|
| 368 |
+
stack.append((parent, child, iter(G[child])))
|
| 369 |
+
if components:
|
| 370 |
+
edge_index[parent, child] = len(edge_stack)
|
| 371 |
+
edge_stack.append((parent, child))
|
| 372 |
+
|
| 373 |
+
except StopIteration:
|
| 374 |
+
stack.pop()
|
| 375 |
+
if len(stack) > 1:
|
| 376 |
+
if low[parent] >= discovery[grandparent]:
|
| 377 |
+
if components:
|
| 378 |
+
ind = edge_index[grandparent, parent]
|
| 379 |
+
yield edge_stack[ind:]
|
| 380 |
+
del edge_stack[ind:]
|
| 381 |
+
|
| 382 |
+
else:
|
| 383 |
+
yield grandparent
|
| 384 |
+
low[grandparent] = min(low[parent], low[grandparent])
|
| 385 |
+
elif stack: # length 1 so grandparent is root
|
| 386 |
+
root_children += 1
|
| 387 |
+
if components:
|
| 388 |
+
ind = edge_index[grandparent, parent]
|
| 389 |
+
yield edge_stack[ind:]
|
| 390 |
+
del edge_stack[ind:]
|
| 391 |
+
if not components:
|
| 392 |
+
# root node is articulation point if it has more than 1 child
|
| 393 |
+
if root_children > 1:
|
| 394 |
+
yield start
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/connected.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Connected components."""
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.utils.decorators import not_implemented_for
|
| 5 |
+
|
| 6 |
+
from ...utils import arbitrary_element
|
| 7 |
+
|
| 8 |
+
__all__ = [
|
| 9 |
+
"number_connected_components",
|
| 10 |
+
"connected_components",
|
| 11 |
+
"is_connected",
|
| 12 |
+
"node_connected_component",
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@not_implemented_for("directed")
|
| 17 |
+
@nx._dispatchable
|
| 18 |
+
def connected_components(G):
|
| 19 |
+
"""Generate connected components.
|
| 20 |
+
|
| 21 |
+
Parameters
|
| 22 |
+
----------
|
| 23 |
+
G : NetworkX graph
|
| 24 |
+
An undirected graph
|
| 25 |
+
|
| 26 |
+
Returns
|
| 27 |
+
-------
|
| 28 |
+
comp : generator of sets
|
| 29 |
+
A generator of sets of nodes, one for each component of G.
|
| 30 |
+
|
| 31 |
+
Raises
|
| 32 |
+
------
|
| 33 |
+
NetworkXNotImplemented
|
| 34 |
+
If G is directed.
|
| 35 |
+
|
| 36 |
+
Examples
|
| 37 |
+
--------
|
| 38 |
+
Generate a sorted list of connected components, largest first.
|
| 39 |
+
|
| 40 |
+
>>> G = nx.path_graph(4)
|
| 41 |
+
>>> nx.add_path(G, [10, 11, 12])
|
| 42 |
+
>>> [len(c) for c in sorted(nx.connected_components(G), key=len, reverse=True)]
|
| 43 |
+
[4, 3]
|
| 44 |
+
|
| 45 |
+
If you only want the largest connected component, it's more
|
| 46 |
+
efficient to use max instead of sort.
|
| 47 |
+
|
| 48 |
+
>>> largest_cc = max(nx.connected_components(G), key=len)
|
| 49 |
+
|
| 50 |
+
To create the induced subgraph of each component use:
|
| 51 |
+
|
| 52 |
+
>>> S = [G.subgraph(c).copy() for c in nx.connected_components(G)]
|
| 53 |
+
|
| 54 |
+
See Also
|
| 55 |
+
--------
|
| 56 |
+
strongly_connected_components
|
| 57 |
+
weakly_connected_components
|
| 58 |
+
|
| 59 |
+
Notes
|
| 60 |
+
-----
|
| 61 |
+
For undirected graphs only.
|
| 62 |
+
|
| 63 |
+
"""
|
| 64 |
+
seen = set()
|
| 65 |
+
n = len(G)
|
| 66 |
+
for v in G:
|
| 67 |
+
if v not in seen:
|
| 68 |
+
c = _plain_bfs(G, n, v)
|
| 69 |
+
seen.update(c)
|
| 70 |
+
yield c
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
@not_implemented_for("directed")
|
| 74 |
+
@nx._dispatchable
|
| 75 |
+
def number_connected_components(G):
|
| 76 |
+
"""Returns the number of connected components.
|
| 77 |
+
|
| 78 |
+
Parameters
|
| 79 |
+
----------
|
| 80 |
+
G : NetworkX graph
|
| 81 |
+
An undirected graph.
|
| 82 |
+
|
| 83 |
+
Returns
|
| 84 |
+
-------
|
| 85 |
+
n : integer
|
| 86 |
+
Number of connected components
|
| 87 |
+
|
| 88 |
+
Raises
|
| 89 |
+
------
|
| 90 |
+
NetworkXNotImplemented
|
| 91 |
+
If G is directed.
|
| 92 |
+
|
| 93 |
+
Examples
|
| 94 |
+
--------
|
| 95 |
+
>>> G = nx.Graph([(0, 1), (1, 2), (5, 6), (3, 4)])
|
| 96 |
+
>>> nx.number_connected_components(G)
|
| 97 |
+
3
|
| 98 |
+
|
| 99 |
+
See Also
|
| 100 |
+
--------
|
| 101 |
+
connected_components
|
| 102 |
+
number_weakly_connected_components
|
| 103 |
+
number_strongly_connected_components
|
| 104 |
+
|
| 105 |
+
Notes
|
| 106 |
+
-----
|
| 107 |
+
For undirected graphs only.
|
| 108 |
+
|
| 109 |
+
"""
|
| 110 |
+
return sum(1 for cc in connected_components(G))
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
@not_implemented_for("directed")
|
| 114 |
+
@nx._dispatchable
|
| 115 |
+
def is_connected(G):
|
| 116 |
+
"""Returns True if the graph is connected, False otherwise.
|
| 117 |
+
|
| 118 |
+
Parameters
|
| 119 |
+
----------
|
| 120 |
+
G : NetworkX Graph
|
| 121 |
+
An undirected graph.
|
| 122 |
+
|
| 123 |
+
Returns
|
| 124 |
+
-------
|
| 125 |
+
connected : bool
|
| 126 |
+
True if the graph is connected, false otherwise.
|
| 127 |
+
|
| 128 |
+
Raises
|
| 129 |
+
------
|
| 130 |
+
NetworkXNotImplemented
|
| 131 |
+
If G is directed.
|
| 132 |
+
|
| 133 |
+
Examples
|
| 134 |
+
--------
|
| 135 |
+
>>> G = nx.path_graph(4)
|
| 136 |
+
>>> print(nx.is_connected(G))
|
| 137 |
+
True
|
| 138 |
+
|
| 139 |
+
See Also
|
| 140 |
+
--------
|
| 141 |
+
is_strongly_connected
|
| 142 |
+
is_weakly_connected
|
| 143 |
+
is_semiconnected
|
| 144 |
+
is_biconnected
|
| 145 |
+
connected_components
|
| 146 |
+
|
| 147 |
+
Notes
|
| 148 |
+
-----
|
| 149 |
+
For undirected graphs only.
|
| 150 |
+
|
| 151 |
+
"""
|
| 152 |
+
n = len(G)
|
| 153 |
+
if n == 0:
|
| 154 |
+
raise nx.NetworkXPointlessConcept(
|
| 155 |
+
"Connectivity is undefined for the null graph."
|
| 156 |
+
)
|
| 157 |
+
return sum(1 for node in _plain_bfs(G, n, arbitrary_element(G))) == len(G)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
@not_implemented_for("directed")
|
| 161 |
+
@nx._dispatchable
|
| 162 |
+
def node_connected_component(G, n):
|
| 163 |
+
"""Returns the set of nodes in the component of graph containing node n.
|
| 164 |
+
|
| 165 |
+
Parameters
|
| 166 |
+
----------
|
| 167 |
+
G : NetworkX Graph
|
| 168 |
+
An undirected graph.
|
| 169 |
+
|
| 170 |
+
n : node label
|
| 171 |
+
A node in G
|
| 172 |
+
|
| 173 |
+
Returns
|
| 174 |
+
-------
|
| 175 |
+
comp : set
|
| 176 |
+
A set of nodes in the component of G containing node n.
|
| 177 |
+
|
| 178 |
+
Raises
|
| 179 |
+
------
|
| 180 |
+
NetworkXNotImplemented
|
| 181 |
+
If G is directed.
|
| 182 |
+
|
| 183 |
+
Examples
|
| 184 |
+
--------
|
| 185 |
+
>>> G = nx.Graph([(0, 1), (1, 2), (5, 6), (3, 4)])
|
| 186 |
+
>>> nx.node_connected_component(G, 0) # nodes of component that contains node 0
|
| 187 |
+
{0, 1, 2}
|
| 188 |
+
|
| 189 |
+
See Also
|
| 190 |
+
--------
|
| 191 |
+
connected_components
|
| 192 |
+
|
| 193 |
+
Notes
|
| 194 |
+
-----
|
| 195 |
+
For undirected graphs only.
|
| 196 |
+
|
| 197 |
+
"""
|
| 198 |
+
return _plain_bfs(G, len(G), n)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def _plain_bfs(G, n, source):
|
| 202 |
+
"""A fast BFS node generator"""
|
| 203 |
+
adj = G._adj
|
| 204 |
+
seen = {source}
|
| 205 |
+
nextlevel = [source]
|
| 206 |
+
while nextlevel:
|
| 207 |
+
thislevel = nextlevel
|
| 208 |
+
nextlevel = []
|
| 209 |
+
for v in thislevel:
|
| 210 |
+
for w in adj[v]:
|
| 211 |
+
if w not in seen:
|
| 212 |
+
seen.add(w)
|
| 213 |
+
nextlevel.append(w)
|
| 214 |
+
if len(seen) == n:
|
| 215 |
+
return seen
|
| 216 |
+
return seen
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/semiconnected.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Semiconnectedness."""
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.utils import not_implemented_for, pairwise
|
| 5 |
+
|
| 6 |
+
__all__ = ["is_semiconnected"]
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@not_implemented_for("undirected")
|
| 10 |
+
@nx._dispatchable
|
| 11 |
+
def is_semiconnected(G):
|
| 12 |
+
r"""Returns True if the graph is semiconnected, False otherwise.
|
| 13 |
+
|
| 14 |
+
A graph is semiconnected if and only if for any pair of nodes, either one
|
| 15 |
+
is reachable from the other, or they are mutually reachable.
|
| 16 |
+
|
| 17 |
+
This function uses a theorem that states that a DAG is semiconnected
|
| 18 |
+
if for any topological sort, for node $v_n$ in that sort, there is an
|
| 19 |
+
edge $(v_i, v_{i+1})$. That allows us to check if a non-DAG `G` is
|
| 20 |
+
semiconnected by condensing the graph: i.e. constructing a new graph `H`
|
| 21 |
+
with nodes being the strongly connected components of `G`, and edges
|
| 22 |
+
(scc_1, scc_2) if there is a edge $(v_1, v_2)$ in `G` for some
|
| 23 |
+
$v_1 \in scc_1$ and $v_2 \in scc_2$. That results in a DAG, so we compute
|
| 24 |
+
the topological sort of `H` and check if for every $n$ there is an edge
|
| 25 |
+
$(scc_n, scc_{n+1})$.
|
| 26 |
+
|
| 27 |
+
Parameters
|
| 28 |
+
----------
|
| 29 |
+
G : NetworkX graph
|
| 30 |
+
A directed graph.
|
| 31 |
+
|
| 32 |
+
Returns
|
| 33 |
+
-------
|
| 34 |
+
semiconnected : bool
|
| 35 |
+
True if the graph is semiconnected, False otherwise.
|
| 36 |
+
|
| 37 |
+
Raises
|
| 38 |
+
------
|
| 39 |
+
NetworkXNotImplemented
|
| 40 |
+
If the input graph is undirected.
|
| 41 |
+
|
| 42 |
+
NetworkXPointlessConcept
|
| 43 |
+
If the graph is empty.
|
| 44 |
+
|
| 45 |
+
Examples
|
| 46 |
+
--------
|
| 47 |
+
>>> G = nx.path_graph(4, create_using=nx.DiGraph())
|
| 48 |
+
>>> print(nx.is_semiconnected(G))
|
| 49 |
+
True
|
| 50 |
+
>>> G = nx.DiGraph([(1, 2), (3, 2)])
|
| 51 |
+
>>> print(nx.is_semiconnected(G))
|
| 52 |
+
False
|
| 53 |
+
|
| 54 |
+
See Also
|
| 55 |
+
--------
|
| 56 |
+
is_strongly_connected
|
| 57 |
+
is_weakly_connected
|
| 58 |
+
is_connected
|
| 59 |
+
is_biconnected
|
| 60 |
+
"""
|
| 61 |
+
if len(G) == 0:
|
| 62 |
+
raise nx.NetworkXPointlessConcept(
|
| 63 |
+
"Connectivity is undefined for the null graph."
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
if not nx.is_weakly_connected(G):
|
| 67 |
+
return False
|
| 68 |
+
|
| 69 |
+
H = nx.condensation(G)
|
| 70 |
+
|
| 71 |
+
return all(H.has_edge(u, v) for u, v in pairwise(nx.topological_sort(H)))
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/strongly_connected.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Strongly connected components."""
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.utils.decorators import not_implemented_for
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
"number_strongly_connected_components",
|
| 8 |
+
"strongly_connected_components",
|
| 9 |
+
"is_strongly_connected",
|
| 10 |
+
"kosaraju_strongly_connected_components",
|
| 11 |
+
"condensation",
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@not_implemented_for("undirected")
|
| 16 |
+
@nx._dispatchable
|
| 17 |
+
def strongly_connected_components(G):
|
| 18 |
+
"""Generate nodes in strongly connected components of graph.
|
| 19 |
+
|
| 20 |
+
Parameters
|
| 21 |
+
----------
|
| 22 |
+
G : NetworkX Graph
|
| 23 |
+
A directed graph.
|
| 24 |
+
|
| 25 |
+
Returns
|
| 26 |
+
-------
|
| 27 |
+
comp : generator of sets
|
| 28 |
+
A generator of sets of nodes, one for each strongly connected
|
| 29 |
+
component of G.
|
| 30 |
+
|
| 31 |
+
Raises
|
| 32 |
+
------
|
| 33 |
+
NetworkXNotImplemented
|
| 34 |
+
If G is undirected.
|
| 35 |
+
|
| 36 |
+
Examples
|
| 37 |
+
--------
|
| 38 |
+
Generate a sorted list of strongly connected components, largest first.
|
| 39 |
+
|
| 40 |
+
>>> G = nx.cycle_graph(4, create_using=nx.DiGraph())
|
| 41 |
+
>>> nx.add_cycle(G, [10, 11, 12])
|
| 42 |
+
>>> [
|
| 43 |
+
... len(c)
|
| 44 |
+
... for c in sorted(nx.strongly_connected_components(G), key=len, reverse=True)
|
| 45 |
+
... ]
|
| 46 |
+
[4, 3]
|
| 47 |
+
|
| 48 |
+
If you only want the largest component, it's more efficient to
|
| 49 |
+
use max instead of sort.
|
| 50 |
+
|
| 51 |
+
>>> largest = max(nx.strongly_connected_components(G), key=len)
|
| 52 |
+
|
| 53 |
+
See Also
|
| 54 |
+
--------
|
| 55 |
+
connected_components
|
| 56 |
+
weakly_connected_components
|
| 57 |
+
kosaraju_strongly_connected_components
|
| 58 |
+
|
| 59 |
+
Notes
|
| 60 |
+
-----
|
| 61 |
+
Uses Tarjan's algorithm[1]_ with Nuutila's modifications[2]_.
|
| 62 |
+
Nonrecursive version of algorithm.
|
| 63 |
+
|
| 64 |
+
References
|
| 65 |
+
----------
|
| 66 |
+
.. [1] Depth-first search and linear graph algorithms, R. Tarjan
|
| 67 |
+
SIAM Journal of Computing 1(2):146-160, (1972).
|
| 68 |
+
|
| 69 |
+
.. [2] On finding the strongly connected components in a directed graph.
|
| 70 |
+
E. Nuutila and E. Soisalon-Soinen
|
| 71 |
+
Information Processing Letters 49(1): 9-14, (1994)..
|
| 72 |
+
|
| 73 |
+
"""
|
| 74 |
+
preorder = {}
|
| 75 |
+
lowlink = {}
|
| 76 |
+
scc_found = set()
|
| 77 |
+
scc_queue = []
|
| 78 |
+
i = 0 # Preorder counter
|
| 79 |
+
neighbors = {v: iter(G[v]) for v in G}
|
| 80 |
+
for source in G:
|
| 81 |
+
if source not in scc_found:
|
| 82 |
+
queue = [source]
|
| 83 |
+
while queue:
|
| 84 |
+
v = queue[-1]
|
| 85 |
+
if v not in preorder:
|
| 86 |
+
i = i + 1
|
| 87 |
+
preorder[v] = i
|
| 88 |
+
done = True
|
| 89 |
+
for w in neighbors[v]:
|
| 90 |
+
if w not in preorder:
|
| 91 |
+
queue.append(w)
|
| 92 |
+
done = False
|
| 93 |
+
break
|
| 94 |
+
if done:
|
| 95 |
+
lowlink[v] = preorder[v]
|
| 96 |
+
for w in G[v]:
|
| 97 |
+
if w not in scc_found:
|
| 98 |
+
if preorder[w] > preorder[v]:
|
| 99 |
+
lowlink[v] = min([lowlink[v], lowlink[w]])
|
| 100 |
+
else:
|
| 101 |
+
lowlink[v] = min([lowlink[v], preorder[w]])
|
| 102 |
+
queue.pop()
|
| 103 |
+
if lowlink[v] == preorder[v]:
|
| 104 |
+
scc = {v}
|
| 105 |
+
while scc_queue and preorder[scc_queue[-1]] > preorder[v]:
|
| 106 |
+
k = scc_queue.pop()
|
| 107 |
+
scc.add(k)
|
| 108 |
+
scc_found.update(scc)
|
| 109 |
+
yield scc
|
| 110 |
+
else:
|
| 111 |
+
scc_queue.append(v)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
@not_implemented_for("undirected")
|
| 115 |
+
@nx._dispatchable
|
| 116 |
+
def kosaraju_strongly_connected_components(G, source=None):
|
| 117 |
+
"""Generate nodes in strongly connected components of graph.
|
| 118 |
+
|
| 119 |
+
Parameters
|
| 120 |
+
----------
|
| 121 |
+
G : NetworkX Graph
|
| 122 |
+
A directed graph.
|
| 123 |
+
|
| 124 |
+
Returns
|
| 125 |
+
-------
|
| 126 |
+
comp : generator of sets
|
| 127 |
+
A generator of sets of nodes, one for each strongly connected
|
| 128 |
+
component of G.
|
| 129 |
+
|
| 130 |
+
Raises
|
| 131 |
+
------
|
| 132 |
+
NetworkXNotImplemented
|
| 133 |
+
If G is undirected.
|
| 134 |
+
|
| 135 |
+
Examples
|
| 136 |
+
--------
|
| 137 |
+
Generate a sorted list of strongly connected components, largest first.
|
| 138 |
+
|
| 139 |
+
>>> G = nx.cycle_graph(4, create_using=nx.DiGraph())
|
| 140 |
+
>>> nx.add_cycle(G, [10, 11, 12])
|
| 141 |
+
>>> [
|
| 142 |
+
... len(c)
|
| 143 |
+
... for c in sorted(
|
| 144 |
+
... nx.kosaraju_strongly_connected_components(G), key=len, reverse=True
|
| 145 |
+
... )
|
| 146 |
+
... ]
|
| 147 |
+
[4, 3]
|
| 148 |
+
|
| 149 |
+
If you only want the largest component, it's more efficient to
|
| 150 |
+
use max instead of sort.
|
| 151 |
+
|
| 152 |
+
>>> largest = max(nx.kosaraju_strongly_connected_components(G), key=len)
|
| 153 |
+
|
| 154 |
+
See Also
|
| 155 |
+
--------
|
| 156 |
+
strongly_connected_components
|
| 157 |
+
|
| 158 |
+
Notes
|
| 159 |
+
-----
|
| 160 |
+
Uses Kosaraju's algorithm.
|
| 161 |
+
|
| 162 |
+
"""
|
| 163 |
+
post = list(nx.dfs_postorder_nodes(G.reverse(copy=False), source=source))
|
| 164 |
+
|
| 165 |
+
seen = set()
|
| 166 |
+
while post:
|
| 167 |
+
r = post.pop()
|
| 168 |
+
if r in seen:
|
| 169 |
+
continue
|
| 170 |
+
c = nx.dfs_preorder_nodes(G, r)
|
| 171 |
+
new = {v for v in c if v not in seen}
|
| 172 |
+
seen.update(new)
|
| 173 |
+
yield new
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
@not_implemented_for("undirected")
|
| 177 |
+
@nx._dispatchable
|
| 178 |
+
def number_strongly_connected_components(G):
|
| 179 |
+
"""Returns number of strongly connected components in graph.
|
| 180 |
+
|
| 181 |
+
Parameters
|
| 182 |
+
----------
|
| 183 |
+
G : NetworkX graph
|
| 184 |
+
A directed graph.
|
| 185 |
+
|
| 186 |
+
Returns
|
| 187 |
+
-------
|
| 188 |
+
n : integer
|
| 189 |
+
Number of strongly connected components
|
| 190 |
+
|
| 191 |
+
Raises
|
| 192 |
+
------
|
| 193 |
+
NetworkXNotImplemented
|
| 194 |
+
If G is undirected.
|
| 195 |
+
|
| 196 |
+
Examples
|
| 197 |
+
--------
|
| 198 |
+
>>> G = nx.DiGraph(
|
| 199 |
+
... [(0, 1), (1, 2), (2, 0), (2, 3), (4, 5), (3, 4), (5, 6), (6, 3), (6, 7)]
|
| 200 |
+
... )
|
| 201 |
+
>>> nx.number_strongly_connected_components(G)
|
| 202 |
+
3
|
| 203 |
+
|
| 204 |
+
See Also
|
| 205 |
+
--------
|
| 206 |
+
strongly_connected_components
|
| 207 |
+
number_connected_components
|
| 208 |
+
number_weakly_connected_components
|
| 209 |
+
|
| 210 |
+
Notes
|
| 211 |
+
-----
|
| 212 |
+
For directed graphs only.
|
| 213 |
+
"""
|
| 214 |
+
return sum(1 for scc in strongly_connected_components(G))
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
@not_implemented_for("undirected")
|
| 218 |
+
@nx._dispatchable
|
| 219 |
+
def is_strongly_connected(G):
|
| 220 |
+
"""Test directed graph for strong connectivity.
|
| 221 |
+
|
| 222 |
+
A directed graph is strongly connected if and only if every vertex in
|
| 223 |
+
the graph is reachable from every other vertex.
|
| 224 |
+
|
| 225 |
+
Parameters
|
| 226 |
+
----------
|
| 227 |
+
G : NetworkX Graph
|
| 228 |
+
A directed graph.
|
| 229 |
+
|
| 230 |
+
Returns
|
| 231 |
+
-------
|
| 232 |
+
connected : bool
|
| 233 |
+
True if the graph is strongly connected, False otherwise.
|
| 234 |
+
|
| 235 |
+
Examples
|
| 236 |
+
--------
|
| 237 |
+
>>> G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 0), (2, 4), (4, 2)])
|
| 238 |
+
>>> nx.is_strongly_connected(G)
|
| 239 |
+
True
|
| 240 |
+
>>> G.remove_edge(2, 3)
|
| 241 |
+
>>> nx.is_strongly_connected(G)
|
| 242 |
+
False
|
| 243 |
+
|
| 244 |
+
Raises
|
| 245 |
+
------
|
| 246 |
+
NetworkXNotImplemented
|
| 247 |
+
If G is undirected.
|
| 248 |
+
|
| 249 |
+
See Also
|
| 250 |
+
--------
|
| 251 |
+
is_weakly_connected
|
| 252 |
+
is_semiconnected
|
| 253 |
+
is_connected
|
| 254 |
+
is_biconnected
|
| 255 |
+
strongly_connected_components
|
| 256 |
+
|
| 257 |
+
Notes
|
| 258 |
+
-----
|
| 259 |
+
For directed graphs only.
|
| 260 |
+
"""
|
| 261 |
+
if len(G) == 0:
|
| 262 |
+
raise nx.NetworkXPointlessConcept(
|
| 263 |
+
"""Connectivity is undefined for the null graph."""
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
return len(next(strongly_connected_components(G))) == len(G)
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
@not_implemented_for("undirected")
|
| 270 |
+
@nx._dispatchable(returns_graph=True)
|
| 271 |
+
def condensation(G, scc=None):
|
| 272 |
+
"""Returns the condensation of G.
|
| 273 |
+
|
| 274 |
+
The condensation of G is the graph with each of the strongly connected
|
| 275 |
+
components contracted into a single node.
|
| 276 |
+
|
| 277 |
+
Parameters
|
| 278 |
+
----------
|
| 279 |
+
G : NetworkX DiGraph
|
| 280 |
+
A directed graph.
|
| 281 |
+
|
| 282 |
+
scc: list or generator (optional, default=None)
|
| 283 |
+
Strongly connected components. If provided, the elements in
|
| 284 |
+
`scc` must partition the nodes in `G`. If not provided, it will be
|
| 285 |
+
calculated as scc=nx.strongly_connected_components(G).
|
| 286 |
+
|
| 287 |
+
Returns
|
| 288 |
+
-------
|
| 289 |
+
C : NetworkX DiGraph
|
| 290 |
+
The condensation graph C of G. The node labels are integers
|
| 291 |
+
corresponding to the index of the component in the list of
|
| 292 |
+
strongly connected components of G. C has a graph attribute named
|
| 293 |
+
'mapping' with a dictionary mapping the original nodes to the
|
| 294 |
+
nodes in C to which they belong. Each node in C also has a node
|
| 295 |
+
attribute 'members' with the set of original nodes in G that
|
| 296 |
+
form the SCC that the node in C represents.
|
| 297 |
+
|
| 298 |
+
Raises
|
| 299 |
+
------
|
| 300 |
+
NetworkXNotImplemented
|
| 301 |
+
If G is undirected.
|
| 302 |
+
|
| 303 |
+
Examples
|
| 304 |
+
--------
|
| 305 |
+
Contracting two sets of strongly connected nodes into two distinct SCC
|
| 306 |
+
using the barbell graph.
|
| 307 |
+
|
| 308 |
+
>>> G = nx.barbell_graph(4, 0)
|
| 309 |
+
>>> G.remove_edge(3, 4)
|
| 310 |
+
>>> G = nx.DiGraph(G)
|
| 311 |
+
>>> H = nx.condensation(G)
|
| 312 |
+
>>> H.nodes.data()
|
| 313 |
+
NodeDataView({0: {'members': {0, 1, 2, 3}}, 1: {'members': {4, 5, 6, 7}}})
|
| 314 |
+
>>> H.graph["mapping"]
|
| 315 |
+
{0: 0, 1: 0, 2: 0, 3: 0, 4: 1, 5: 1, 6: 1, 7: 1}
|
| 316 |
+
|
| 317 |
+
Contracting a complete graph into one single SCC.
|
| 318 |
+
|
| 319 |
+
>>> G = nx.complete_graph(7, create_using=nx.DiGraph)
|
| 320 |
+
>>> H = nx.condensation(G)
|
| 321 |
+
>>> H.nodes
|
| 322 |
+
NodeView((0,))
|
| 323 |
+
>>> H.nodes.data()
|
| 324 |
+
NodeDataView({0: {'members': {0, 1, 2, 3, 4, 5, 6}}})
|
| 325 |
+
|
| 326 |
+
Notes
|
| 327 |
+
-----
|
| 328 |
+
After contracting all strongly connected components to a single node,
|
| 329 |
+
the resulting graph is a directed acyclic graph.
|
| 330 |
+
|
| 331 |
+
"""
|
| 332 |
+
if scc is None:
|
| 333 |
+
scc = nx.strongly_connected_components(G)
|
| 334 |
+
mapping = {}
|
| 335 |
+
members = {}
|
| 336 |
+
C = nx.DiGraph()
|
| 337 |
+
# Add mapping dict as graph attribute
|
| 338 |
+
C.graph["mapping"] = mapping
|
| 339 |
+
if len(G) == 0:
|
| 340 |
+
return C
|
| 341 |
+
for i, component in enumerate(scc):
|
| 342 |
+
members[i] = component
|
| 343 |
+
mapping.update((n, i) for n in component)
|
| 344 |
+
number_of_components = i + 1
|
| 345 |
+
C.add_nodes_from(range(number_of_components))
|
| 346 |
+
C.add_edges_from(
|
| 347 |
+
(mapping[u], mapping[v]) for u, v in G.edges() if mapping[u] != mapping[v]
|
| 348 |
+
)
|
| 349 |
+
# Add a list of members (ie original nodes) to each node (ie scc) in C.
|
| 350 |
+
nx.set_node_attributes(C, members, "members")
|
| 351 |
+
return C
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (209 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/test_attracting.cpython-311.pyc
ADDED
|
Binary file (5.23 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/test_biconnected.cpython-311.pyc
ADDED
|
Binary file (12.6 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/test_connected.cpython-311.pyc
ADDED
|
Binary file (9.58 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/test_semiconnected.cpython-311.pyc
ADDED
|
Binary file (5.5 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/test_strongly_connected.cpython-311.pyc
ADDED
|
Binary file (13.2 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/__pycache__/test_weakly_connected.cpython-311.pyc
ADDED
|
Binary file (7.49 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/test_attracting.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx import NetworkXNotImplemented
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class TestAttractingComponents:
|
| 8 |
+
@classmethod
|
| 9 |
+
def setup_class(cls):
|
| 10 |
+
cls.G1 = nx.DiGraph()
|
| 11 |
+
cls.G1.add_edges_from(
|
| 12 |
+
[
|
| 13 |
+
(5, 11),
|
| 14 |
+
(11, 2),
|
| 15 |
+
(11, 9),
|
| 16 |
+
(11, 10),
|
| 17 |
+
(7, 11),
|
| 18 |
+
(7, 8),
|
| 19 |
+
(8, 9),
|
| 20 |
+
(3, 8),
|
| 21 |
+
(3, 10),
|
| 22 |
+
]
|
| 23 |
+
)
|
| 24 |
+
cls.G2 = nx.DiGraph()
|
| 25 |
+
cls.G2.add_edges_from([(0, 1), (0, 2), (1, 1), (1, 2), (2, 1)])
|
| 26 |
+
|
| 27 |
+
cls.G3 = nx.DiGraph()
|
| 28 |
+
cls.G3.add_edges_from([(0, 1), (1, 2), (2, 1), (0, 3), (3, 4), (4, 3)])
|
| 29 |
+
|
| 30 |
+
cls.G4 = nx.DiGraph()
|
| 31 |
+
|
| 32 |
+
def test_attracting_components(self):
|
| 33 |
+
ac = list(nx.attracting_components(self.G1))
|
| 34 |
+
assert {2} in ac
|
| 35 |
+
assert {9} in ac
|
| 36 |
+
assert {10} in ac
|
| 37 |
+
|
| 38 |
+
ac = list(nx.attracting_components(self.G2))
|
| 39 |
+
ac = [tuple(sorted(x)) for x in ac]
|
| 40 |
+
assert ac == [(1, 2)]
|
| 41 |
+
|
| 42 |
+
ac = list(nx.attracting_components(self.G3))
|
| 43 |
+
ac = [tuple(sorted(x)) for x in ac]
|
| 44 |
+
assert (1, 2) in ac
|
| 45 |
+
assert (3, 4) in ac
|
| 46 |
+
assert len(ac) == 2
|
| 47 |
+
|
| 48 |
+
ac = list(nx.attracting_components(self.G4))
|
| 49 |
+
assert ac == []
|
| 50 |
+
|
| 51 |
+
def test_number_attacting_components(self):
|
| 52 |
+
assert nx.number_attracting_components(self.G1) == 3
|
| 53 |
+
assert nx.number_attracting_components(self.G2) == 1
|
| 54 |
+
assert nx.number_attracting_components(self.G3) == 2
|
| 55 |
+
assert nx.number_attracting_components(self.G4) == 0
|
| 56 |
+
|
| 57 |
+
def test_is_attracting_component(self):
|
| 58 |
+
assert not nx.is_attracting_component(self.G1)
|
| 59 |
+
assert not nx.is_attracting_component(self.G2)
|
| 60 |
+
assert not nx.is_attracting_component(self.G3)
|
| 61 |
+
g2 = self.G3.subgraph([1, 2])
|
| 62 |
+
assert nx.is_attracting_component(g2)
|
| 63 |
+
assert not nx.is_attracting_component(self.G4)
|
| 64 |
+
|
| 65 |
+
def test_connected_raise(self):
|
| 66 |
+
G = nx.Graph()
|
| 67 |
+
with pytest.raises(NetworkXNotImplemented):
|
| 68 |
+
next(nx.attracting_components(G))
|
| 69 |
+
pytest.raises(NetworkXNotImplemented, nx.number_attracting_components, G)
|
| 70 |
+
pytest.raises(NetworkXNotImplemented, nx.is_attracting_component, G)
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/test_biconnected.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx import NetworkXNotImplemented
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def assert_components_edges_equal(x, y):
|
| 8 |
+
sx = {frozenset(frozenset(e) for e in c) for c in x}
|
| 9 |
+
sy = {frozenset(frozenset(e) for e in c) for c in y}
|
| 10 |
+
assert sx == sy
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def assert_components_equal(x, y):
|
| 14 |
+
sx = {frozenset(c) for c in x}
|
| 15 |
+
sy = {frozenset(c) for c in y}
|
| 16 |
+
assert sx == sy
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def test_barbell():
|
| 20 |
+
G = nx.barbell_graph(8, 4)
|
| 21 |
+
nx.add_path(G, [7, 20, 21, 22])
|
| 22 |
+
nx.add_cycle(G, [22, 23, 24, 25])
|
| 23 |
+
pts = set(nx.articulation_points(G))
|
| 24 |
+
assert pts == {7, 8, 9, 10, 11, 12, 20, 21, 22}
|
| 25 |
+
|
| 26 |
+
answer = [
|
| 27 |
+
{12, 13, 14, 15, 16, 17, 18, 19},
|
| 28 |
+
{0, 1, 2, 3, 4, 5, 6, 7},
|
| 29 |
+
{22, 23, 24, 25},
|
| 30 |
+
{11, 12},
|
| 31 |
+
{10, 11},
|
| 32 |
+
{9, 10},
|
| 33 |
+
{8, 9},
|
| 34 |
+
{7, 8},
|
| 35 |
+
{21, 22},
|
| 36 |
+
{20, 21},
|
| 37 |
+
{7, 20},
|
| 38 |
+
]
|
| 39 |
+
assert_components_equal(list(nx.biconnected_components(G)), answer)
|
| 40 |
+
|
| 41 |
+
G.add_edge(2, 17)
|
| 42 |
+
pts = set(nx.articulation_points(G))
|
| 43 |
+
assert pts == {7, 20, 21, 22}
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def test_articulation_points_repetitions():
|
| 47 |
+
G = nx.Graph()
|
| 48 |
+
G.add_edges_from([(0, 1), (1, 2), (1, 3)])
|
| 49 |
+
assert list(nx.articulation_points(G)) == [1]
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def test_articulation_points_cycle():
|
| 53 |
+
G = nx.cycle_graph(3)
|
| 54 |
+
nx.add_cycle(G, [1, 3, 4])
|
| 55 |
+
pts = set(nx.articulation_points(G))
|
| 56 |
+
assert pts == {1}
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def test_is_biconnected():
|
| 60 |
+
G = nx.cycle_graph(3)
|
| 61 |
+
assert nx.is_biconnected(G)
|
| 62 |
+
nx.add_cycle(G, [1, 3, 4])
|
| 63 |
+
assert not nx.is_biconnected(G)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def test_empty_is_biconnected():
|
| 67 |
+
G = nx.empty_graph(5)
|
| 68 |
+
assert not nx.is_biconnected(G)
|
| 69 |
+
G.add_edge(0, 1)
|
| 70 |
+
assert not nx.is_biconnected(G)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def test_biconnected_components_cycle():
|
| 74 |
+
G = nx.cycle_graph(3)
|
| 75 |
+
nx.add_cycle(G, [1, 3, 4])
|
| 76 |
+
answer = [{0, 1, 2}, {1, 3, 4}]
|
| 77 |
+
assert_components_equal(list(nx.biconnected_components(G)), answer)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def test_biconnected_components1():
|
| 81 |
+
# graph example from
|
| 82 |
+
# https://web.archive.org/web/20121229123447/http://www.ibluemojo.com/school/articul_algorithm.html
|
| 83 |
+
edges = [
|
| 84 |
+
(0, 1),
|
| 85 |
+
(0, 5),
|
| 86 |
+
(0, 6),
|
| 87 |
+
(0, 14),
|
| 88 |
+
(1, 5),
|
| 89 |
+
(1, 6),
|
| 90 |
+
(1, 14),
|
| 91 |
+
(2, 4),
|
| 92 |
+
(2, 10),
|
| 93 |
+
(3, 4),
|
| 94 |
+
(3, 15),
|
| 95 |
+
(4, 6),
|
| 96 |
+
(4, 7),
|
| 97 |
+
(4, 10),
|
| 98 |
+
(5, 14),
|
| 99 |
+
(6, 14),
|
| 100 |
+
(7, 9),
|
| 101 |
+
(8, 9),
|
| 102 |
+
(8, 12),
|
| 103 |
+
(8, 13),
|
| 104 |
+
(10, 15),
|
| 105 |
+
(11, 12),
|
| 106 |
+
(11, 13),
|
| 107 |
+
(12, 13),
|
| 108 |
+
]
|
| 109 |
+
G = nx.Graph(edges)
|
| 110 |
+
pts = set(nx.articulation_points(G))
|
| 111 |
+
assert pts == {4, 6, 7, 8, 9}
|
| 112 |
+
comps = list(nx.biconnected_component_edges(G))
|
| 113 |
+
answer = [
|
| 114 |
+
[(3, 4), (15, 3), (10, 15), (10, 4), (2, 10), (4, 2)],
|
| 115 |
+
[(13, 12), (13, 8), (11, 13), (12, 11), (8, 12)],
|
| 116 |
+
[(9, 8)],
|
| 117 |
+
[(7, 9)],
|
| 118 |
+
[(4, 7)],
|
| 119 |
+
[(6, 4)],
|
| 120 |
+
[(14, 0), (5, 1), (5, 0), (14, 5), (14, 1), (6, 14), (6, 0), (1, 6), (0, 1)],
|
| 121 |
+
]
|
| 122 |
+
assert_components_edges_equal(comps, answer)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def test_biconnected_components2():
|
| 126 |
+
G = nx.Graph()
|
| 127 |
+
nx.add_cycle(G, "ABC")
|
| 128 |
+
nx.add_cycle(G, "CDE")
|
| 129 |
+
nx.add_cycle(G, "FIJHG")
|
| 130 |
+
nx.add_cycle(G, "GIJ")
|
| 131 |
+
G.add_edge("E", "G")
|
| 132 |
+
comps = list(nx.biconnected_component_edges(G))
|
| 133 |
+
answer = [
|
| 134 |
+
[
|
| 135 |
+
tuple("GF"),
|
| 136 |
+
tuple("FI"),
|
| 137 |
+
tuple("IG"),
|
| 138 |
+
tuple("IJ"),
|
| 139 |
+
tuple("JG"),
|
| 140 |
+
tuple("JH"),
|
| 141 |
+
tuple("HG"),
|
| 142 |
+
],
|
| 143 |
+
[tuple("EG")],
|
| 144 |
+
[tuple("CD"), tuple("DE"), tuple("CE")],
|
| 145 |
+
[tuple("AB"), tuple("BC"), tuple("AC")],
|
| 146 |
+
]
|
| 147 |
+
assert_components_edges_equal(comps, answer)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def test_biconnected_davis():
|
| 151 |
+
D = nx.davis_southern_women_graph()
|
| 152 |
+
bcc = list(nx.biconnected_components(D))[0]
|
| 153 |
+
assert set(D) == bcc # All nodes in a giant bicomponent
|
| 154 |
+
# So no articulation points
|
| 155 |
+
assert len(list(nx.articulation_points(D))) == 0
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def test_biconnected_karate():
|
| 159 |
+
K = nx.karate_club_graph()
|
| 160 |
+
answer = [
|
| 161 |
+
{
|
| 162 |
+
0,
|
| 163 |
+
1,
|
| 164 |
+
2,
|
| 165 |
+
3,
|
| 166 |
+
7,
|
| 167 |
+
8,
|
| 168 |
+
9,
|
| 169 |
+
12,
|
| 170 |
+
13,
|
| 171 |
+
14,
|
| 172 |
+
15,
|
| 173 |
+
17,
|
| 174 |
+
18,
|
| 175 |
+
19,
|
| 176 |
+
20,
|
| 177 |
+
21,
|
| 178 |
+
22,
|
| 179 |
+
23,
|
| 180 |
+
24,
|
| 181 |
+
25,
|
| 182 |
+
26,
|
| 183 |
+
27,
|
| 184 |
+
28,
|
| 185 |
+
29,
|
| 186 |
+
30,
|
| 187 |
+
31,
|
| 188 |
+
32,
|
| 189 |
+
33,
|
| 190 |
+
},
|
| 191 |
+
{0, 4, 5, 6, 10, 16},
|
| 192 |
+
{0, 11},
|
| 193 |
+
]
|
| 194 |
+
bcc = list(nx.biconnected_components(K))
|
| 195 |
+
assert_components_equal(bcc, answer)
|
| 196 |
+
assert set(nx.articulation_points(K)) == {0}
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def test_biconnected_eppstein():
|
| 200 |
+
# tests from http://www.ics.uci.edu/~eppstein/PADS/Biconnectivity.py
|
| 201 |
+
G1 = nx.Graph(
|
| 202 |
+
{
|
| 203 |
+
0: [1, 2, 5],
|
| 204 |
+
1: [0, 5],
|
| 205 |
+
2: [0, 3, 4],
|
| 206 |
+
3: [2, 4, 5, 6],
|
| 207 |
+
4: [2, 3, 5, 6],
|
| 208 |
+
5: [0, 1, 3, 4],
|
| 209 |
+
6: [3, 4],
|
| 210 |
+
}
|
| 211 |
+
)
|
| 212 |
+
G2 = nx.Graph(
|
| 213 |
+
{
|
| 214 |
+
0: [2, 5],
|
| 215 |
+
1: [3, 8],
|
| 216 |
+
2: [0, 3, 5],
|
| 217 |
+
3: [1, 2, 6, 8],
|
| 218 |
+
4: [7],
|
| 219 |
+
5: [0, 2],
|
| 220 |
+
6: [3, 8],
|
| 221 |
+
7: [4],
|
| 222 |
+
8: [1, 3, 6],
|
| 223 |
+
}
|
| 224 |
+
)
|
| 225 |
+
assert nx.is_biconnected(G1)
|
| 226 |
+
assert not nx.is_biconnected(G2)
|
| 227 |
+
answer_G2 = [{1, 3, 6, 8}, {0, 2, 5}, {2, 3}, {4, 7}]
|
| 228 |
+
bcc = list(nx.biconnected_components(G2))
|
| 229 |
+
assert_components_equal(bcc, answer_G2)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def test_null_graph():
|
| 233 |
+
G = nx.Graph()
|
| 234 |
+
assert not nx.is_biconnected(G)
|
| 235 |
+
assert list(nx.biconnected_components(G)) == []
|
| 236 |
+
assert list(nx.biconnected_component_edges(G)) == []
|
| 237 |
+
assert list(nx.articulation_points(G)) == []
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def test_connected_raise():
|
| 241 |
+
DG = nx.DiGraph()
|
| 242 |
+
with pytest.raises(NetworkXNotImplemented):
|
| 243 |
+
next(nx.biconnected_components(DG))
|
| 244 |
+
with pytest.raises(NetworkXNotImplemented):
|
| 245 |
+
next(nx.biconnected_component_edges(DG))
|
| 246 |
+
with pytest.raises(NetworkXNotImplemented):
|
| 247 |
+
next(nx.articulation_points(DG))
|
| 248 |
+
pytest.raises(NetworkXNotImplemented, nx.is_biconnected, DG)
|
.venv/lib/python3.11/site-packages/networkx/algorithms/components/tests/test_connected.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx import NetworkXNotImplemented
|
| 5 |
+
from networkx import convert_node_labels_to_integers as cnlti
|
| 6 |
+
from networkx.classes.tests import dispatch_interface
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class TestConnected:
|
| 10 |
+
@classmethod
|
| 11 |
+
def setup_class(cls):
|
| 12 |
+
G1 = cnlti(nx.grid_2d_graph(2, 2), first_label=0, ordering="sorted")
|
| 13 |
+
G2 = cnlti(nx.lollipop_graph(3, 3), first_label=4, ordering="sorted")
|
| 14 |
+
G3 = cnlti(nx.house_graph(), first_label=10, ordering="sorted")
|
| 15 |
+
cls.G = nx.union(G1, G2)
|
| 16 |
+
cls.G = nx.union(cls.G, G3)
|
| 17 |
+
cls.DG = nx.DiGraph([(1, 2), (1, 3), (2, 3)])
|
| 18 |
+
cls.grid = cnlti(nx.grid_2d_graph(4, 4), first_label=1)
|
| 19 |
+
|
| 20 |
+
cls.gc = []
|
| 21 |
+
G = nx.DiGraph()
|
| 22 |
+
G.add_edges_from(
|
| 23 |
+
[
|
| 24 |
+
(1, 2),
|
| 25 |
+
(2, 3),
|
| 26 |
+
(2, 8),
|
| 27 |
+
(3, 4),
|
| 28 |
+
(3, 7),
|
| 29 |
+
(4, 5),
|
| 30 |
+
(5, 3),
|
| 31 |
+
(5, 6),
|
| 32 |
+
(7, 4),
|
| 33 |
+
(7, 6),
|
| 34 |
+
(8, 1),
|
| 35 |
+
(8, 7),
|
| 36 |
+
]
|
| 37 |
+
)
|
| 38 |
+
C = [[3, 4, 5, 7], [1, 2, 8], [6]]
|
| 39 |
+
cls.gc.append((G, C))
|
| 40 |
+
|
| 41 |
+
G = nx.DiGraph()
|
| 42 |
+
G.add_edges_from([(1, 2), (1, 3), (1, 4), (4, 2), (3, 4), (2, 3)])
|
| 43 |
+
C = [[2, 3, 4], [1]]
|
| 44 |
+
cls.gc.append((G, C))
|
| 45 |
+
|
| 46 |
+
G = nx.DiGraph()
|
| 47 |
+
G.add_edges_from([(1, 2), (2, 3), (3, 2), (2, 1)])
|
| 48 |
+
C = [[1, 2, 3]]
|
| 49 |
+
cls.gc.append((G, C))
|
| 50 |
+
|
| 51 |
+
# Eppstein's tests
|
| 52 |
+
G = nx.DiGraph({0: [1], 1: [2, 3], 2: [4, 5], 3: [4, 5], 4: [6], 5: [], 6: []})
|
| 53 |
+
C = [[0], [1], [2], [3], [4], [5], [6]]
|
| 54 |
+
cls.gc.append((G, C))
|
| 55 |
+
|
| 56 |
+
G = nx.DiGraph({0: [1], 1: [2, 3, 4], 2: [0, 3], 3: [4], 4: [3]})
|
| 57 |
+
C = [[0, 1, 2], [3, 4]]
|
| 58 |
+
cls.gc.append((G, C))
|
| 59 |
+
|
| 60 |
+
G = nx.DiGraph()
|
| 61 |
+
C = []
|
| 62 |
+
cls.gc.append((G, C))
|
| 63 |
+
|
| 64 |
+
def test_connected_components(self):
|
| 65 |
+
# Test duplicated below
|
| 66 |
+
cc = nx.connected_components
|
| 67 |
+
G = self.G
|
| 68 |
+
C = {
|
| 69 |
+
frozenset([0, 1, 2, 3]),
|
| 70 |
+
frozenset([4, 5, 6, 7, 8, 9]),
|
| 71 |
+
frozenset([10, 11, 12, 13, 14]),
|
| 72 |
+
}
|
| 73 |
+
assert {frozenset(g) for g in cc(G)} == C
|
| 74 |
+
|
| 75 |
+
def test_connected_components_nx_loopback(self):
|
| 76 |
+
# This tests the @nx._dispatchable mechanism, treating nx.connected_components
|
| 77 |
+
# as if it were a re-implementation from another package.
|
| 78 |
+
# Test duplicated from above
|
| 79 |
+
cc = nx.connected_components
|
| 80 |
+
G = dispatch_interface.convert(self.G)
|
| 81 |
+
C = {
|
| 82 |
+
frozenset([0, 1, 2, 3]),
|
| 83 |
+
frozenset([4, 5, 6, 7, 8, 9]),
|
| 84 |
+
frozenset([10, 11, 12, 13, 14]),
|
| 85 |
+
}
|
| 86 |
+
if "nx_loopback" in nx.config.backends or not nx.config.backends:
|
| 87 |
+
# If `nx.config.backends` is empty, then `_dispatchable.__call__` takes a
|
| 88 |
+
# "fast path" and does not check graph inputs, so using an unknown backend
|
| 89 |
+
# here will still work.
|
| 90 |
+
assert {frozenset(g) for g in cc(G)} == C
|
| 91 |
+
else:
|
| 92 |
+
# This raises, because "nx_loopback" is not registered as a backend.
|
| 93 |
+
with pytest.raises(
|
| 94 |
+
ImportError, match="'nx_loopback' backend is not installed"
|
| 95 |
+
):
|
| 96 |
+
cc(G)
|
| 97 |
+
|
| 98 |
+
def test_number_connected_components(self):
|
| 99 |
+
ncc = nx.number_connected_components
|
| 100 |
+
assert ncc(self.G) == 3
|
| 101 |
+
|
| 102 |
+
def test_number_connected_components2(self):
|
| 103 |
+
ncc = nx.number_connected_components
|
| 104 |
+
assert ncc(self.grid) == 1
|
| 105 |
+
|
| 106 |
+
def test_connected_components2(self):
|
| 107 |
+
cc = nx.connected_components
|
| 108 |
+
G = self.grid
|
| 109 |
+
C = {frozenset([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])}
|
| 110 |
+
assert {frozenset(g) for g in cc(G)} == C
|
| 111 |
+
|
| 112 |
+
def test_node_connected_components(self):
|
| 113 |
+
ncc = nx.node_connected_component
|
| 114 |
+
G = self.grid
|
| 115 |
+
C = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
|
| 116 |
+
assert ncc(G, 1) == C
|
| 117 |
+
|
| 118 |
+
def test_is_connected(self):
|
| 119 |
+
assert nx.is_connected(self.grid)
|
| 120 |
+
G = nx.Graph()
|
| 121 |
+
G.add_nodes_from([1, 2])
|
| 122 |
+
assert not nx.is_connected(G)
|
| 123 |
+
|
| 124 |
+
def test_connected_raise(self):
|
| 125 |
+
with pytest.raises(NetworkXNotImplemented):
|
| 126 |
+
next(nx.connected_components(self.DG))
|
| 127 |
+
pytest.raises(NetworkXNotImplemented, nx.number_connected_components, self.DG)
|
| 128 |
+
pytest.raises(NetworkXNotImplemented, nx.node_connected_component, self.DG, 1)
|
| 129 |
+
pytest.raises(NetworkXNotImplemented, nx.is_connected, self.DG)
|
| 130 |
+
pytest.raises(nx.NetworkXPointlessConcept, nx.is_connected, nx.Graph())
|
| 131 |
+
|
| 132 |
+
def test_connected_mutability(self):
|
| 133 |
+
G = self.grid
|
| 134 |
+
seen = set()
|
| 135 |
+
for component in nx.connected_components(G):
|
| 136 |
+
assert len(seen & component) == 0
|
| 137 |
+
seen.update(component)
|
| 138 |
+
component.clear()
|