Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .venv/lib/python3.11/site-packages/networkx/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/__pycache__/conftest.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/__pycache__/convert.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/__pycache__/convert_matrix.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/__pycache__/exception.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/__pycache__/lazy_imports.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/__pycache__/relabel.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/centrality.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/extendability.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/redundancy.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__init__.py +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_basic.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_centrality.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_cluster.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_covering.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_edgelist.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_extendability.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_generators.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_matching.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_matrix.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_project.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_redundancy.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_spectral_bipartivity.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_basic.py +125 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_centrality.py +192 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_cluster.py +84 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_covering.py +33 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_edgelist.py +240 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_extendability.py +334 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_generators.py +409 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_matching.py +327 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_matrix.py +84 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_project.py +407 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_redundancy.py +35 -0
- .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py +80 -0
- .venv/lib/python3.11/site-packages/networkx/generators/__init__.py +34 -0
- .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/directed.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/duplication.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/geometric.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/joint_degree_seq.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/mycielski.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/social.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/spectral_graph_forge.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/trees.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/networkx/generators/atlas.py +180 -0
- .venv/lib/python3.11/site-packages/networkx/generators/classic.py +1068 -0
- .venv/lib/python3.11/site-packages/networkx/generators/cographs.py +68 -0
- .venv/lib/python3.11/site-packages/networkx/generators/community.py +1070 -0
.venv/lib/python3.11/site-packages/networkx/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (1.73 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/__pycache__/conftest.cpython-311.pyc
ADDED
|
Binary file (9.63 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/__pycache__/convert.cpython-311.pyc
ADDED
|
Binary file (20.4 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/__pycache__/convert_matrix.cpython-311.pyc
ADDED
|
Binary file (54.9 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/__pycache__/exception.cpython-311.pyc
ADDED
|
Binary file (6.31 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/__pycache__/lazy_imports.cpython-311.pyc
ADDED
|
Binary file (7.98 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/__pycache__/relabel.cpython-311.pyc
ADDED
|
Binary file (16.2 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (4.14 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/centrality.cpython-311.pyc
ADDED
|
Binary file (11.5 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/extendability.cpython-311.pyc
ADDED
|
Binary file (5.56 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/redundancy.cpython-311.pyc
ADDED
|
Binary file (4.85 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__init__.py
ADDED
|
File without changes
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (208 Bytes). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_basic.cpython-311.pyc
ADDED
|
Binary file (11.1 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_centrality.cpython-311.pyc
ADDED
|
Binary file (8.7 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_cluster.cpython-311.pyc
ADDED
|
Binary file (6.42 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_covering.cpython-311.pyc
ADDED
|
Binary file (3.01 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_edgelist.cpython-311.pyc
ADDED
|
Binary file (17.2 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_extendability.cpython-311.pyc
ADDED
|
Binary file (8.26 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_generators.cpython-311.pyc
ADDED
|
Binary file (24.8 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_matching.cpython-311.pyc
ADDED
|
Binary file (23.6 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_matrix.cpython-311.pyc
ADDED
|
Binary file (8.71 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_project.cpython-311.pyc
ADDED
|
Binary file (30.1 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_redundancy.cpython-311.pyc
ADDED
|
Binary file (2.3 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_spectral_bipartivity.cpython-311.pyc
ADDED
|
Binary file (5.32 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_basic.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.algorithms import bipartite
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class TestBipartiteBasic:
|
| 8 |
+
def test_is_bipartite(self):
|
| 9 |
+
assert bipartite.is_bipartite(nx.path_graph(4))
|
| 10 |
+
assert bipartite.is_bipartite(nx.DiGraph([(1, 0)]))
|
| 11 |
+
assert not bipartite.is_bipartite(nx.complete_graph(3))
|
| 12 |
+
|
| 13 |
+
def test_bipartite_color(self):
|
| 14 |
+
G = nx.path_graph(4)
|
| 15 |
+
c = bipartite.color(G)
|
| 16 |
+
assert c == {0: 1, 1: 0, 2: 1, 3: 0}
|
| 17 |
+
|
| 18 |
+
def test_not_bipartite_color(self):
|
| 19 |
+
with pytest.raises(nx.NetworkXError):
|
| 20 |
+
c = bipartite.color(nx.complete_graph(4))
|
| 21 |
+
|
| 22 |
+
def test_bipartite_directed(self):
|
| 23 |
+
G = bipartite.random_graph(10, 10, 0.1, directed=True)
|
| 24 |
+
assert bipartite.is_bipartite(G)
|
| 25 |
+
|
| 26 |
+
def test_bipartite_sets(self):
|
| 27 |
+
G = nx.path_graph(4)
|
| 28 |
+
X, Y = bipartite.sets(G)
|
| 29 |
+
assert X == {0, 2}
|
| 30 |
+
assert Y == {1, 3}
|
| 31 |
+
|
| 32 |
+
def test_bipartite_sets_directed(self):
|
| 33 |
+
G = nx.path_graph(4)
|
| 34 |
+
D = G.to_directed()
|
| 35 |
+
X, Y = bipartite.sets(D)
|
| 36 |
+
assert X == {0, 2}
|
| 37 |
+
assert Y == {1, 3}
|
| 38 |
+
|
| 39 |
+
def test_bipartite_sets_given_top_nodes(self):
|
| 40 |
+
G = nx.path_graph(4)
|
| 41 |
+
top_nodes = [0, 2]
|
| 42 |
+
X, Y = bipartite.sets(G, top_nodes)
|
| 43 |
+
assert X == {0, 2}
|
| 44 |
+
assert Y == {1, 3}
|
| 45 |
+
|
| 46 |
+
def test_bipartite_sets_disconnected(self):
|
| 47 |
+
with pytest.raises(nx.AmbiguousSolution):
|
| 48 |
+
G = nx.path_graph(4)
|
| 49 |
+
G.add_edges_from([(5, 6), (6, 7)])
|
| 50 |
+
X, Y = bipartite.sets(G)
|
| 51 |
+
|
| 52 |
+
def test_is_bipartite_node_set(self):
|
| 53 |
+
G = nx.path_graph(4)
|
| 54 |
+
|
| 55 |
+
with pytest.raises(nx.AmbiguousSolution):
|
| 56 |
+
bipartite.is_bipartite_node_set(G, [1, 1, 2, 3])
|
| 57 |
+
|
| 58 |
+
assert bipartite.is_bipartite_node_set(G, [0, 2])
|
| 59 |
+
assert bipartite.is_bipartite_node_set(G, [1, 3])
|
| 60 |
+
assert not bipartite.is_bipartite_node_set(G, [1, 2])
|
| 61 |
+
G.add_edge(10, 20)
|
| 62 |
+
assert bipartite.is_bipartite_node_set(G, [0, 2, 10])
|
| 63 |
+
assert bipartite.is_bipartite_node_set(G, [0, 2, 20])
|
| 64 |
+
assert bipartite.is_bipartite_node_set(G, [1, 3, 10])
|
| 65 |
+
assert bipartite.is_bipartite_node_set(G, [1, 3, 20])
|
| 66 |
+
|
| 67 |
+
def test_bipartite_density(self):
|
| 68 |
+
G = nx.path_graph(5)
|
| 69 |
+
X, Y = bipartite.sets(G)
|
| 70 |
+
density = len(list(G.edges())) / (len(X) * len(Y))
|
| 71 |
+
assert bipartite.density(G, X) == density
|
| 72 |
+
D = nx.DiGraph(G.edges())
|
| 73 |
+
assert bipartite.density(D, X) == density / 2.0
|
| 74 |
+
assert bipartite.density(nx.Graph(), {}) == 0.0
|
| 75 |
+
|
| 76 |
+
def test_bipartite_degrees(self):
|
| 77 |
+
G = nx.path_graph(5)
|
| 78 |
+
X = {1, 3}
|
| 79 |
+
Y = {0, 2, 4}
|
| 80 |
+
u, d = bipartite.degrees(G, Y)
|
| 81 |
+
assert dict(u) == {1: 2, 3: 2}
|
| 82 |
+
assert dict(d) == {0: 1, 2: 2, 4: 1}
|
| 83 |
+
|
| 84 |
+
def test_bipartite_weighted_degrees(self):
|
| 85 |
+
G = nx.path_graph(5)
|
| 86 |
+
G.add_edge(0, 1, weight=0.1, other=0.2)
|
| 87 |
+
X = {1, 3}
|
| 88 |
+
Y = {0, 2, 4}
|
| 89 |
+
u, d = bipartite.degrees(G, Y, weight="weight")
|
| 90 |
+
assert dict(u) == {1: 1.1, 3: 2}
|
| 91 |
+
assert dict(d) == {0: 0.1, 2: 2, 4: 1}
|
| 92 |
+
u, d = bipartite.degrees(G, Y, weight="other")
|
| 93 |
+
assert dict(u) == {1: 1.2, 3: 2}
|
| 94 |
+
assert dict(d) == {0: 0.2, 2: 2, 4: 1}
|
| 95 |
+
|
| 96 |
+
def test_biadjacency_matrix_weight(self):
|
| 97 |
+
pytest.importorskip("scipy")
|
| 98 |
+
G = nx.path_graph(5)
|
| 99 |
+
G.add_edge(0, 1, weight=2, other=4)
|
| 100 |
+
X = [1, 3]
|
| 101 |
+
Y = [0, 2, 4]
|
| 102 |
+
M = bipartite.biadjacency_matrix(G, X, weight="weight")
|
| 103 |
+
assert M[0, 0] == 2
|
| 104 |
+
M = bipartite.biadjacency_matrix(G, X, weight="other")
|
| 105 |
+
assert M[0, 0] == 4
|
| 106 |
+
|
| 107 |
+
def test_biadjacency_matrix(self):
|
| 108 |
+
pytest.importorskip("scipy")
|
| 109 |
+
tops = [2, 5, 10]
|
| 110 |
+
bots = [5, 10, 15]
|
| 111 |
+
for i in range(len(tops)):
|
| 112 |
+
G = bipartite.random_graph(tops[i], bots[i], 0.2)
|
| 113 |
+
top = [n for n, d in G.nodes(data=True) if d["bipartite"] == 0]
|
| 114 |
+
M = bipartite.biadjacency_matrix(G, top)
|
| 115 |
+
assert M.shape[0] == tops[i]
|
| 116 |
+
assert M.shape[1] == bots[i]
|
| 117 |
+
|
| 118 |
+
def test_biadjacency_matrix_order(self):
|
| 119 |
+
pytest.importorskip("scipy")
|
| 120 |
+
G = nx.path_graph(5)
|
| 121 |
+
G.add_edge(0, 1, weight=2)
|
| 122 |
+
X = [3, 1]
|
| 123 |
+
Y = [4, 2, 0]
|
| 124 |
+
M = bipartite.biadjacency_matrix(G, X, Y, weight="weight")
|
| 125 |
+
assert M[1, 2] == 2
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_centrality.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.algorithms import bipartite
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class TestBipartiteCentrality:
|
| 8 |
+
@classmethod
|
| 9 |
+
def setup_class(cls):
|
| 10 |
+
cls.P4 = nx.path_graph(4)
|
| 11 |
+
cls.K3 = nx.complete_bipartite_graph(3, 3)
|
| 12 |
+
cls.C4 = nx.cycle_graph(4)
|
| 13 |
+
cls.davis = nx.davis_southern_women_graph()
|
| 14 |
+
cls.top_nodes = [
|
| 15 |
+
n for n, d in cls.davis.nodes(data=True) if d["bipartite"] == 0
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
def test_degree_centrality(self):
|
| 19 |
+
d = bipartite.degree_centrality(self.P4, [1, 3])
|
| 20 |
+
answer = {0: 0.5, 1: 1.0, 2: 1.0, 3: 0.5}
|
| 21 |
+
assert d == answer
|
| 22 |
+
d = bipartite.degree_centrality(self.K3, [0, 1, 2])
|
| 23 |
+
answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0, 5: 1.0}
|
| 24 |
+
assert d == answer
|
| 25 |
+
d = bipartite.degree_centrality(self.C4, [0, 2])
|
| 26 |
+
answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0}
|
| 27 |
+
assert d == answer
|
| 28 |
+
|
| 29 |
+
def test_betweenness_centrality(self):
|
| 30 |
+
c = bipartite.betweenness_centrality(self.P4, [1, 3])
|
| 31 |
+
answer = {0: 0.0, 1: 1.0, 2: 1.0, 3: 0.0}
|
| 32 |
+
assert c == answer
|
| 33 |
+
c = bipartite.betweenness_centrality(self.K3, [0, 1, 2])
|
| 34 |
+
answer = {0: 0.125, 1: 0.125, 2: 0.125, 3: 0.125, 4: 0.125, 5: 0.125}
|
| 35 |
+
assert c == answer
|
| 36 |
+
c = bipartite.betweenness_centrality(self.C4, [0, 2])
|
| 37 |
+
answer = {0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25}
|
| 38 |
+
assert c == answer
|
| 39 |
+
|
| 40 |
+
def test_closeness_centrality(self):
|
| 41 |
+
c = bipartite.closeness_centrality(self.P4, [1, 3])
|
| 42 |
+
answer = {0: 2.0 / 3, 1: 1.0, 2: 1.0, 3: 2.0 / 3}
|
| 43 |
+
assert c == answer
|
| 44 |
+
c = bipartite.closeness_centrality(self.K3, [0, 1, 2])
|
| 45 |
+
answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0, 5: 1.0}
|
| 46 |
+
assert c == answer
|
| 47 |
+
c = bipartite.closeness_centrality(self.C4, [0, 2])
|
| 48 |
+
answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0}
|
| 49 |
+
assert c == answer
|
| 50 |
+
G = nx.Graph()
|
| 51 |
+
G.add_node(0)
|
| 52 |
+
G.add_node(1)
|
| 53 |
+
c = bipartite.closeness_centrality(G, [0])
|
| 54 |
+
assert c == {0: 0.0, 1: 0.0}
|
| 55 |
+
c = bipartite.closeness_centrality(G, [1])
|
| 56 |
+
assert c == {0: 0.0, 1: 0.0}
|
| 57 |
+
|
| 58 |
+
def test_bipartite_closeness_centrality_unconnected(self):
|
| 59 |
+
G = nx.complete_bipartite_graph(3, 3)
|
| 60 |
+
G.add_edge(6, 7)
|
| 61 |
+
c = bipartite.closeness_centrality(G, [0, 2, 4, 6], normalized=False)
|
| 62 |
+
answer = {
|
| 63 |
+
0: 10.0 / 7,
|
| 64 |
+
2: 10.0 / 7,
|
| 65 |
+
4: 10.0 / 7,
|
| 66 |
+
6: 10.0,
|
| 67 |
+
1: 10.0 / 7,
|
| 68 |
+
3: 10.0 / 7,
|
| 69 |
+
5: 10.0 / 7,
|
| 70 |
+
7: 10.0,
|
| 71 |
+
}
|
| 72 |
+
assert c == answer
|
| 73 |
+
|
| 74 |
+
def test_davis_degree_centrality(self):
|
| 75 |
+
G = self.davis
|
| 76 |
+
deg = bipartite.degree_centrality(G, self.top_nodes)
|
| 77 |
+
answer = {
|
| 78 |
+
"E8": 0.78,
|
| 79 |
+
"E9": 0.67,
|
| 80 |
+
"E7": 0.56,
|
| 81 |
+
"Nora Fayette": 0.57,
|
| 82 |
+
"Evelyn Jefferson": 0.57,
|
| 83 |
+
"Theresa Anderson": 0.57,
|
| 84 |
+
"E6": 0.44,
|
| 85 |
+
"Sylvia Avondale": 0.50,
|
| 86 |
+
"Laura Mandeville": 0.50,
|
| 87 |
+
"Brenda Rogers": 0.50,
|
| 88 |
+
"Katherina Rogers": 0.43,
|
| 89 |
+
"E5": 0.44,
|
| 90 |
+
"Helen Lloyd": 0.36,
|
| 91 |
+
"E3": 0.33,
|
| 92 |
+
"Ruth DeSand": 0.29,
|
| 93 |
+
"Verne Sanderson": 0.29,
|
| 94 |
+
"E12": 0.33,
|
| 95 |
+
"Myra Liddel": 0.29,
|
| 96 |
+
"E11": 0.22,
|
| 97 |
+
"Eleanor Nye": 0.29,
|
| 98 |
+
"Frances Anderson": 0.29,
|
| 99 |
+
"Pearl Oglethorpe": 0.21,
|
| 100 |
+
"E4": 0.22,
|
| 101 |
+
"Charlotte McDowd": 0.29,
|
| 102 |
+
"E10": 0.28,
|
| 103 |
+
"Olivia Carleton": 0.14,
|
| 104 |
+
"Flora Price": 0.14,
|
| 105 |
+
"E2": 0.17,
|
| 106 |
+
"E1": 0.17,
|
| 107 |
+
"Dorothy Murchison": 0.14,
|
| 108 |
+
"E13": 0.17,
|
| 109 |
+
"E14": 0.17,
|
| 110 |
+
}
|
| 111 |
+
for node, value in answer.items():
|
| 112 |
+
assert value == pytest.approx(deg[node], abs=1e-2)
|
| 113 |
+
|
| 114 |
+
def test_davis_betweenness_centrality(self):
|
| 115 |
+
G = self.davis
|
| 116 |
+
bet = bipartite.betweenness_centrality(G, self.top_nodes)
|
| 117 |
+
answer = {
|
| 118 |
+
"E8": 0.24,
|
| 119 |
+
"E9": 0.23,
|
| 120 |
+
"E7": 0.13,
|
| 121 |
+
"Nora Fayette": 0.11,
|
| 122 |
+
"Evelyn Jefferson": 0.10,
|
| 123 |
+
"Theresa Anderson": 0.09,
|
| 124 |
+
"E6": 0.07,
|
| 125 |
+
"Sylvia Avondale": 0.07,
|
| 126 |
+
"Laura Mandeville": 0.05,
|
| 127 |
+
"Brenda Rogers": 0.05,
|
| 128 |
+
"Katherina Rogers": 0.05,
|
| 129 |
+
"E5": 0.04,
|
| 130 |
+
"Helen Lloyd": 0.04,
|
| 131 |
+
"E3": 0.02,
|
| 132 |
+
"Ruth DeSand": 0.02,
|
| 133 |
+
"Verne Sanderson": 0.02,
|
| 134 |
+
"E12": 0.02,
|
| 135 |
+
"Myra Liddel": 0.02,
|
| 136 |
+
"E11": 0.02,
|
| 137 |
+
"Eleanor Nye": 0.01,
|
| 138 |
+
"Frances Anderson": 0.01,
|
| 139 |
+
"Pearl Oglethorpe": 0.01,
|
| 140 |
+
"E4": 0.01,
|
| 141 |
+
"Charlotte McDowd": 0.01,
|
| 142 |
+
"E10": 0.01,
|
| 143 |
+
"Olivia Carleton": 0.01,
|
| 144 |
+
"Flora Price": 0.01,
|
| 145 |
+
"E2": 0.00,
|
| 146 |
+
"E1": 0.00,
|
| 147 |
+
"Dorothy Murchison": 0.00,
|
| 148 |
+
"E13": 0.00,
|
| 149 |
+
"E14": 0.00,
|
| 150 |
+
}
|
| 151 |
+
for node, value in answer.items():
|
| 152 |
+
assert value == pytest.approx(bet[node], abs=1e-2)
|
| 153 |
+
|
| 154 |
+
def test_davis_closeness_centrality(self):
|
| 155 |
+
G = self.davis
|
| 156 |
+
clos = bipartite.closeness_centrality(G, self.top_nodes)
|
| 157 |
+
answer = {
|
| 158 |
+
"E8": 0.85,
|
| 159 |
+
"E9": 0.79,
|
| 160 |
+
"E7": 0.73,
|
| 161 |
+
"Nora Fayette": 0.80,
|
| 162 |
+
"Evelyn Jefferson": 0.80,
|
| 163 |
+
"Theresa Anderson": 0.80,
|
| 164 |
+
"E6": 0.69,
|
| 165 |
+
"Sylvia Avondale": 0.77,
|
| 166 |
+
"Laura Mandeville": 0.73,
|
| 167 |
+
"Brenda Rogers": 0.73,
|
| 168 |
+
"Katherina Rogers": 0.73,
|
| 169 |
+
"E5": 0.59,
|
| 170 |
+
"Helen Lloyd": 0.73,
|
| 171 |
+
"E3": 0.56,
|
| 172 |
+
"Ruth DeSand": 0.71,
|
| 173 |
+
"Verne Sanderson": 0.71,
|
| 174 |
+
"E12": 0.56,
|
| 175 |
+
"Myra Liddel": 0.69,
|
| 176 |
+
"E11": 0.54,
|
| 177 |
+
"Eleanor Nye": 0.67,
|
| 178 |
+
"Frances Anderson": 0.67,
|
| 179 |
+
"Pearl Oglethorpe": 0.67,
|
| 180 |
+
"E4": 0.54,
|
| 181 |
+
"Charlotte McDowd": 0.60,
|
| 182 |
+
"E10": 0.55,
|
| 183 |
+
"Olivia Carleton": 0.59,
|
| 184 |
+
"Flora Price": 0.59,
|
| 185 |
+
"E2": 0.52,
|
| 186 |
+
"E1": 0.52,
|
| 187 |
+
"Dorothy Murchison": 0.65,
|
| 188 |
+
"E13": 0.52,
|
| 189 |
+
"E14": 0.52,
|
| 190 |
+
}
|
| 191 |
+
for node, value in answer.items():
|
| 192 |
+
assert value == pytest.approx(clos[node], abs=1e-2)
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_cluster.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.algorithms import bipartite
|
| 5 |
+
from networkx.algorithms.bipartite.cluster import cc_dot, cc_max, cc_min
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_pairwise_bipartite_cc_functions():
|
| 9 |
+
# Test functions for different kinds of bipartite clustering coefficients
|
| 10 |
+
# between pairs of nodes using 3 example graphs from figure 5 p. 40
|
| 11 |
+
# Latapy et al (2008)
|
| 12 |
+
G1 = nx.Graph([(0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (1, 5), (1, 6), (1, 7)])
|
| 13 |
+
G2 = nx.Graph([(0, 2), (0, 3), (0, 4), (1, 3), (1, 4), (1, 5)])
|
| 14 |
+
G3 = nx.Graph(
|
| 15 |
+
[(0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9)]
|
| 16 |
+
)
|
| 17 |
+
result = {
|
| 18 |
+
0: [1 / 3.0, 2 / 3.0, 2 / 5.0],
|
| 19 |
+
1: [1 / 2.0, 2 / 3.0, 2 / 3.0],
|
| 20 |
+
2: [2 / 8.0, 2 / 5.0, 2 / 5.0],
|
| 21 |
+
}
|
| 22 |
+
for i, G in enumerate([G1, G2, G3]):
|
| 23 |
+
assert bipartite.is_bipartite(G)
|
| 24 |
+
assert cc_dot(set(G[0]), set(G[1])) == result[i][0]
|
| 25 |
+
assert cc_min(set(G[0]), set(G[1])) == result[i][1]
|
| 26 |
+
assert cc_max(set(G[0]), set(G[1])) == result[i][2]
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def test_star_graph():
|
| 30 |
+
G = nx.star_graph(3)
|
| 31 |
+
# all modes are the same
|
| 32 |
+
answer = {0: 0, 1: 1, 2: 1, 3: 1}
|
| 33 |
+
assert bipartite.clustering(G, mode="dot") == answer
|
| 34 |
+
assert bipartite.clustering(G, mode="min") == answer
|
| 35 |
+
assert bipartite.clustering(G, mode="max") == answer
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def test_not_bipartite():
|
| 39 |
+
with pytest.raises(nx.NetworkXError):
|
| 40 |
+
bipartite.clustering(nx.complete_graph(4))
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def test_bad_mode():
|
| 44 |
+
with pytest.raises(nx.NetworkXError):
|
| 45 |
+
bipartite.clustering(nx.path_graph(4), mode="foo")
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def test_path_graph():
|
| 49 |
+
G = nx.path_graph(4)
|
| 50 |
+
answer = {0: 0.5, 1: 0.5, 2: 0.5, 3: 0.5}
|
| 51 |
+
assert bipartite.clustering(G, mode="dot") == answer
|
| 52 |
+
assert bipartite.clustering(G, mode="max") == answer
|
| 53 |
+
answer = {0: 1, 1: 1, 2: 1, 3: 1}
|
| 54 |
+
assert bipartite.clustering(G, mode="min") == answer
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def test_average_path_graph():
|
| 58 |
+
G = nx.path_graph(4)
|
| 59 |
+
assert bipartite.average_clustering(G, mode="dot") == 0.5
|
| 60 |
+
assert bipartite.average_clustering(G, mode="max") == 0.5
|
| 61 |
+
assert bipartite.average_clustering(G, mode="min") == 1
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def test_ra_clustering_davis():
|
| 65 |
+
G = nx.davis_southern_women_graph()
|
| 66 |
+
cc4 = round(bipartite.robins_alexander_clustering(G), 3)
|
| 67 |
+
assert cc4 == 0.468
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def test_ra_clustering_square():
|
| 71 |
+
G = nx.path_graph(4)
|
| 72 |
+
G.add_edge(0, 3)
|
| 73 |
+
assert bipartite.robins_alexander_clustering(G) == 1.0
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def test_ra_clustering_zero():
|
| 77 |
+
G = nx.Graph()
|
| 78 |
+
assert bipartite.robins_alexander_clustering(G) == 0
|
| 79 |
+
G.add_nodes_from(range(4))
|
| 80 |
+
assert bipartite.robins_alexander_clustering(G) == 0
|
| 81 |
+
G.add_edges_from([(0, 1), (2, 3), (3, 4)])
|
| 82 |
+
assert bipartite.robins_alexander_clustering(G) == 0
|
| 83 |
+
G.add_edge(1, 2)
|
| 84 |
+
assert bipartite.robins_alexander_clustering(G) == 0
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_covering.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import networkx as nx
|
| 2 |
+
from networkx.algorithms import bipartite
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class TestMinEdgeCover:
|
| 6 |
+
"""Tests for :func:`networkx.algorithms.bipartite.min_edge_cover`"""
|
| 7 |
+
|
| 8 |
+
def test_empty_graph(self):
|
| 9 |
+
G = nx.Graph()
|
| 10 |
+
assert bipartite.min_edge_cover(G) == set()
|
| 11 |
+
|
| 12 |
+
def test_graph_single_edge(self):
|
| 13 |
+
G = nx.Graph()
|
| 14 |
+
G.add_edge(0, 1)
|
| 15 |
+
assert bipartite.min_edge_cover(G) == {(0, 1), (1, 0)}
|
| 16 |
+
|
| 17 |
+
def test_bipartite_default(self):
|
| 18 |
+
G = nx.Graph()
|
| 19 |
+
G.add_nodes_from([1, 2, 3, 4], bipartite=0)
|
| 20 |
+
G.add_nodes_from(["a", "b", "c"], bipartite=1)
|
| 21 |
+
G.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")])
|
| 22 |
+
min_cover = bipartite.min_edge_cover(G)
|
| 23 |
+
assert nx.is_edge_cover(G, min_cover)
|
| 24 |
+
assert len(min_cover) == 8
|
| 25 |
+
|
| 26 |
+
def test_bipartite_explicit(self):
|
| 27 |
+
G = nx.Graph()
|
| 28 |
+
G.add_nodes_from([1, 2, 3, 4], bipartite=0)
|
| 29 |
+
G.add_nodes_from(["a", "b", "c"], bipartite=1)
|
| 30 |
+
G.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")])
|
| 31 |
+
min_cover = bipartite.min_edge_cover(G, bipartite.eppstein_matching)
|
| 32 |
+
assert nx.is_edge_cover(G, min_cover)
|
| 33 |
+
assert len(min_cover) == 8
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_edgelist.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Unit tests for bipartite edgelists.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import io
|
| 6 |
+
|
| 7 |
+
import pytest
|
| 8 |
+
|
| 9 |
+
import networkx as nx
|
| 10 |
+
from networkx.algorithms import bipartite
|
| 11 |
+
from networkx.utils import edges_equal, graphs_equal, nodes_equal
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class TestEdgelist:
|
| 15 |
+
@classmethod
|
| 16 |
+
def setup_class(cls):
|
| 17 |
+
cls.G = nx.Graph(name="test")
|
| 18 |
+
e = [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f"), ("a", "f")]
|
| 19 |
+
cls.G.add_edges_from(e)
|
| 20 |
+
cls.G.add_nodes_from(["a", "c", "e"], bipartite=0)
|
| 21 |
+
cls.G.add_nodes_from(["b", "d", "f"], bipartite=1)
|
| 22 |
+
cls.G.add_node("g", bipartite=0)
|
| 23 |
+
cls.DG = nx.DiGraph(cls.G)
|
| 24 |
+
cls.MG = nx.MultiGraph()
|
| 25 |
+
cls.MG.add_edges_from([(1, 2), (1, 2), (1, 2)])
|
| 26 |
+
cls.MG.add_node(1, bipartite=0)
|
| 27 |
+
cls.MG.add_node(2, bipartite=1)
|
| 28 |
+
|
| 29 |
+
def test_read_edgelist_1(self):
|
| 30 |
+
s = b"""\
|
| 31 |
+
# comment line
|
| 32 |
+
1 2
|
| 33 |
+
# comment line
|
| 34 |
+
2 3
|
| 35 |
+
"""
|
| 36 |
+
bytesIO = io.BytesIO(s)
|
| 37 |
+
G = bipartite.read_edgelist(bytesIO, nodetype=int)
|
| 38 |
+
assert edges_equal(G.edges(), [(1, 2), (2, 3)])
|
| 39 |
+
|
| 40 |
+
def test_read_edgelist_3(self):
|
| 41 |
+
s = b"""\
|
| 42 |
+
# comment line
|
| 43 |
+
1 2 {'weight':2.0}
|
| 44 |
+
# comment line
|
| 45 |
+
2 3 {'weight':3.0}
|
| 46 |
+
"""
|
| 47 |
+
bytesIO = io.BytesIO(s)
|
| 48 |
+
G = bipartite.read_edgelist(bytesIO, nodetype=int, data=False)
|
| 49 |
+
assert edges_equal(G.edges(), [(1, 2), (2, 3)])
|
| 50 |
+
|
| 51 |
+
bytesIO = io.BytesIO(s)
|
| 52 |
+
G = bipartite.read_edgelist(bytesIO, nodetype=int, data=True)
|
| 53 |
+
assert edges_equal(
|
| 54 |
+
G.edges(data=True), [(1, 2, {"weight": 2.0}), (2, 3, {"weight": 3.0})]
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
def test_write_edgelist_1(self):
|
| 58 |
+
fh = io.BytesIO()
|
| 59 |
+
G = nx.Graph()
|
| 60 |
+
G.add_edges_from([(1, 2), (2, 3)])
|
| 61 |
+
G.add_node(1, bipartite=0)
|
| 62 |
+
G.add_node(2, bipartite=1)
|
| 63 |
+
G.add_node(3, bipartite=0)
|
| 64 |
+
bipartite.write_edgelist(G, fh, data=False)
|
| 65 |
+
fh.seek(0)
|
| 66 |
+
assert fh.read() == b"1 2\n3 2\n"
|
| 67 |
+
|
| 68 |
+
def test_write_edgelist_2(self):
|
| 69 |
+
fh = io.BytesIO()
|
| 70 |
+
G = nx.Graph()
|
| 71 |
+
G.add_edges_from([(1, 2), (2, 3)])
|
| 72 |
+
G.add_node(1, bipartite=0)
|
| 73 |
+
G.add_node(2, bipartite=1)
|
| 74 |
+
G.add_node(3, bipartite=0)
|
| 75 |
+
bipartite.write_edgelist(G, fh, data=True)
|
| 76 |
+
fh.seek(0)
|
| 77 |
+
assert fh.read() == b"1 2 {}\n3 2 {}\n"
|
| 78 |
+
|
| 79 |
+
def test_write_edgelist_3(self):
|
| 80 |
+
fh = io.BytesIO()
|
| 81 |
+
G = nx.Graph()
|
| 82 |
+
G.add_edge(1, 2, weight=2.0)
|
| 83 |
+
G.add_edge(2, 3, weight=3.0)
|
| 84 |
+
G.add_node(1, bipartite=0)
|
| 85 |
+
G.add_node(2, bipartite=1)
|
| 86 |
+
G.add_node(3, bipartite=0)
|
| 87 |
+
bipartite.write_edgelist(G, fh, data=True)
|
| 88 |
+
fh.seek(0)
|
| 89 |
+
assert fh.read() == b"1 2 {'weight': 2.0}\n3 2 {'weight': 3.0}\n"
|
| 90 |
+
|
| 91 |
+
def test_write_edgelist_4(self):
|
| 92 |
+
fh = io.BytesIO()
|
| 93 |
+
G = nx.Graph()
|
| 94 |
+
G.add_edge(1, 2, weight=2.0)
|
| 95 |
+
G.add_edge(2, 3, weight=3.0)
|
| 96 |
+
G.add_node(1, bipartite=0)
|
| 97 |
+
G.add_node(2, bipartite=1)
|
| 98 |
+
G.add_node(3, bipartite=0)
|
| 99 |
+
bipartite.write_edgelist(G, fh, data=[("weight")])
|
| 100 |
+
fh.seek(0)
|
| 101 |
+
assert fh.read() == b"1 2 2.0\n3 2 3.0\n"
|
| 102 |
+
|
| 103 |
+
def test_unicode(self, tmp_path):
|
| 104 |
+
G = nx.Graph()
|
| 105 |
+
name1 = chr(2344) + chr(123) + chr(6543)
|
| 106 |
+
name2 = chr(5543) + chr(1543) + chr(324)
|
| 107 |
+
G.add_edge(name1, "Radiohead", **{name2: 3})
|
| 108 |
+
G.add_node(name1, bipartite=0)
|
| 109 |
+
G.add_node("Radiohead", bipartite=1)
|
| 110 |
+
|
| 111 |
+
fname = tmp_path / "edgelist.txt"
|
| 112 |
+
bipartite.write_edgelist(G, fname)
|
| 113 |
+
H = bipartite.read_edgelist(fname)
|
| 114 |
+
assert graphs_equal(G, H)
|
| 115 |
+
|
| 116 |
+
def test_latin1_issue(self, tmp_path):
|
| 117 |
+
G = nx.Graph()
|
| 118 |
+
name1 = chr(2344) + chr(123) + chr(6543)
|
| 119 |
+
name2 = chr(5543) + chr(1543) + chr(324)
|
| 120 |
+
G.add_edge(name1, "Radiohead", **{name2: 3})
|
| 121 |
+
G.add_node(name1, bipartite=0)
|
| 122 |
+
G.add_node("Radiohead", bipartite=1)
|
| 123 |
+
|
| 124 |
+
fname = tmp_path / "edgelist.txt"
|
| 125 |
+
with pytest.raises(UnicodeEncodeError):
|
| 126 |
+
bipartite.write_edgelist(G, fname, encoding="latin-1")
|
| 127 |
+
|
| 128 |
+
def test_latin1(self, tmp_path):
|
| 129 |
+
G = nx.Graph()
|
| 130 |
+
name1 = "Bj" + chr(246) + "rk"
|
| 131 |
+
name2 = chr(220) + "ber"
|
| 132 |
+
G.add_edge(name1, "Radiohead", **{name2: 3})
|
| 133 |
+
G.add_node(name1, bipartite=0)
|
| 134 |
+
G.add_node("Radiohead", bipartite=1)
|
| 135 |
+
|
| 136 |
+
fname = tmp_path / "edgelist.txt"
|
| 137 |
+
bipartite.write_edgelist(G, fname, encoding="latin-1")
|
| 138 |
+
H = bipartite.read_edgelist(fname, encoding="latin-1")
|
| 139 |
+
assert graphs_equal(G, H)
|
| 140 |
+
|
| 141 |
+
def test_edgelist_graph(self, tmp_path):
|
| 142 |
+
G = self.G
|
| 143 |
+
fname = tmp_path / "edgelist.txt"
|
| 144 |
+
bipartite.write_edgelist(G, fname)
|
| 145 |
+
H = bipartite.read_edgelist(fname)
|
| 146 |
+
H2 = bipartite.read_edgelist(fname)
|
| 147 |
+
assert H is not H2 # they should be different graphs
|
| 148 |
+
G.remove_node("g") # isolated nodes are not written in edgelist
|
| 149 |
+
assert nodes_equal(list(H), list(G))
|
| 150 |
+
assert edges_equal(list(H.edges()), list(G.edges()))
|
| 151 |
+
|
| 152 |
+
def test_edgelist_integers(self, tmp_path):
|
| 153 |
+
G = nx.convert_node_labels_to_integers(self.G)
|
| 154 |
+
fname = tmp_path / "edgelist.txt"
|
| 155 |
+
bipartite.write_edgelist(G, fname)
|
| 156 |
+
H = bipartite.read_edgelist(fname, nodetype=int)
|
| 157 |
+
# isolated nodes are not written in edgelist
|
| 158 |
+
G.remove_nodes_from(list(nx.isolates(G)))
|
| 159 |
+
assert nodes_equal(list(H), list(G))
|
| 160 |
+
assert edges_equal(list(H.edges()), list(G.edges()))
|
| 161 |
+
|
| 162 |
+
def test_edgelist_multigraph(self, tmp_path):
|
| 163 |
+
G = self.MG
|
| 164 |
+
fname = tmp_path / "edgelist.txt"
|
| 165 |
+
bipartite.write_edgelist(G, fname)
|
| 166 |
+
H = bipartite.read_edgelist(fname, nodetype=int, create_using=nx.MultiGraph())
|
| 167 |
+
H2 = bipartite.read_edgelist(fname, nodetype=int, create_using=nx.MultiGraph())
|
| 168 |
+
assert H is not H2 # they should be different graphs
|
| 169 |
+
assert nodes_equal(list(H), list(G))
|
| 170 |
+
assert edges_equal(list(H.edges()), list(G.edges()))
|
| 171 |
+
|
| 172 |
+
def test_empty_digraph(self):
|
| 173 |
+
with pytest.raises(nx.NetworkXNotImplemented):
|
| 174 |
+
bytesIO = io.BytesIO()
|
| 175 |
+
bipartite.write_edgelist(nx.DiGraph(), bytesIO)
|
| 176 |
+
|
| 177 |
+
def test_raise_attribute(self):
|
| 178 |
+
with pytest.raises(AttributeError):
|
| 179 |
+
G = nx.path_graph(4)
|
| 180 |
+
bytesIO = io.BytesIO()
|
| 181 |
+
bipartite.write_edgelist(G, bytesIO)
|
| 182 |
+
|
| 183 |
+
def test_parse_edgelist(self):
|
| 184 |
+
"""Tests for conditions specific to
|
| 185 |
+
parse_edge_list method"""
|
| 186 |
+
|
| 187 |
+
# ignore strings of length less than 2
|
| 188 |
+
lines = ["1 2", "2 3", "3 1", "4", " "]
|
| 189 |
+
G = bipartite.parse_edgelist(lines, nodetype=int)
|
| 190 |
+
assert list(G.nodes) == [1, 2, 3]
|
| 191 |
+
|
| 192 |
+
# Exception raised when node is not convertible
|
| 193 |
+
# to specified data type
|
| 194 |
+
with pytest.raises(TypeError, match=".*Failed to convert nodes"):
|
| 195 |
+
lines = ["a b", "b c", "c a"]
|
| 196 |
+
G = bipartite.parse_edgelist(lines, nodetype=int)
|
| 197 |
+
|
| 198 |
+
# Exception raised when format of data is not
|
| 199 |
+
# convertible to dictionary object
|
| 200 |
+
with pytest.raises(TypeError, match=".*Failed to convert edge data"):
|
| 201 |
+
lines = ["1 2 3", "2 3 4", "3 1 2"]
|
| 202 |
+
G = bipartite.parse_edgelist(lines, nodetype=int)
|
| 203 |
+
|
| 204 |
+
# Exception raised when edge data and data
|
| 205 |
+
# keys are not of same length
|
| 206 |
+
with pytest.raises(IndexError):
|
| 207 |
+
lines = ["1 2 3 4", "2 3 4"]
|
| 208 |
+
G = bipartite.parse_edgelist(
|
| 209 |
+
lines, nodetype=int, data=[("weight", int), ("key", int)]
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
# Exception raised when edge data is not
|
| 213 |
+
# convertible to specified data type
|
| 214 |
+
with pytest.raises(TypeError, match=".*Failed to convert key data"):
|
| 215 |
+
lines = ["1 2 3 a", "2 3 4 b"]
|
| 216 |
+
G = bipartite.parse_edgelist(
|
| 217 |
+
lines, nodetype=int, data=[("weight", int), ("key", int)]
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def test_bipartite_edgelist_consistent_strip_handling():
|
| 222 |
+
"""See gh-7462
|
| 223 |
+
|
| 224 |
+
Input when printed looks like:
|
| 225 |
+
|
| 226 |
+
A B interaction 2
|
| 227 |
+
B C interaction 4
|
| 228 |
+
C A interaction
|
| 229 |
+
|
| 230 |
+
Note the trailing \\t in the last line, which indicates the existence of
|
| 231 |
+
an empty data field.
|
| 232 |
+
"""
|
| 233 |
+
lines = io.StringIO(
|
| 234 |
+
"A\tB\tinteraction\t2\nB\tC\tinteraction\t4\nC\tA\tinteraction\t"
|
| 235 |
+
)
|
| 236 |
+
descr = [("type", str), ("weight", str)]
|
| 237 |
+
# Should not raise
|
| 238 |
+
G = nx.bipartite.parse_edgelist(lines, delimiter="\t", data=descr)
|
| 239 |
+
expected = [("A", "B", "2"), ("A", "C", ""), ("B", "C", "4")]
|
| 240 |
+
assert sorted(G.edges(data="weight")) == expected
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_extendability.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def test_selfloops_raises():
|
| 7 |
+
G = nx.ladder_graph(3)
|
| 8 |
+
G.add_edge(0, 0)
|
| 9 |
+
with pytest.raises(nx.NetworkXError, match=".*not bipartite"):
|
| 10 |
+
nx.bipartite.maximal_extendability(G)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def test_disconnected_raises():
|
| 14 |
+
G = nx.ladder_graph(3)
|
| 15 |
+
G.add_node("a")
|
| 16 |
+
with pytest.raises(nx.NetworkXError, match=".*not connected"):
|
| 17 |
+
nx.bipartite.maximal_extendability(G)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def test_not_bipartite_raises():
|
| 21 |
+
G = nx.complete_graph(5)
|
| 22 |
+
with pytest.raises(nx.NetworkXError, match=".*not bipartite"):
|
| 23 |
+
nx.bipartite.maximal_extendability(G)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def test_no_perfect_matching_raises():
|
| 27 |
+
G = nx.Graph([(0, 1), (0, 2)])
|
| 28 |
+
with pytest.raises(nx.NetworkXError, match=".*not contain a perfect matching"):
|
| 29 |
+
nx.bipartite.maximal_extendability(G)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def test_residual_graph_not_strongly_connected_raises():
|
| 33 |
+
G = nx.Graph([(1, 2), (2, 3), (3, 4)])
|
| 34 |
+
with pytest.raises(
|
| 35 |
+
nx.NetworkXError, match="The residual graph of G is not strongly connected"
|
| 36 |
+
):
|
| 37 |
+
nx.bipartite.maximal_extendability(G)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def test_ladder_graph_is_1():
|
| 41 |
+
G = nx.ladder_graph(3)
|
| 42 |
+
assert nx.bipartite.maximal_extendability(G) == 1
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def test_cubical_graph_is_2():
|
| 46 |
+
G = nx.cubical_graph()
|
| 47 |
+
assert nx.bipartite.maximal_extendability(G) == 2
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def test_k_is_3():
|
| 51 |
+
G = nx.Graph(
|
| 52 |
+
[
|
| 53 |
+
(1, 6),
|
| 54 |
+
(1, 7),
|
| 55 |
+
(1, 8),
|
| 56 |
+
(1, 9),
|
| 57 |
+
(2, 6),
|
| 58 |
+
(2, 7),
|
| 59 |
+
(2, 8),
|
| 60 |
+
(2, 10),
|
| 61 |
+
(3, 6),
|
| 62 |
+
(3, 8),
|
| 63 |
+
(3, 9),
|
| 64 |
+
(3, 10),
|
| 65 |
+
(4, 7),
|
| 66 |
+
(4, 8),
|
| 67 |
+
(4, 9),
|
| 68 |
+
(4, 10),
|
| 69 |
+
(5, 6),
|
| 70 |
+
(5, 7),
|
| 71 |
+
(5, 9),
|
| 72 |
+
(5, 10),
|
| 73 |
+
]
|
| 74 |
+
)
|
| 75 |
+
assert nx.bipartite.maximal_extendability(G) == 3
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def test_k_is_4():
|
| 79 |
+
G = nx.Graph(
|
| 80 |
+
[
|
| 81 |
+
(8, 1),
|
| 82 |
+
(8, 2),
|
| 83 |
+
(8, 3),
|
| 84 |
+
(8, 4),
|
| 85 |
+
(8, 5),
|
| 86 |
+
(9, 1),
|
| 87 |
+
(9, 2),
|
| 88 |
+
(9, 3),
|
| 89 |
+
(9, 4),
|
| 90 |
+
(9, 7),
|
| 91 |
+
(10, 1),
|
| 92 |
+
(10, 2),
|
| 93 |
+
(10, 3),
|
| 94 |
+
(10, 4),
|
| 95 |
+
(10, 6),
|
| 96 |
+
(11, 1),
|
| 97 |
+
(11, 2),
|
| 98 |
+
(11, 5),
|
| 99 |
+
(11, 6),
|
| 100 |
+
(11, 7),
|
| 101 |
+
(12, 1),
|
| 102 |
+
(12, 3),
|
| 103 |
+
(12, 5),
|
| 104 |
+
(12, 6),
|
| 105 |
+
(12, 7),
|
| 106 |
+
(13, 2),
|
| 107 |
+
(13, 4),
|
| 108 |
+
(13, 5),
|
| 109 |
+
(13, 6),
|
| 110 |
+
(13, 7),
|
| 111 |
+
(14, 3),
|
| 112 |
+
(14, 4),
|
| 113 |
+
(14, 5),
|
| 114 |
+
(14, 6),
|
| 115 |
+
(14, 7),
|
| 116 |
+
]
|
| 117 |
+
)
|
| 118 |
+
assert nx.bipartite.maximal_extendability(G) == 4
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def test_k_is_5():
|
| 122 |
+
G = nx.Graph(
|
| 123 |
+
[
|
| 124 |
+
(8, 1),
|
| 125 |
+
(8, 2),
|
| 126 |
+
(8, 3),
|
| 127 |
+
(8, 4),
|
| 128 |
+
(8, 5),
|
| 129 |
+
(8, 6),
|
| 130 |
+
(9, 1),
|
| 131 |
+
(9, 2),
|
| 132 |
+
(9, 3),
|
| 133 |
+
(9, 4),
|
| 134 |
+
(9, 5),
|
| 135 |
+
(9, 7),
|
| 136 |
+
(10, 1),
|
| 137 |
+
(10, 2),
|
| 138 |
+
(10, 3),
|
| 139 |
+
(10, 4),
|
| 140 |
+
(10, 6),
|
| 141 |
+
(10, 7),
|
| 142 |
+
(11, 1),
|
| 143 |
+
(11, 2),
|
| 144 |
+
(11, 3),
|
| 145 |
+
(11, 5),
|
| 146 |
+
(11, 6),
|
| 147 |
+
(11, 7),
|
| 148 |
+
(12, 1),
|
| 149 |
+
(12, 2),
|
| 150 |
+
(12, 4),
|
| 151 |
+
(12, 5),
|
| 152 |
+
(12, 6),
|
| 153 |
+
(12, 7),
|
| 154 |
+
(13, 1),
|
| 155 |
+
(13, 3),
|
| 156 |
+
(13, 4),
|
| 157 |
+
(13, 5),
|
| 158 |
+
(13, 6),
|
| 159 |
+
(13, 7),
|
| 160 |
+
(14, 2),
|
| 161 |
+
(14, 3),
|
| 162 |
+
(14, 4),
|
| 163 |
+
(14, 5),
|
| 164 |
+
(14, 6),
|
| 165 |
+
(14, 7),
|
| 166 |
+
]
|
| 167 |
+
)
|
| 168 |
+
assert nx.bipartite.maximal_extendability(G) == 5
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def test_k_is_6():
|
| 172 |
+
G = nx.Graph(
|
| 173 |
+
[
|
| 174 |
+
(9, 1),
|
| 175 |
+
(9, 2),
|
| 176 |
+
(9, 3),
|
| 177 |
+
(9, 4),
|
| 178 |
+
(9, 5),
|
| 179 |
+
(9, 6),
|
| 180 |
+
(9, 7),
|
| 181 |
+
(10, 1),
|
| 182 |
+
(10, 2),
|
| 183 |
+
(10, 3),
|
| 184 |
+
(10, 4),
|
| 185 |
+
(10, 5),
|
| 186 |
+
(10, 6),
|
| 187 |
+
(10, 8),
|
| 188 |
+
(11, 1),
|
| 189 |
+
(11, 2),
|
| 190 |
+
(11, 3),
|
| 191 |
+
(11, 4),
|
| 192 |
+
(11, 5),
|
| 193 |
+
(11, 7),
|
| 194 |
+
(11, 8),
|
| 195 |
+
(12, 1),
|
| 196 |
+
(12, 2),
|
| 197 |
+
(12, 3),
|
| 198 |
+
(12, 4),
|
| 199 |
+
(12, 6),
|
| 200 |
+
(12, 7),
|
| 201 |
+
(12, 8),
|
| 202 |
+
(13, 1),
|
| 203 |
+
(13, 2),
|
| 204 |
+
(13, 3),
|
| 205 |
+
(13, 5),
|
| 206 |
+
(13, 6),
|
| 207 |
+
(13, 7),
|
| 208 |
+
(13, 8),
|
| 209 |
+
(14, 1),
|
| 210 |
+
(14, 2),
|
| 211 |
+
(14, 4),
|
| 212 |
+
(14, 5),
|
| 213 |
+
(14, 6),
|
| 214 |
+
(14, 7),
|
| 215 |
+
(14, 8),
|
| 216 |
+
(15, 1),
|
| 217 |
+
(15, 3),
|
| 218 |
+
(15, 4),
|
| 219 |
+
(15, 5),
|
| 220 |
+
(15, 6),
|
| 221 |
+
(15, 7),
|
| 222 |
+
(15, 8),
|
| 223 |
+
(16, 2),
|
| 224 |
+
(16, 3),
|
| 225 |
+
(16, 4),
|
| 226 |
+
(16, 5),
|
| 227 |
+
(16, 6),
|
| 228 |
+
(16, 7),
|
| 229 |
+
(16, 8),
|
| 230 |
+
]
|
| 231 |
+
)
|
| 232 |
+
assert nx.bipartite.maximal_extendability(G) == 6
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def test_k_is_7():
|
| 236 |
+
G = nx.Graph(
|
| 237 |
+
[
|
| 238 |
+
(1, 11),
|
| 239 |
+
(1, 12),
|
| 240 |
+
(1, 13),
|
| 241 |
+
(1, 14),
|
| 242 |
+
(1, 15),
|
| 243 |
+
(1, 16),
|
| 244 |
+
(1, 17),
|
| 245 |
+
(1, 18),
|
| 246 |
+
(2, 11),
|
| 247 |
+
(2, 12),
|
| 248 |
+
(2, 13),
|
| 249 |
+
(2, 14),
|
| 250 |
+
(2, 15),
|
| 251 |
+
(2, 16),
|
| 252 |
+
(2, 17),
|
| 253 |
+
(2, 19),
|
| 254 |
+
(3, 11),
|
| 255 |
+
(3, 12),
|
| 256 |
+
(3, 13),
|
| 257 |
+
(3, 14),
|
| 258 |
+
(3, 15),
|
| 259 |
+
(3, 16),
|
| 260 |
+
(3, 17),
|
| 261 |
+
(3, 20),
|
| 262 |
+
(4, 11),
|
| 263 |
+
(4, 12),
|
| 264 |
+
(4, 13),
|
| 265 |
+
(4, 14),
|
| 266 |
+
(4, 15),
|
| 267 |
+
(4, 16),
|
| 268 |
+
(4, 17),
|
| 269 |
+
(4, 18),
|
| 270 |
+
(4, 19),
|
| 271 |
+
(4, 20),
|
| 272 |
+
(5, 11),
|
| 273 |
+
(5, 12),
|
| 274 |
+
(5, 13),
|
| 275 |
+
(5, 14),
|
| 276 |
+
(5, 15),
|
| 277 |
+
(5, 16),
|
| 278 |
+
(5, 17),
|
| 279 |
+
(5, 18),
|
| 280 |
+
(5, 19),
|
| 281 |
+
(5, 20),
|
| 282 |
+
(6, 11),
|
| 283 |
+
(6, 12),
|
| 284 |
+
(6, 13),
|
| 285 |
+
(6, 14),
|
| 286 |
+
(6, 15),
|
| 287 |
+
(6, 16),
|
| 288 |
+
(6, 17),
|
| 289 |
+
(6, 18),
|
| 290 |
+
(6, 19),
|
| 291 |
+
(6, 20),
|
| 292 |
+
(7, 11),
|
| 293 |
+
(7, 12),
|
| 294 |
+
(7, 13),
|
| 295 |
+
(7, 14),
|
| 296 |
+
(7, 15),
|
| 297 |
+
(7, 16),
|
| 298 |
+
(7, 17),
|
| 299 |
+
(7, 18),
|
| 300 |
+
(7, 19),
|
| 301 |
+
(7, 20),
|
| 302 |
+
(8, 11),
|
| 303 |
+
(8, 12),
|
| 304 |
+
(8, 13),
|
| 305 |
+
(8, 14),
|
| 306 |
+
(8, 15),
|
| 307 |
+
(8, 16),
|
| 308 |
+
(8, 17),
|
| 309 |
+
(8, 18),
|
| 310 |
+
(8, 19),
|
| 311 |
+
(8, 20),
|
| 312 |
+
(9, 11),
|
| 313 |
+
(9, 12),
|
| 314 |
+
(9, 13),
|
| 315 |
+
(9, 14),
|
| 316 |
+
(9, 15),
|
| 317 |
+
(9, 16),
|
| 318 |
+
(9, 17),
|
| 319 |
+
(9, 18),
|
| 320 |
+
(9, 19),
|
| 321 |
+
(9, 20),
|
| 322 |
+
(10, 11),
|
| 323 |
+
(10, 12),
|
| 324 |
+
(10, 13),
|
| 325 |
+
(10, 14),
|
| 326 |
+
(10, 15),
|
| 327 |
+
(10, 16),
|
| 328 |
+
(10, 17),
|
| 329 |
+
(10, 18),
|
| 330 |
+
(10, 19),
|
| 331 |
+
(10, 20),
|
| 332 |
+
]
|
| 333 |
+
)
|
| 334 |
+
assert nx.bipartite.maximal_extendability(G) == 7
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_generators.py
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numbers
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
|
| 7 |
+
from ..generators import (
|
| 8 |
+
alternating_havel_hakimi_graph,
|
| 9 |
+
complete_bipartite_graph,
|
| 10 |
+
configuration_model,
|
| 11 |
+
gnmk_random_graph,
|
| 12 |
+
havel_hakimi_graph,
|
| 13 |
+
preferential_attachment_graph,
|
| 14 |
+
random_graph,
|
| 15 |
+
reverse_havel_hakimi_graph,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
"""
|
| 19 |
+
Generators - Bipartite
|
| 20 |
+
----------------------
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class TestGeneratorsBipartite:
|
| 25 |
+
def test_complete_bipartite_graph(self):
|
| 26 |
+
G = complete_bipartite_graph(0, 0)
|
| 27 |
+
assert nx.is_isomorphic(G, nx.null_graph())
|
| 28 |
+
|
| 29 |
+
for i in [1, 5]:
|
| 30 |
+
G = complete_bipartite_graph(i, 0)
|
| 31 |
+
assert nx.is_isomorphic(G, nx.empty_graph(i))
|
| 32 |
+
G = complete_bipartite_graph(0, i)
|
| 33 |
+
assert nx.is_isomorphic(G, nx.empty_graph(i))
|
| 34 |
+
|
| 35 |
+
G = complete_bipartite_graph(2, 2)
|
| 36 |
+
assert nx.is_isomorphic(G, nx.cycle_graph(4))
|
| 37 |
+
|
| 38 |
+
G = complete_bipartite_graph(1, 5)
|
| 39 |
+
assert nx.is_isomorphic(G, nx.star_graph(5))
|
| 40 |
+
|
| 41 |
+
G = complete_bipartite_graph(5, 1)
|
| 42 |
+
assert nx.is_isomorphic(G, nx.star_graph(5))
|
| 43 |
+
|
| 44 |
+
# complete_bipartite_graph(m1,m2) is a connected graph with
|
| 45 |
+
# m1+m2 nodes and m1*m2 edges
|
| 46 |
+
for m1, m2 in [(5, 11), (7, 3)]:
|
| 47 |
+
G = complete_bipartite_graph(m1, m2)
|
| 48 |
+
assert nx.number_of_nodes(G) == m1 + m2
|
| 49 |
+
assert nx.number_of_edges(G) == m1 * m2
|
| 50 |
+
|
| 51 |
+
with pytest.raises(nx.NetworkXError):
|
| 52 |
+
complete_bipartite_graph(7, 3, create_using=nx.DiGraph)
|
| 53 |
+
with pytest.raises(nx.NetworkXError):
|
| 54 |
+
complete_bipartite_graph(7, 3, create_using=nx.MultiDiGraph)
|
| 55 |
+
|
| 56 |
+
mG = complete_bipartite_graph(7, 3, create_using=nx.MultiGraph)
|
| 57 |
+
assert mG.is_multigraph()
|
| 58 |
+
assert sorted(mG.edges()) == sorted(G.edges())
|
| 59 |
+
|
| 60 |
+
mG = complete_bipartite_graph(7, 3, create_using=nx.MultiGraph)
|
| 61 |
+
assert mG.is_multigraph()
|
| 62 |
+
assert sorted(mG.edges()) == sorted(G.edges())
|
| 63 |
+
|
| 64 |
+
mG = complete_bipartite_graph(7, 3) # default to Graph
|
| 65 |
+
assert sorted(mG.edges()) == sorted(G.edges())
|
| 66 |
+
assert not mG.is_multigraph()
|
| 67 |
+
assert not mG.is_directed()
|
| 68 |
+
|
| 69 |
+
# specify nodes rather than number of nodes
|
| 70 |
+
for n1, n2 in [([1, 2], "ab"), (3, 2), (3, "ab"), ("ab", 3)]:
|
| 71 |
+
G = complete_bipartite_graph(n1, n2)
|
| 72 |
+
if isinstance(n1, numbers.Integral):
|
| 73 |
+
if isinstance(n2, numbers.Integral):
|
| 74 |
+
n2 = range(n1, n1 + n2)
|
| 75 |
+
n1 = range(n1)
|
| 76 |
+
elif isinstance(n2, numbers.Integral):
|
| 77 |
+
n2 = range(n2)
|
| 78 |
+
edges = {(u, v) for u in n1 for v in n2}
|
| 79 |
+
assert edges == set(G.edges)
|
| 80 |
+
assert G.size() == len(edges)
|
| 81 |
+
|
| 82 |
+
# raise when node sets are not distinct
|
| 83 |
+
for n1, n2 in [([1, 2], 3), (3, [1, 2]), ("abc", "bcd")]:
|
| 84 |
+
pytest.raises(nx.NetworkXError, complete_bipartite_graph, n1, n2)
|
| 85 |
+
|
| 86 |
+
def test_configuration_model(self):
|
| 87 |
+
aseq = []
|
| 88 |
+
bseq = []
|
| 89 |
+
G = configuration_model(aseq, bseq)
|
| 90 |
+
assert len(G) == 0
|
| 91 |
+
|
| 92 |
+
aseq = [0, 0]
|
| 93 |
+
bseq = [0, 0]
|
| 94 |
+
G = configuration_model(aseq, bseq)
|
| 95 |
+
assert len(G) == 4
|
| 96 |
+
assert G.number_of_edges() == 0
|
| 97 |
+
|
| 98 |
+
aseq = [3, 3, 3, 3]
|
| 99 |
+
bseq = [2, 2, 2, 2, 2]
|
| 100 |
+
pytest.raises(nx.NetworkXError, configuration_model, aseq, bseq)
|
| 101 |
+
|
| 102 |
+
aseq = [3, 3, 3, 3]
|
| 103 |
+
bseq = [2, 2, 2, 2, 2, 2]
|
| 104 |
+
G = configuration_model(aseq, bseq)
|
| 105 |
+
assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
|
| 106 |
+
|
| 107 |
+
aseq = [2, 2, 2, 2, 2, 2]
|
| 108 |
+
bseq = [3, 3, 3, 3]
|
| 109 |
+
G = configuration_model(aseq, bseq)
|
| 110 |
+
assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
|
| 111 |
+
|
| 112 |
+
aseq = [2, 2, 2, 1, 1, 1]
|
| 113 |
+
bseq = [3, 3, 3]
|
| 114 |
+
G = configuration_model(aseq, bseq)
|
| 115 |
+
assert G.is_multigraph()
|
| 116 |
+
assert not G.is_directed()
|
| 117 |
+
assert sorted(d for n, d in G.degree()) == [1, 1, 1, 2, 2, 2, 3, 3, 3]
|
| 118 |
+
|
| 119 |
+
GU = nx.projected_graph(nx.Graph(G), range(len(aseq)))
|
| 120 |
+
assert GU.number_of_nodes() == 6
|
| 121 |
+
|
| 122 |
+
GD = nx.projected_graph(nx.Graph(G), range(len(aseq), len(aseq) + len(bseq)))
|
| 123 |
+
assert GD.number_of_nodes() == 3
|
| 124 |
+
|
| 125 |
+
G = reverse_havel_hakimi_graph(aseq, bseq, create_using=nx.Graph)
|
| 126 |
+
assert not G.is_multigraph()
|
| 127 |
+
assert not G.is_directed()
|
| 128 |
+
|
| 129 |
+
pytest.raises(
|
| 130 |
+
nx.NetworkXError, configuration_model, aseq, bseq, create_using=nx.DiGraph()
|
| 131 |
+
)
|
| 132 |
+
pytest.raises(
|
| 133 |
+
nx.NetworkXError, configuration_model, aseq, bseq, create_using=nx.DiGraph
|
| 134 |
+
)
|
| 135 |
+
pytest.raises(
|
| 136 |
+
nx.NetworkXError,
|
| 137 |
+
configuration_model,
|
| 138 |
+
aseq,
|
| 139 |
+
bseq,
|
| 140 |
+
create_using=nx.MultiDiGraph,
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
def test_havel_hakimi_graph(self):
|
| 144 |
+
aseq = []
|
| 145 |
+
bseq = []
|
| 146 |
+
G = havel_hakimi_graph(aseq, bseq)
|
| 147 |
+
assert len(G) == 0
|
| 148 |
+
|
| 149 |
+
aseq = [0, 0]
|
| 150 |
+
bseq = [0, 0]
|
| 151 |
+
G = havel_hakimi_graph(aseq, bseq)
|
| 152 |
+
assert len(G) == 4
|
| 153 |
+
assert G.number_of_edges() == 0
|
| 154 |
+
|
| 155 |
+
aseq = [3, 3, 3, 3]
|
| 156 |
+
bseq = [2, 2, 2, 2, 2]
|
| 157 |
+
pytest.raises(nx.NetworkXError, havel_hakimi_graph, aseq, bseq)
|
| 158 |
+
|
| 159 |
+
bseq = [2, 2, 2, 2, 2, 2]
|
| 160 |
+
G = havel_hakimi_graph(aseq, bseq)
|
| 161 |
+
assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
|
| 162 |
+
|
| 163 |
+
aseq = [2, 2, 2, 2, 2, 2]
|
| 164 |
+
bseq = [3, 3, 3, 3]
|
| 165 |
+
G = havel_hakimi_graph(aseq, bseq)
|
| 166 |
+
assert G.is_multigraph()
|
| 167 |
+
assert not G.is_directed()
|
| 168 |
+
assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
|
| 169 |
+
|
| 170 |
+
GU = nx.projected_graph(nx.Graph(G), range(len(aseq)))
|
| 171 |
+
assert GU.number_of_nodes() == 6
|
| 172 |
+
|
| 173 |
+
GD = nx.projected_graph(nx.Graph(G), range(len(aseq), len(aseq) + len(bseq)))
|
| 174 |
+
assert GD.number_of_nodes() == 4
|
| 175 |
+
|
| 176 |
+
G = reverse_havel_hakimi_graph(aseq, bseq, create_using=nx.Graph)
|
| 177 |
+
assert not G.is_multigraph()
|
| 178 |
+
assert not G.is_directed()
|
| 179 |
+
|
| 180 |
+
pytest.raises(
|
| 181 |
+
nx.NetworkXError, havel_hakimi_graph, aseq, bseq, create_using=nx.DiGraph
|
| 182 |
+
)
|
| 183 |
+
pytest.raises(
|
| 184 |
+
nx.NetworkXError, havel_hakimi_graph, aseq, bseq, create_using=nx.DiGraph
|
| 185 |
+
)
|
| 186 |
+
pytest.raises(
|
| 187 |
+
nx.NetworkXError,
|
| 188 |
+
havel_hakimi_graph,
|
| 189 |
+
aseq,
|
| 190 |
+
bseq,
|
| 191 |
+
create_using=nx.MultiDiGraph,
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
def test_reverse_havel_hakimi_graph(self):
|
| 195 |
+
aseq = []
|
| 196 |
+
bseq = []
|
| 197 |
+
G = reverse_havel_hakimi_graph(aseq, bseq)
|
| 198 |
+
assert len(G) == 0
|
| 199 |
+
|
| 200 |
+
aseq = [0, 0]
|
| 201 |
+
bseq = [0, 0]
|
| 202 |
+
G = reverse_havel_hakimi_graph(aseq, bseq)
|
| 203 |
+
assert len(G) == 4
|
| 204 |
+
assert G.number_of_edges() == 0
|
| 205 |
+
|
| 206 |
+
aseq = [3, 3, 3, 3]
|
| 207 |
+
bseq = [2, 2, 2, 2, 2]
|
| 208 |
+
pytest.raises(nx.NetworkXError, reverse_havel_hakimi_graph, aseq, bseq)
|
| 209 |
+
|
| 210 |
+
bseq = [2, 2, 2, 2, 2, 2]
|
| 211 |
+
G = reverse_havel_hakimi_graph(aseq, bseq)
|
| 212 |
+
assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
|
| 213 |
+
|
| 214 |
+
aseq = [2, 2, 2, 2, 2, 2]
|
| 215 |
+
bseq = [3, 3, 3, 3]
|
| 216 |
+
G = reverse_havel_hakimi_graph(aseq, bseq)
|
| 217 |
+
assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
|
| 218 |
+
|
| 219 |
+
aseq = [2, 2, 2, 1, 1, 1]
|
| 220 |
+
bseq = [3, 3, 3]
|
| 221 |
+
G = reverse_havel_hakimi_graph(aseq, bseq)
|
| 222 |
+
assert G.is_multigraph()
|
| 223 |
+
assert not G.is_directed()
|
| 224 |
+
assert sorted(d for n, d in G.degree()) == [1, 1, 1, 2, 2, 2, 3, 3, 3]
|
| 225 |
+
|
| 226 |
+
GU = nx.projected_graph(nx.Graph(G), range(len(aseq)))
|
| 227 |
+
assert GU.number_of_nodes() == 6
|
| 228 |
+
|
| 229 |
+
GD = nx.projected_graph(nx.Graph(G), range(len(aseq), len(aseq) + len(bseq)))
|
| 230 |
+
assert GD.number_of_nodes() == 3
|
| 231 |
+
|
| 232 |
+
G = reverse_havel_hakimi_graph(aseq, bseq, create_using=nx.Graph)
|
| 233 |
+
assert not G.is_multigraph()
|
| 234 |
+
assert not G.is_directed()
|
| 235 |
+
|
| 236 |
+
pytest.raises(
|
| 237 |
+
nx.NetworkXError,
|
| 238 |
+
reverse_havel_hakimi_graph,
|
| 239 |
+
aseq,
|
| 240 |
+
bseq,
|
| 241 |
+
create_using=nx.DiGraph,
|
| 242 |
+
)
|
| 243 |
+
pytest.raises(
|
| 244 |
+
nx.NetworkXError,
|
| 245 |
+
reverse_havel_hakimi_graph,
|
| 246 |
+
aseq,
|
| 247 |
+
bseq,
|
| 248 |
+
create_using=nx.DiGraph,
|
| 249 |
+
)
|
| 250 |
+
pytest.raises(
|
| 251 |
+
nx.NetworkXError,
|
| 252 |
+
reverse_havel_hakimi_graph,
|
| 253 |
+
aseq,
|
| 254 |
+
bseq,
|
| 255 |
+
create_using=nx.MultiDiGraph,
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
def test_alternating_havel_hakimi_graph(self):
|
| 259 |
+
aseq = []
|
| 260 |
+
bseq = []
|
| 261 |
+
G = alternating_havel_hakimi_graph(aseq, bseq)
|
| 262 |
+
assert len(G) == 0
|
| 263 |
+
|
| 264 |
+
aseq = [0, 0]
|
| 265 |
+
bseq = [0, 0]
|
| 266 |
+
G = alternating_havel_hakimi_graph(aseq, bseq)
|
| 267 |
+
assert len(G) == 4
|
| 268 |
+
assert G.number_of_edges() == 0
|
| 269 |
+
|
| 270 |
+
aseq = [3, 3, 3, 3]
|
| 271 |
+
bseq = [2, 2, 2, 2, 2]
|
| 272 |
+
pytest.raises(nx.NetworkXError, alternating_havel_hakimi_graph, aseq, bseq)
|
| 273 |
+
|
| 274 |
+
bseq = [2, 2, 2, 2, 2, 2]
|
| 275 |
+
G = alternating_havel_hakimi_graph(aseq, bseq)
|
| 276 |
+
assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
|
| 277 |
+
|
| 278 |
+
aseq = [2, 2, 2, 2, 2, 2]
|
| 279 |
+
bseq = [3, 3, 3, 3]
|
| 280 |
+
G = alternating_havel_hakimi_graph(aseq, bseq)
|
| 281 |
+
assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
|
| 282 |
+
|
| 283 |
+
aseq = [2, 2, 2, 1, 1, 1]
|
| 284 |
+
bseq = [3, 3, 3]
|
| 285 |
+
G = alternating_havel_hakimi_graph(aseq, bseq)
|
| 286 |
+
assert G.is_multigraph()
|
| 287 |
+
assert not G.is_directed()
|
| 288 |
+
assert sorted(d for n, d in G.degree()) == [1, 1, 1, 2, 2, 2, 3, 3, 3]
|
| 289 |
+
|
| 290 |
+
GU = nx.projected_graph(nx.Graph(G), range(len(aseq)))
|
| 291 |
+
assert GU.number_of_nodes() == 6
|
| 292 |
+
|
| 293 |
+
GD = nx.projected_graph(nx.Graph(G), range(len(aseq), len(aseq) + len(bseq)))
|
| 294 |
+
assert GD.number_of_nodes() == 3
|
| 295 |
+
|
| 296 |
+
G = reverse_havel_hakimi_graph(aseq, bseq, create_using=nx.Graph)
|
| 297 |
+
assert not G.is_multigraph()
|
| 298 |
+
assert not G.is_directed()
|
| 299 |
+
|
| 300 |
+
pytest.raises(
|
| 301 |
+
nx.NetworkXError,
|
| 302 |
+
alternating_havel_hakimi_graph,
|
| 303 |
+
aseq,
|
| 304 |
+
bseq,
|
| 305 |
+
create_using=nx.DiGraph,
|
| 306 |
+
)
|
| 307 |
+
pytest.raises(
|
| 308 |
+
nx.NetworkXError,
|
| 309 |
+
alternating_havel_hakimi_graph,
|
| 310 |
+
aseq,
|
| 311 |
+
bseq,
|
| 312 |
+
create_using=nx.DiGraph,
|
| 313 |
+
)
|
| 314 |
+
pytest.raises(
|
| 315 |
+
nx.NetworkXError,
|
| 316 |
+
alternating_havel_hakimi_graph,
|
| 317 |
+
aseq,
|
| 318 |
+
bseq,
|
| 319 |
+
create_using=nx.MultiDiGraph,
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
def test_preferential_attachment(self):
|
| 323 |
+
aseq = [3, 2, 1, 1]
|
| 324 |
+
G = preferential_attachment_graph(aseq, 0.5)
|
| 325 |
+
assert G.is_multigraph()
|
| 326 |
+
assert not G.is_directed()
|
| 327 |
+
|
| 328 |
+
G = preferential_attachment_graph(aseq, 0.5, create_using=nx.Graph)
|
| 329 |
+
assert not G.is_multigraph()
|
| 330 |
+
assert not G.is_directed()
|
| 331 |
+
|
| 332 |
+
pytest.raises(
|
| 333 |
+
nx.NetworkXError,
|
| 334 |
+
preferential_attachment_graph,
|
| 335 |
+
aseq,
|
| 336 |
+
0.5,
|
| 337 |
+
create_using=nx.DiGraph(),
|
| 338 |
+
)
|
| 339 |
+
pytest.raises(
|
| 340 |
+
nx.NetworkXError,
|
| 341 |
+
preferential_attachment_graph,
|
| 342 |
+
aseq,
|
| 343 |
+
0.5,
|
| 344 |
+
create_using=nx.DiGraph(),
|
| 345 |
+
)
|
| 346 |
+
pytest.raises(
|
| 347 |
+
nx.NetworkXError,
|
| 348 |
+
preferential_attachment_graph,
|
| 349 |
+
aseq,
|
| 350 |
+
0.5,
|
| 351 |
+
create_using=nx.DiGraph(),
|
| 352 |
+
)
|
| 353 |
+
|
| 354 |
+
def test_random_graph(self):
|
| 355 |
+
n = 10
|
| 356 |
+
m = 20
|
| 357 |
+
G = random_graph(n, m, 0.9)
|
| 358 |
+
assert len(G) == 30
|
| 359 |
+
assert nx.is_bipartite(G)
|
| 360 |
+
X, Y = nx.algorithms.bipartite.sets(G)
|
| 361 |
+
assert set(range(n)) == X
|
| 362 |
+
assert set(range(n, n + m)) == Y
|
| 363 |
+
|
| 364 |
+
def test_random_digraph(self):
|
| 365 |
+
n = 10
|
| 366 |
+
m = 20
|
| 367 |
+
G = random_graph(n, m, 0.9, directed=True)
|
| 368 |
+
assert len(G) == 30
|
| 369 |
+
assert nx.is_bipartite(G)
|
| 370 |
+
X, Y = nx.algorithms.bipartite.sets(G)
|
| 371 |
+
assert set(range(n)) == X
|
| 372 |
+
assert set(range(n, n + m)) == Y
|
| 373 |
+
|
| 374 |
+
def test_gnmk_random_graph(self):
|
| 375 |
+
n = 10
|
| 376 |
+
m = 20
|
| 377 |
+
edges = 100
|
| 378 |
+
# set seed because sometimes it is not connected
|
| 379 |
+
# which raises an error in bipartite.sets(G) below.
|
| 380 |
+
G = gnmk_random_graph(n, m, edges, seed=1234)
|
| 381 |
+
assert len(G) == n + m
|
| 382 |
+
assert nx.is_bipartite(G)
|
| 383 |
+
X, Y = nx.algorithms.bipartite.sets(G)
|
| 384 |
+
# print(X)
|
| 385 |
+
assert set(range(n)) == X
|
| 386 |
+
assert set(range(n, n + m)) == Y
|
| 387 |
+
assert edges == len(list(G.edges()))
|
| 388 |
+
|
| 389 |
+
def test_gnmk_random_graph_complete(self):
|
| 390 |
+
n = 10
|
| 391 |
+
m = 20
|
| 392 |
+
edges = 200
|
| 393 |
+
G = gnmk_random_graph(n, m, edges)
|
| 394 |
+
assert len(G) == n + m
|
| 395 |
+
assert nx.is_bipartite(G)
|
| 396 |
+
X, Y = nx.algorithms.bipartite.sets(G)
|
| 397 |
+
# print(X)
|
| 398 |
+
assert set(range(n)) == X
|
| 399 |
+
assert set(range(n, n + m)) == Y
|
| 400 |
+
assert edges == len(list(G.edges()))
|
| 401 |
+
|
| 402 |
+
@pytest.mark.parametrize("n", (4, range(4), {0, 1, 2, 3}))
|
| 403 |
+
@pytest.mark.parametrize("m", (range(4, 7), {4, 5, 6}))
|
| 404 |
+
def test_complete_bipartite_graph_str(self, n, m):
|
| 405 |
+
"""Ensure G.name is consistent for all inputs accepted by nodes_or_number.
|
| 406 |
+
See gh-7396"""
|
| 407 |
+
G = nx.complete_bipartite_graph(n, m)
|
| 408 |
+
ans = "Graph named 'complete_bipartite_graph(4, 3)' with 7 nodes and 12 edges"
|
| 409 |
+
assert str(G) == ans
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_matching.py
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit tests for the :mod:`networkx.algorithms.bipartite.matching` module."""
|
| 2 |
+
|
| 3 |
+
import itertools
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
|
| 7 |
+
import networkx as nx
|
| 8 |
+
from networkx.algorithms.bipartite.matching import (
|
| 9 |
+
eppstein_matching,
|
| 10 |
+
hopcroft_karp_matching,
|
| 11 |
+
maximum_matching,
|
| 12 |
+
minimum_weight_full_matching,
|
| 13 |
+
to_vertex_cover,
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class TestMatching:
|
| 18 |
+
"""Tests for bipartite matching algorithms."""
|
| 19 |
+
|
| 20 |
+
def setup_method(self):
|
| 21 |
+
"""Creates a bipartite graph for use in testing matching algorithms.
|
| 22 |
+
|
| 23 |
+
The bipartite graph has a maximum cardinality matching that leaves
|
| 24 |
+
vertex 1 and vertex 10 unmatched. The first six numbers are the left
|
| 25 |
+
vertices and the next six numbers are the right vertices.
|
| 26 |
+
|
| 27 |
+
"""
|
| 28 |
+
self.simple_graph = nx.complete_bipartite_graph(2, 3)
|
| 29 |
+
self.simple_solution = {0: 2, 1: 3, 2: 0, 3: 1}
|
| 30 |
+
|
| 31 |
+
edges = [(0, 7), (0, 8), (2, 6), (2, 9), (3, 8), (4, 8), (4, 9), (5, 11)]
|
| 32 |
+
self.top_nodes = set(range(6))
|
| 33 |
+
self.graph = nx.Graph()
|
| 34 |
+
self.graph.add_nodes_from(range(12))
|
| 35 |
+
self.graph.add_edges_from(edges)
|
| 36 |
+
|
| 37 |
+
# Example bipartite graph from issue 2127
|
| 38 |
+
G = nx.Graph()
|
| 39 |
+
G.add_nodes_from(
|
| 40 |
+
[
|
| 41 |
+
(1, "C"),
|
| 42 |
+
(1, "B"),
|
| 43 |
+
(0, "G"),
|
| 44 |
+
(1, "F"),
|
| 45 |
+
(1, "E"),
|
| 46 |
+
(0, "C"),
|
| 47 |
+
(1, "D"),
|
| 48 |
+
(1, "I"),
|
| 49 |
+
(0, "A"),
|
| 50 |
+
(0, "D"),
|
| 51 |
+
(0, "F"),
|
| 52 |
+
(0, "E"),
|
| 53 |
+
(0, "H"),
|
| 54 |
+
(1, "G"),
|
| 55 |
+
(1, "A"),
|
| 56 |
+
(0, "I"),
|
| 57 |
+
(0, "B"),
|
| 58 |
+
(1, "H"),
|
| 59 |
+
]
|
| 60 |
+
)
|
| 61 |
+
G.add_edge((1, "C"), (0, "A"))
|
| 62 |
+
G.add_edge((1, "B"), (0, "A"))
|
| 63 |
+
G.add_edge((0, "G"), (1, "I"))
|
| 64 |
+
G.add_edge((0, "G"), (1, "H"))
|
| 65 |
+
G.add_edge((1, "F"), (0, "A"))
|
| 66 |
+
G.add_edge((1, "F"), (0, "C"))
|
| 67 |
+
G.add_edge((1, "F"), (0, "E"))
|
| 68 |
+
G.add_edge((1, "E"), (0, "A"))
|
| 69 |
+
G.add_edge((1, "E"), (0, "C"))
|
| 70 |
+
G.add_edge((0, "C"), (1, "D"))
|
| 71 |
+
G.add_edge((0, "C"), (1, "I"))
|
| 72 |
+
G.add_edge((0, "C"), (1, "G"))
|
| 73 |
+
G.add_edge((0, "C"), (1, "H"))
|
| 74 |
+
G.add_edge((1, "D"), (0, "A"))
|
| 75 |
+
G.add_edge((1, "I"), (0, "A"))
|
| 76 |
+
G.add_edge((1, "I"), (0, "E"))
|
| 77 |
+
G.add_edge((0, "A"), (1, "G"))
|
| 78 |
+
G.add_edge((0, "A"), (1, "H"))
|
| 79 |
+
G.add_edge((0, "E"), (1, "G"))
|
| 80 |
+
G.add_edge((0, "E"), (1, "H"))
|
| 81 |
+
self.disconnected_graph = G
|
| 82 |
+
|
| 83 |
+
def check_match(self, matching):
|
| 84 |
+
"""Asserts that the matching is what we expect from the bipartite graph
|
| 85 |
+
constructed in the :meth:`setup` fixture.
|
| 86 |
+
|
| 87 |
+
"""
|
| 88 |
+
# For the sake of brevity, rename `matching` to `M`.
|
| 89 |
+
M = matching
|
| 90 |
+
matched_vertices = frozenset(itertools.chain(*M.items()))
|
| 91 |
+
# Assert that the maximum number of vertices (10) is matched.
|
| 92 |
+
assert matched_vertices == frozenset(range(12)) - {1, 10}
|
| 93 |
+
# Assert that no vertex appears in two edges, or in other words, that
|
| 94 |
+
# the matching (u, v) and (v, u) both appear in the matching
|
| 95 |
+
# dictionary.
|
| 96 |
+
assert all(u == M[M[u]] for u in range(12) if u in M)
|
| 97 |
+
|
| 98 |
+
def check_vertex_cover(self, vertices):
|
| 99 |
+
"""Asserts that the given set of vertices is the vertex cover we
|
| 100 |
+
expected from the bipartite graph constructed in the :meth:`setup`
|
| 101 |
+
fixture.
|
| 102 |
+
|
| 103 |
+
"""
|
| 104 |
+
# By Konig's theorem, the number of edges in a maximum matching equals
|
| 105 |
+
# the number of vertices in a minimum vertex cover.
|
| 106 |
+
assert len(vertices) == 5
|
| 107 |
+
# Assert that the set is truly a vertex cover.
|
| 108 |
+
for u, v in self.graph.edges():
|
| 109 |
+
assert u in vertices or v in vertices
|
| 110 |
+
# TODO Assert that the vertices are the correct ones.
|
| 111 |
+
|
| 112 |
+
def test_eppstein_matching(self):
|
| 113 |
+
"""Tests that David Eppstein's implementation of the Hopcroft--Karp
|
| 114 |
+
algorithm produces a maximum cardinality matching.
|
| 115 |
+
|
| 116 |
+
"""
|
| 117 |
+
self.check_match(eppstein_matching(self.graph, self.top_nodes))
|
| 118 |
+
|
| 119 |
+
def test_hopcroft_karp_matching(self):
|
| 120 |
+
"""Tests that the Hopcroft--Karp algorithm produces a maximum
|
| 121 |
+
cardinality matching in a bipartite graph.
|
| 122 |
+
|
| 123 |
+
"""
|
| 124 |
+
self.check_match(hopcroft_karp_matching(self.graph, self.top_nodes))
|
| 125 |
+
|
| 126 |
+
def test_to_vertex_cover(self):
|
| 127 |
+
"""Test for converting a maximum matching to a minimum vertex cover."""
|
| 128 |
+
matching = maximum_matching(self.graph, self.top_nodes)
|
| 129 |
+
vertex_cover = to_vertex_cover(self.graph, matching, self.top_nodes)
|
| 130 |
+
self.check_vertex_cover(vertex_cover)
|
| 131 |
+
|
| 132 |
+
def test_eppstein_matching_simple(self):
|
| 133 |
+
match = eppstein_matching(self.simple_graph)
|
| 134 |
+
assert match == self.simple_solution
|
| 135 |
+
|
| 136 |
+
def test_hopcroft_karp_matching_simple(self):
|
| 137 |
+
match = hopcroft_karp_matching(self.simple_graph)
|
| 138 |
+
assert match == self.simple_solution
|
| 139 |
+
|
| 140 |
+
def test_eppstein_matching_disconnected(self):
|
| 141 |
+
with pytest.raises(nx.AmbiguousSolution):
|
| 142 |
+
match = eppstein_matching(self.disconnected_graph)
|
| 143 |
+
|
| 144 |
+
def test_hopcroft_karp_matching_disconnected(self):
|
| 145 |
+
with pytest.raises(nx.AmbiguousSolution):
|
| 146 |
+
match = hopcroft_karp_matching(self.disconnected_graph)
|
| 147 |
+
|
| 148 |
+
def test_issue_2127(self):
|
| 149 |
+
"""Test from issue 2127"""
|
| 150 |
+
# Build the example DAG
|
| 151 |
+
G = nx.DiGraph()
|
| 152 |
+
G.add_edge("A", "C")
|
| 153 |
+
G.add_edge("A", "B")
|
| 154 |
+
G.add_edge("C", "E")
|
| 155 |
+
G.add_edge("C", "D")
|
| 156 |
+
G.add_edge("E", "G")
|
| 157 |
+
G.add_edge("E", "F")
|
| 158 |
+
G.add_edge("G", "I")
|
| 159 |
+
G.add_edge("G", "H")
|
| 160 |
+
|
| 161 |
+
tc = nx.transitive_closure(G)
|
| 162 |
+
btc = nx.Graph()
|
| 163 |
+
|
| 164 |
+
# Create a bipartite graph based on the transitive closure of G
|
| 165 |
+
for v in tc.nodes():
|
| 166 |
+
btc.add_node((0, v))
|
| 167 |
+
btc.add_node((1, v))
|
| 168 |
+
|
| 169 |
+
for u, v in tc.edges():
|
| 170 |
+
btc.add_edge((0, u), (1, v))
|
| 171 |
+
|
| 172 |
+
top_nodes = {n for n in btc if n[0] == 0}
|
| 173 |
+
matching = hopcroft_karp_matching(btc, top_nodes)
|
| 174 |
+
vertex_cover = to_vertex_cover(btc, matching, top_nodes)
|
| 175 |
+
independent_set = set(G) - {v for _, v in vertex_cover}
|
| 176 |
+
assert {"B", "D", "F", "I", "H"} == independent_set
|
| 177 |
+
|
| 178 |
+
def test_vertex_cover_issue_2384(self):
|
| 179 |
+
G = nx.Graph([(0, 3), (1, 3), (1, 4), (2, 3)])
|
| 180 |
+
matching = maximum_matching(G)
|
| 181 |
+
vertex_cover = to_vertex_cover(G, matching)
|
| 182 |
+
for u, v in G.edges():
|
| 183 |
+
assert u in vertex_cover or v in vertex_cover
|
| 184 |
+
|
| 185 |
+
def test_vertex_cover_issue_3306(self):
|
| 186 |
+
G = nx.Graph()
|
| 187 |
+
edges = [(0, 2), (1, 0), (1, 1), (1, 2), (2, 2)]
|
| 188 |
+
G.add_edges_from([((i, "L"), (j, "R")) for i, j in edges])
|
| 189 |
+
|
| 190 |
+
matching = maximum_matching(G)
|
| 191 |
+
vertex_cover = to_vertex_cover(G, matching)
|
| 192 |
+
for u, v in G.edges():
|
| 193 |
+
assert u in vertex_cover or v in vertex_cover
|
| 194 |
+
|
| 195 |
+
def test_unorderable_nodes(self):
|
| 196 |
+
a = object()
|
| 197 |
+
b = object()
|
| 198 |
+
c = object()
|
| 199 |
+
d = object()
|
| 200 |
+
e = object()
|
| 201 |
+
G = nx.Graph([(a, d), (b, d), (b, e), (c, d)])
|
| 202 |
+
matching = maximum_matching(G)
|
| 203 |
+
vertex_cover = to_vertex_cover(G, matching)
|
| 204 |
+
for u, v in G.edges():
|
| 205 |
+
assert u in vertex_cover or v in vertex_cover
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def test_eppstein_matching():
|
| 209 |
+
"""Test in accordance to issue #1927"""
|
| 210 |
+
G = nx.Graph()
|
| 211 |
+
G.add_nodes_from(["a", 2, 3, 4], bipartite=0)
|
| 212 |
+
G.add_nodes_from([1, "b", "c"], bipartite=1)
|
| 213 |
+
G.add_edges_from([("a", 1), ("a", "b"), (2, "b"), (2, "c"), (3, "c"), (4, 1)])
|
| 214 |
+
matching = eppstein_matching(G)
|
| 215 |
+
assert len(matching) == len(maximum_matching(G))
|
| 216 |
+
assert all(x in set(matching.keys()) for x in set(matching.values()))
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
class TestMinimumWeightFullMatching:
|
| 220 |
+
@classmethod
|
| 221 |
+
def setup_class(cls):
|
| 222 |
+
pytest.importorskip("scipy")
|
| 223 |
+
|
| 224 |
+
def test_minimum_weight_full_matching_incomplete_graph(self):
|
| 225 |
+
B = nx.Graph()
|
| 226 |
+
B.add_nodes_from([1, 2], bipartite=0)
|
| 227 |
+
B.add_nodes_from([3, 4], bipartite=1)
|
| 228 |
+
B.add_edge(1, 4, weight=100)
|
| 229 |
+
B.add_edge(2, 3, weight=100)
|
| 230 |
+
B.add_edge(2, 4, weight=50)
|
| 231 |
+
matching = minimum_weight_full_matching(B)
|
| 232 |
+
assert matching == {1: 4, 2: 3, 4: 1, 3: 2}
|
| 233 |
+
|
| 234 |
+
def test_minimum_weight_full_matching_with_no_full_matching(self):
|
| 235 |
+
B = nx.Graph()
|
| 236 |
+
B.add_nodes_from([1, 2, 3], bipartite=0)
|
| 237 |
+
B.add_nodes_from([4, 5, 6], bipartite=1)
|
| 238 |
+
B.add_edge(1, 4, weight=100)
|
| 239 |
+
B.add_edge(2, 4, weight=100)
|
| 240 |
+
B.add_edge(3, 4, weight=50)
|
| 241 |
+
B.add_edge(3, 5, weight=50)
|
| 242 |
+
B.add_edge(3, 6, weight=50)
|
| 243 |
+
with pytest.raises(ValueError):
|
| 244 |
+
minimum_weight_full_matching(B)
|
| 245 |
+
|
| 246 |
+
def test_minimum_weight_full_matching_square(self):
|
| 247 |
+
G = nx.complete_bipartite_graph(3, 3)
|
| 248 |
+
G.add_edge(0, 3, weight=400)
|
| 249 |
+
G.add_edge(0, 4, weight=150)
|
| 250 |
+
G.add_edge(0, 5, weight=400)
|
| 251 |
+
G.add_edge(1, 3, weight=400)
|
| 252 |
+
G.add_edge(1, 4, weight=450)
|
| 253 |
+
G.add_edge(1, 5, weight=600)
|
| 254 |
+
G.add_edge(2, 3, weight=300)
|
| 255 |
+
G.add_edge(2, 4, weight=225)
|
| 256 |
+
G.add_edge(2, 5, weight=300)
|
| 257 |
+
matching = minimum_weight_full_matching(G)
|
| 258 |
+
assert matching == {0: 4, 1: 3, 2: 5, 4: 0, 3: 1, 5: 2}
|
| 259 |
+
|
| 260 |
+
def test_minimum_weight_full_matching_smaller_left(self):
|
| 261 |
+
G = nx.complete_bipartite_graph(3, 4)
|
| 262 |
+
G.add_edge(0, 3, weight=400)
|
| 263 |
+
G.add_edge(0, 4, weight=150)
|
| 264 |
+
G.add_edge(0, 5, weight=400)
|
| 265 |
+
G.add_edge(0, 6, weight=1)
|
| 266 |
+
G.add_edge(1, 3, weight=400)
|
| 267 |
+
G.add_edge(1, 4, weight=450)
|
| 268 |
+
G.add_edge(1, 5, weight=600)
|
| 269 |
+
G.add_edge(1, 6, weight=2)
|
| 270 |
+
G.add_edge(2, 3, weight=300)
|
| 271 |
+
G.add_edge(2, 4, weight=225)
|
| 272 |
+
G.add_edge(2, 5, weight=290)
|
| 273 |
+
G.add_edge(2, 6, weight=3)
|
| 274 |
+
matching = minimum_weight_full_matching(G)
|
| 275 |
+
assert matching == {0: 4, 1: 6, 2: 5, 4: 0, 5: 2, 6: 1}
|
| 276 |
+
|
| 277 |
+
def test_minimum_weight_full_matching_smaller_top_nodes_right(self):
|
| 278 |
+
G = nx.complete_bipartite_graph(3, 4)
|
| 279 |
+
G.add_edge(0, 3, weight=400)
|
| 280 |
+
G.add_edge(0, 4, weight=150)
|
| 281 |
+
G.add_edge(0, 5, weight=400)
|
| 282 |
+
G.add_edge(0, 6, weight=1)
|
| 283 |
+
G.add_edge(1, 3, weight=400)
|
| 284 |
+
G.add_edge(1, 4, weight=450)
|
| 285 |
+
G.add_edge(1, 5, weight=600)
|
| 286 |
+
G.add_edge(1, 6, weight=2)
|
| 287 |
+
G.add_edge(2, 3, weight=300)
|
| 288 |
+
G.add_edge(2, 4, weight=225)
|
| 289 |
+
G.add_edge(2, 5, weight=290)
|
| 290 |
+
G.add_edge(2, 6, weight=3)
|
| 291 |
+
matching = minimum_weight_full_matching(G, top_nodes=[3, 4, 5, 6])
|
| 292 |
+
assert matching == {0: 4, 1: 6, 2: 5, 4: 0, 5: 2, 6: 1}
|
| 293 |
+
|
| 294 |
+
def test_minimum_weight_full_matching_smaller_right(self):
|
| 295 |
+
G = nx.complete_bipartite_graph(4, 3)
|
| 296 |
+
G.add_edge(0, 4, weight=400)
|
| 297 |
+
G.add_edge(0, 5, weight=400)
|
| 298 |
+
G.add_edge(0, 6, weight=300)
|
| 299 |
+
G.add_edge(1, 4, weight=150)
|
| 300 |
+
G.add_edge(1, 5, weight=450)
|
| 301 |
+
G.add_edge(1, 6, weight=225)
|
| 302 |
+
G.add_edge(2, 4, weight=400)
|
| 303 |
+
G.add_edge(2, 5, weight=600)
|
| 304 |
+
G.add_edge(2, 6, weight=290)
|
| 305 |
+
G.add_edge(3, 4, weight=1)
|
| 306 |
+
G.add_edge(3, 5, weight=2)
|
| 307 |
+
G.add_edge(3, 6, weight=3)
|
| 308 |
+
matching = minimum_weight_full_matching(G)
|
| 309 |
+
assert matching == {1: 4, 2: 6, 3: 5, 4: 1, 5: 3, 6: 2}
|
| 310 |
+
|
| 311 |
+
def test_minimum_weight_full_matching_negative_weights(self):
|
| 312 |
+
G = nx.complete_bipartite_graph(2, 2)
|
| 313 |
+
G.add_edge(0, 2, weight=-2)
|
| 314 |
+
G.add_edge(0, 3, weight=0.2)
|
| 315 |
+
G.add_edge(1, 2, weight=-2)
|
| 316 |
+
G.add_edge(1, 3, weight=0.3)
|
| 317 |
+
matching = minimum_weight_full_matching(G)
|
| 318 |
+
assert matching == {0: 3, 1: 2, 2: 1, 3: 0}
|
| 319 |
+
|
| 320 |
+
def test_minimum_weight_full_matching_different_weight_key(self):
|
| 321 |
+
G = nx.complete_bipartite_graph(2, 2)
|
| 322 |
+
G.add_edge(0, 2, mass=2)
|
| 323 |
+
G.add_edge(0, 3, mass=0.2)
|
| 324 |
+
G.add_edge(1, 2, mass=1)
|
| 325 |
+
G.add_edge(1, 3, mass=2)
|
| 326 |
+
matching = minimum_weight_full_matching(G, weight="mass")
|
| 327 |
+
assert matching == {0: 3, 1: 2, 2: 1, 3: 0}
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_matrix.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
np = pytest.importorskip("numpy")
|
| 4 |
+
sp = pytest.importorskip("scipy")
|
| 5 |
+
sparse = pytest.importorskip("scipy.sparse")
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
import networkx as nx
|
| 9 |
+
from networkx.algorithms import bipartite
|
| 10 |
+
from networkx.utils import edges_equal
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class TestBiadjacencyMatrix:
|
| 14 |
+
def test_biadjacency_matrix_weight(self):
|
| 15 |
+
G = nx.path_graph(5)
|
| 16 |
+
G.add_edge(0, 1, weight=2, other=4)
|
| 17 |
+
X = [1, 3]
|
| 18 |
+
Y = [0, 2, 4]
|
| 19 |
+
M = bipartite.biadjacency_matrix(G, X, weight="weight")
|
| 20 |
+
assert M[0, 0] == 2
|
| 21 |
+
M = bipartite.biadjacency_matrix(G, X, weight="other")
|
| 22 |
+
assert M[0, 0] == 4
|
| 23 |
+
|
| 24 |
+
def test_biadjacency_matrix(self):
|
| 25 |
+
tops = [2, 5, 10]
|
| 26 |
+
bots = [5, 10, 15]
|
| 27 |
+
for i in range(len(tops)):
|
| 28 |
+
G = bipartite.random_graph(tops[i], bots[i], 0.2)
|
| 29 |
+
top = [n for n, d in G.nodes(data=True) if d["bipartite"] == 0]
|
| 30 |
+
M = bipartite.biadjacency_matrix(G, top)
|
| 31 |
+
assert M.shape[0] == tops[i]
|
| 32 |
+
assert M.shape[1] == bots[i]
|
| 33 |
+
|
| 34 |
+
def test_biadjacency_matrix_order(self):
|
| 35 |
+
G = nx.path_graph(5)
|
| 36 |
+
G.add_edge(0, 1, weight=2)
|
| 37 |
+
X = [3, 1]
|
| 38 |
+
Y = [4, 2, 0]
|
| 39 |
+
M = bipartite.biadjacency_matrix(G, X, Y, weight="weight")
|
| 40 |
+
assert M[1, 2] == 2
|
| 41 |
+
|
| 42 |
+
def test_biadjacency_matrix_empty_graph(self):
|
| 43 |
+
G = nx.empty_graph(2)
|
| 44 |
+
M = nx.bipartite.biadjacency_matrix(G, [0])
|
| 45 |
+
assert np.array_equal(M.toarray(), np.array([[0]]))
|
| 46 |
+
|
| 47 |
+
def test_null_graph(self):
|
| 48 |
+
with pytest.raises(nx.NetworkXError):
|
| 49 |
+
bipartite.biadjacency_matrix(nx.Graph(), [])
|
| 50 |
+
|
| 51 |
+
def test_empty_graph(self):
|
| 52 |
+
with pytest.raises(nx.NetworkXError):
|
| 53 |
+
bipartite.biadjacency_matrix(nx.Graph([(1, 0)]), [])
|
| 54 |
+
|
| 55 |
+
def test_duplicate_row(self):
|
| 56 |
+
with pytest.raises(nx.NetworkXError):
|
| 57 |
+
bipartite.biadjacency_matrix(nx.Graph([(1, 0)]), [1, 1])
|
| 58 |
+
|
| 59 |
+
def test_duplicate_col(self):
|
| 60 |
+
with pytest.raises(nx.NetworkXError):
|
| 61 |
+
bipartite.biadjacency_matrix(nx.Graph([(1, 0)]), [0], [1, 1])
|
| 62 |
+
|
| 63 |
+
def test_format_keyword(self):
|
| 64 |
+
with pytest.raises(nx.NetworkXError):
|
| 65 |
+
bipartite.biadjacency_matrix(nx.Graph([(1, 0)]), [0], format="foo")
|
| 66 |
+
|
| 67 |
+
def test_from_biadjacency_roundtrip(self):
|
| 68 |
+
B1 = nx.path_graph(5)
|
| 69 |
+
M = bipartite.biadjacency_matrix(B1, [0, 2, 4])
|
| 70 |
+
B2 = bipartite.from_biadjacency_matrix(M)
|
| 71 |
+
assert nx.is_isomorphic(B1, B2)
|
| 72 |
+
|
| 73 |
+
def test_from_biadjacency_weight(self):
|
| 74 |
+
M = sparse.csc_matrix([[1, 2], [0, 3]])
|
| 75 |
+
B = bipartite.from_biadjacency_matrix(M)
|
| 76 |
+
assert edges_equal(B.edges(), [(0, 2), (0, 3), (1, 3)])
|
| 77 |
+
B = bipartite.from_biadjacency_matrix(M, edge_attribute="weight")
|
| 78 |
+
e = [(0, 2, {"weight": 1}), (0, 3, {"weight": 2}), (1, 3, {"weight": 3})]
|
| 79 |
+
assert edges_equal(B.edges(data=True), e)
|
| 80 |
+
|
| 81 |
+
def test_from_biadjacency_multigraph(self):
|
| 82 |
+
M = sparse.csc_matrix([[1, 2], [0, 3]])
|
| 83 |
+
B = bipartite.from_biadjacency_matrix(M, create_using=nx.MultiGraph())
|
| 84 |
+
assert edges_equal(B.edges(), [(0, 2), (0, 3), (0, 3), (1, 3), (1, 3), (1, 3)])
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_project.py
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
import networkx as nx
|
| 4 |
+
from networkx.algorithms import bipartite
|
| 5 |
+
from networkx.utils import edges_equal, nodes_equal
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TestBipartiteProject:
|
| 9 |
+
def test_path_projected_graph(self):
|
| 10 |
+
G = nx.path_graph(4)
|
| 11 |
+
P = bipartite.projected_graph(G, [1, 3])
|
| 12 |
+
assert nodes_equal(list(P), [1, 3])
|
| 13 |
+
assert edges_equal(list(P.edges()), [(1, 3)])
|
| 14 |
+
P = bipartite.projected_graph(G, [0, 2])
|
| 15 |
+
assert nodes_equal(list(P), [0, 2])
|
| 16 |
+
assert edges_equal(list(P.edges()), [(0, 2)])
|
| 17 |
+
G = nx.MultiGraph([(0, 1)])
|
| 18 |
+
with pytest.raises(nx.NetworkXError, match="not defined for multigraphs"):
|
| 19 |
+
bipartite.projected_graph(G, [0])
|
| 20 |
+
|
| 21 |
+
def test_path_projected_properties_graph(self):
|
| 22 |
+
G = nx.path_graph(4)
|
| 23 |
+
G.add_node(1, name="one")
|
| 24 |
+
G.add_node(2, name="two")
|
| 25 |
+
P = bipartite.projected_graph(G, [1, 3])
|
| 26 |
+
assert nodes_equal(list(P), [1, 3])
|
| 27 |
+
assert edges_equal(list(P.edges()), [(1, 3)])
|
| 28 |
+
assert P.nodes[1]["name"] == G.nodes[1]["name"]
|
| 29 |
+
P = bipartite.projected_graph(G, [0, 2])
|
| 30 |
+
assert nodes_equal(list(P), [0, 2])
|
| 31 |
+
assert edges_equal(list(P.edges()), [(0, 2)])
|
| 32 |
+
assert P.nodes[2]["name"] == G.nodes[2]["name"]
|
| 33 |
+
|
| 34 |
+
def test_path_collaboration_projected_graph(self):
|
| 35 |
+
G = nx.path_graph(4)
|
| 36 |
+
P = bipartite.collaboration_weighted_projected_graph(G, [1, 3])
|
| 37 |
+
assert nodes_equal(list(P), [1, 3])
|
| 38 |
+
assert edges_equal(list(P.edges()), [(1, 3)])
|
| 39 |
+
P[1][3]["weight"] = 1
|
| 40 |
+
P = bipartite.collaboration_weighted_projected_graph(G, [0, 2])
|
| 41 |
+
assert nodes_equal(list(P), [0, 2])
|
| 42 |
+
assert edges_equal(list(P.edges()), [(0, 2)])
|
| 43 |
+
P[0][2]["weight"] = 1
|
| 44 |
+
|
| 45 |
+
def test_directed_path_collaboration_projected_graph(self):
|
| 46 |
+
G = nx.DiGraph()
|
| 47 |
+
nx.add_path(G, range(4))
|
| 48 |
+
P = bipartite.collaboration_weighted_projected_graph(G, [1, 3])
|
| 49 |
+
assert nodes_equal(list(P), [1, 3])
|
| 50 |
+
assert edges_equal(list(P.edges()), [(1, 3)])
|
| 51 |
+
P[1][3]["weight"] = 1
|
| 52 |
+
P = bipartite.collaboration_weighted_projected_graph(G, [0, 2])
|
| 53 |
+
assert nodes_equal(list(P), [0, 2])
|
| 54 |
+
assert edges_equal(list(P.edges()), [(0, 2)])
|
| 55 |
+
P[0][2]["weight"] = 1
|
| 56 |
+
|
| 57 |
+
def test_path_weighted_projected_graph(self):
|
| 58 |
+
G = nx.path_graph(4)
|
| 59 |
+
|
| 60 |
+
with pytest.raises(nx.NetworkXAlgorithmError):
|
| 61 |
+
bipartite.weighted_projected_graph(G, [1, 2, 3, 3])
|
| 62 |
+
|
| 63 |
+
P = bipartite.weighted_projected_graph(G, [1, 3])
|
| 64 |
+
assert nodes_equal(list(P), [1, 3])
|
| 65 |
+
assert edges_equal(list(P.edges()), [(1, 3)])
|
| 66 |
+
P[1][3]["weight"] = 1
|
| 67 |
+
P = bipartite.weighted_projected_graph(G, [0, 2])
|
| 68 |
+
assert nodes_equal(list(P), [0, 2])
|
| 69 |
+
assert edges_equal(list(P.edges()), [(0, 2)])
|
| 70 |
+
P[0][2]["weight"] = 1
|
| 71 |
+
|
| 72 |
+
def test_digraph_weighted_projection(self):
|
| 73 |
+
G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 4)])
|
| 74 |
+
P = bipartite.overlap_weighted_projected_graph(G, [1, 3])
|
| 75 |
+
assert nx.get_edge_attributes(P, "weight") == {(1, 3): 1.0}
|
| 76 |
+
assert len(P) == 2
|
| 77 |
+
|
| 78 |
+
def test_path_weighted_projected_directed_graph(self):
|
| 79 |
+
G = nx.DiGraph()
|
| 80 |
+
nx.add_path(G, range(4))
|
| 81 |
+
P = bipartite.weighted_projected_graph(G, [1, 3])
|
| 82 |
+
assert nodes_equal(list(P), [1, 3])
|
| 83 |
+
assert edges_equal(list(P.edges()), [(1, 3)])
|
| 84 |
+
P[1][3]["weight"] = 1
|
| 85 |
+
P = bipartite.weighted_projected_graph(G, [0, 2])
|
| 86 |
+
assert nodes_equal(list(P), [0, 2])
|
| 87 |
+
assert edges_equal(list(P.edges()), [(0, 2)])
|
| 88 |
+
P[0][2]["weight"] = 1
|
| 89 |
+
|
| 90 |
+
def test_star_projected_graph(self):
|
| 91 |
+
G = nx.star_graph(3)
|
| 92 |
+
P = bipartite.projected_graph(G, [1, 2, 3])
|
| 93 |
+
assert nodes_equal(list(P), [1, 2, 3])
|
| 94 |
+
assert edges_equal(list(P.edges()), [(1, 2), (1, 3), (2, 3)])
|
| 95 |
+
P = bipartite.weighted_projected_graph(G, [1, 2, 3])
|
| 96 |
+
assert nodes_equal(list(P), [1, 2, 3])
|
| 97 |
+
assert edges_equal(list(P.edges()), [(1, 2), (1, 3), (2, 3)])
|
| 98 |
+
|
| 99 |
+
P = bipartite.projected_graph(G, [0])
|
| 100 |
+
assert nodes_equal(list(P), [0])
|
| 101 |
+
assert edges_equal(list(P.edges()), [])
|
| 102 |
+
|
| 103 |
+
def test_project_multigraph(self):
|
| 104 |
+
G = nx.Graph()
|
| 105 |
+
G.add_edge("a", 1)
|
| 106 |
+
G.add_edge("b", 1)
|
| 107 |
+
G.add_edge("a", 2)
|
| 108 |
+
G.add_edge("b", 2)
|
| 109 |
+
P = bipartite.projected_graph(G, "ab")
|
| 110 |
+
assert edges_equal(list(P.edges()), [("a", "b")])
|
| 111 |
+
P = bipartite.weighted_projected_graph(G, "ab")
|
| 112 |
+
assert edges_equal(list(P.edges()), [("a", "b")])
|
| 113 |
+
P = bipartite.projected_graph(G, "ab", multigraph=True)
|
| 114 |
+
assert edges_equal(list(P.edges()), [("a", "b"), ("a", "b")])
|
| 115 |
+
|
| 116 |
+
def test_project_collaboration(self):
|
| 117 |
+
G = nx.Graph()
|
| 118 |
+
G.add_edge("a", 1)
|
| 119 |
+
G.add_edge("b", 1)
|
| 120 |
+
G.add_edge("b", 2)
|
| 121 |
+
G.add_edge("c", 2)
|
| 122 |
+
G.add_edge("c", 3)
|
| 123 |
+
G.add_edge("c", 4)
|
| 124 |
+
G.add_edge("b", 4)
|
| 125 |
+
P = bipartite.collaboration_weighted_projected_graph(G, "abc")
|
| 126 |
+
assert P["a"]["b"]["weight"] == 1
|
| 127 |
+
assert P["b"]["c"]["weight"] == 2
|
| 128 |
+
|
| 129 |
+
def test_directed_projection(self):
|
| 130 |
+
G = nx.DiGraph()
|
| 131 |
+
G.add_edge("A", 1)
|
| 132 |
+
G.add_edge(1, "B")
|
| 133 |
+
G.add_edge("A", 2)
|
| 134 |
+
G.add_edge("B", 2)
|
| 135 |
+
P = bipartite.projected_graph(G, "AB")
|
| 136 |
+
assert edges_equal(list(P.edges()), [("A", "B")])
|
| 137 |
+
P = bipartite.weighted_projected_graph(G, "AB")
|
| 138 |
+
assert edges_equal(list(P.edges()), [("A", "B")])
|
| 139 |
+
assert P["A"]["B"]["weight"] == 1
|
| 140 |
+
|
| 141 |
+
P = bipartite.projected_graph(G, "AB", multigraph=True)
|
| 142 |
+
assert edges_equal(list(P.edges()), [("A", "B")])
|
| 143 |
+
|
| 144 |
+
G = nx.DiGraph()
|
| 145 |
+
G.add_edge("A", 1)
|
| 146 |
+
G.add_edge(1, "B")
|
| 147 |
+
G.add_edge("A", 2)
|
| 148 |
+
G.add_edge(2, "B")
|
| 149 |
+
P = bipartite.projected_graph(G, "AB")
|
| 150 |
+
assert edges_equal(list(P.edges()), [("A", "B")])
|
| 151 |
+
P = bipartite.weighted_projected_graph(G, "AB")
|
| 152 |
+
assert edges_equal(list(P.edges()), [("A", "B")])
|
| 153 |
+
assert P["A"]["B"]["weight"] == 2
|
| 154 |
+
|
| 155 |
+
P = bipartite.projected_graph(G, "AB", multigraph=True)
|
| 156 |
+
assert edges_equal(list(P.edges()), [("A", "B"), ("A", "B")])
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
class TestBipartiteWeightedProjection:
|
| 160 |
+
@classmethod
|
| 161 |
+
def setup_class(cls):
|
| 162 |
+
# Tore Opsahl's example
|
| 163 |
+
# http://toreopsahl.com/2009/05/01/projecting-two-mode-networks-onto-weighted-one-mode-networks/
|
| 164 |
+
cls.G = nx.Graph()
|
| 165 |
+
cls.G.add_edge("A", 1)
|
| 166 |
+
cls.G.add_edge("A", 2)
|
| 167 |
+
cls.G.add_edge("B", 1)
|
| 168 |
+
cls.G.add_edge("B", 2)
|
| 169 |
+
cls.G.add_edge("B", 3)
|
| 170 |
+
cls.G.add_edge("B", 4)
|
| 171 |
+
cls.G.add_edge("B", 5)
|
| 172 |
+
cls.G.add_edge("C", 1)
|
| 173 |
+
cls.G.add_edge("D", 3)
|
| 174 |
+
cls.G.add_edge("E", 4)
|
| 175 |
+
cls.G.add_edge("E", 5)
|
| 176 |
+
cls.G.add_edge("E", 6)
|
| 177 |
+
cls.G.add_edge("F", 6)
|
| 178 |
+
# Graph based on figure 6 from Newman (2001)
|
| 179 |
+
cls.N = nx.Graph()
|
| 180 |
+
cls.N.add_edge("A", 1)
|
| 181 |
+
cls.N.add_edge("A", 2)
|
| 182 |
+
cls.N.add_edge("A", 3)
|
| 183 |
+
cls.N.add_edge("B", 1)
|
| 184 |
+
cls.N.add_edge("B", 2)
|
| 185 |
+
cls.N.add_edge("B", 3)
|
| 186 |
+
cls.N.add_edge("C", 1)
|
| 187 |
+
cls.N.add_edge("D", 1)
|
| 188 |
+
cls.N.add_edge("E", 3)
|
| 189 |
+
|
| 190 |
+
def test_project_weighted_shared(self):
|
| 191 |
+
edges = [
|
| 192 |
+
("A", "B", 2),
|
| 193 |
+
("A", "C", 1),
|
| 194 |
+
("B", "C", 1),
|
| 195 |
+
("B", "D", 1),
|
| 196 |
+
("B", "E", 2),
|
| 197 |
+
("E", "F", 1),
|
| 198 |
+
]
|
| 199 |
+
Panswer = nx.Graph()
|
| 200 |
+
Panswer.add_weighted_edges_from(edges)
|
| 201 |
+
P = bipartite.weighted_projected_graph(self.G, "ABCDEF")
|
| 202 |
+
assert edges_equal(list(P.edges()), Panswer.edges())
|
| 203 |
+
for u, v in list(P.edges()):
|
| 204 |
+
assert P[u][v]["weight"] == Panswer[u][v]["weight"]
|
| 205 |
+
|
| 206 |
+
edges = [
|
| 207 |
+
("A", "B", 3),
|
| 208 |
+
("A", "E", 1),
|
| 209 |
+
("A", "C", 1),
|
| 210 |
+
("A", "D", 1),
|
| 211 |
+
("B", "E", 1),
|
| 212 |
+
("B", "C", 1),
|
| 213 |
+
("B", "D", 1),
|
| 214 |
+
("C", "D", 1),
|
| 215 |
+
]
|
| 216 |
+
Panswer = nx.Graph()
|
| 217 |
+
Panswer.add_weighted_edges_from(edges)
|
| 218 |
+
P = bipartite.weighted_projected_graph(self.N, "ABCDE")
|
| 219 |
+
assert edges_equal(list(P.edges()), Panswer.edges())
|
| 220 |
+
for u, v in list(P.edges()):
|
| 221 |
+
assert P[u][v]["weight"] == Panswer[u][v]["weight"]
|
| 222 |
+
|
| 223 |
+
def test_project_weighted_newman(self):
|
| 224 |
+
edges = [
|
| 225 |
+
("A", "B", 1.5),
|
| 226 |
+
("A", "C", 0.5),
|
| 227 |
+
("B", "C", 0.5),
|
| 228 |
+
("B", "D", 1),
|
| 229 |
+
("B", "E", 2),
|
| 230 |
+
("E", "F", 1),
|
| 231 |
+
]
|
| 232 |
+
Panswer = nx.Graph()
|
| 233 |
+
Panswer.add_weighted_edges_from(edges)
|
| 234 |
+
P = bipartite.collaboration_weighted_projected_graph(self.G, "ABCDEF")
|
| 235 |
+
assert edges_equal(list(P.edges()), Panswer.edges())
|
| 236 |
+
for u, v in list(P.edges()):
|
| 237 |
+
assert P[u][v]["weight"] == Panswer[u][v]["weight"]
|
| 238 |
+
|
| 239 |
+
edges = [
|
| 240 |
+
("A", "B", 11 / 6.0),
|
| 241 |
+
("A", "E", 1 / 2.0),
|
| 242 |
+
("A", "C", 1 / 3.0),
|
| 243 |
+
("A", "D", 1 / 3.0),
|
| 244 |
+
("B", "E", 1 / 2.0),
|
| 245 |
+
("B", "C", 1 / 3.0),
|
| 246 |
+
("B", "D", 1 / 3.0),
|
| 247 |
+
("C", "D", 1 / 3.0),
|
| 248 |
+
]
|
| 249 |
+
Panswer = nx.Graph()
|
| 250 |
+
Panswer.add_weighted_edges_from(edges)
|
| 251 |
+
P = bipartite.collaboration_weighted_projected_graph(self.N, "ABCDE")
|
| 252 |
+
assert edges_equal(list(P.edges()), Panswer.edges())
|
| 253 |
+
for u, v in list(P.edges()):
|
| 254 |
+
assert P[u][v]["weight"] == Panswer[u][v]["weight"]
|
| 255 |
+
|
| 256 |
+
def test_project_weighted_ratio(self):
|
| 257 |
+
edges = [
|
| 258 |
+
("A", "B", 2 / 6.0),
|
| 259 |
+
("A", "C", 1 / 6.0),
|
| 260 |
+
("B", "C", 1 / 6.0),
|
| 261 |
+
("B", "D", 1 / 6.0),
|
| 262 |
+
("B", "E", 2 / 6.0),
|
| 263 |
+
("E", "F", 1 / 6.0),
|
| 264 |
+
]
|
| 265 |
+
Panswer = nx.Graph()
|
| 266 |
+
Panswer.add_weighted_edges_from(edges)
|
| 267 |
+
P = bipartite.weighted_projected_graph(self.G, "ABCDEF", ratio=True)
|
| 268 |
+
assert edges_equal(list(P.edges()), Panswer.edges())
|
| 269 |
+
for u, v in list(P.edges()):
|
| 270 |
+
assert P[u][v]["weight"] == Panswer[u][v]["weight"]
|
| 271 |
+
|
| 272 |
+
edges = [
|
| 273 |
+
("A", "B", 3 / 3.0),
|
| 274 |
+
("A", "E", 1 / 3.0),
|
| 275 |
+
("A", "C", 1 / 3.0),
|
| 276 |
+
("A", "D", 1 / 3.0),
|
| 277 |
+
("B", "E", 1 / 3.0),
|
| 278 |
+
("B", "C", 1 / 3.0),
|
| 279 |
+
("B", "D", 1 / 3.0),
|
| 280 |
+
("C", "D", 1 / 3.0),
|
| 281 |
+
]
|
| 282 |
+
Panswer = nx.Graph()
|
| 283 |
+
Panswer.add_weighted_edges_from(edges)
|
| 284 |
+
P = bipartite.weighted_projected_graph(self.N, "ABCDE", ratio=True)
|
| 285 |
+
assert edges_equal(list(P.edges()), Panswer.edges())
|
| 286 |
+
for u, v in list(P.edges()):
|
| 287 |
+
assert P[u][v]["weight"] == Panswer[u][v]["weight"]
|
| 288 |
+
|
| 289 |
+
def test_project_weighted_overlap(self):
|
| 290 |
+
edges = [
|
| 291 |
+
("A", "B", 2 / 2.0),
|
| 292 |
+
("A", "C", 1 / 1.0),
|
| 293 |
+
("B", "C", 1 / 1.0),
|
| 294 |
+
("B", "D", 1 / 1.0),
|
| 295 |
+
("B", "E", 2 / 3.0),
|
| 296 |
+
("E", "F", 1 / 1.0),
|
| 297 |
+
]
|
| 298 |
+
Panswer = nx.Graph()
|
| 299 |
+
Panswer.add_weighted_edges_from(edges)
|
| 300 |
+
P = bipartite.overlap_weighted_projected_graph(self.G, "ABCDEF", jaccard=False)
|
| 301 |
+
assert edges_equal(list(P.edges()), Panswer.edges())
|
| 302 |
+
for u, v in list(P.edges()):
|
| 303 |
+
assert P[u][v]["weight"] == Panswer[u][v]["weight"]
|
| 304 |
+
|
| 305 |
+
edges = [
|
| 306 |
+
("A", "B", 3 / 3.0),
|
| 307 |
+
("A", "E", 1 / 1.0),
|
| 308 |
+
("A", "C", 1 / 1.0),
|
| 309 |
+
("A", "D", 1 / 1.0),
|
| 310 |
+
("B", "E", 1 / 1.0),
|
| 311 |
+
("B", "C", 1 / 1.0),
|
| 312 |
+
("B", "D", 1 / 1.0),
|
| 313 |
+
("C", "D", 1 / 1.0),
|
| 314 |
+
]
|
| 315 |
+
Panswer = nx.Graph()
|
| 316 |
+
Panswer.add_weighted_edges_from(edges)
|
| 317 |
+
P = bipartite.overlap_weighted_projected_graph(self.N, "ABCDE", jaccard=False)
|
| 318 |
+
assert edges_equal(list(P.edges()), Panswer.edges())
|
| 319 |
+
for u, v in list(P.edges()):
|
| 320 |
+
assert P[u][v]["weight"] == Panswer[u][v]["weight"]
|
| 321 |
+
|
| 322 |
+
def test_project_weighted_jaccard(self):
|
| 323 |
+
edges = [
|
| 324 |
+
("A", "B", 2 / 5.0),
|
| 325 |
+
("A", "C", 1 / 2.0),
|
| 326 |
+
("B", "C", 1 / 5.0),
|
| 327 |
+
("B", "D", 1 / 5.0),
|
| 328 |
+
("B", "E", 2 / 6.0),
|
| 329 |
+
("E", "F", 1 / 3.0),
|
| 330 |
+
]
|
| 331 |
+
Panswer = nx.Graph()
|
| 332 |
+
Panswer.add_weighted_edges_from(edges)
|
| 333 |
+
P = bipartite.overlap_weighted_projected_graph(self.G, "ABCDEF")
|
| 334 |
+
assert edges_equal(list(P.edges()), Panswer.edges())
|
| 335 |
+
for u, v in list(P.edges()):
|
| 336 |
+
assert P[u][v]["weight"] == Panswer[u][v]["weight"]
|
| 337 |
+
|
| 338 |
+
edges = [
|
| 339 |
+
("A", "B", 3 / 3.0),
|
| 340 |
+
("A", "E", 1 / 3.0),
|
| 341 |
+
("A", "C", 1 / 3.0),
|
| 342 |
+
("A", "D", 1 / 3.0),
|
| 343 |
+
("B", "E", 1 / 3.0),
|
| 344 |
+
("B", "C", 1 / 3.0),
|
| 345 |
+
("B", "D", 1 / 3.0),
|
| 346 |
+
("C", "D", 1 / 1.0),
|
| 347 |
+
]
|
| 348 |
+
Panswer = nx.Graph()
|
| 349 |
+
Panswer.add_weighted_edges_from(edges)
|
| 350 |
+
P = bipartite.overlap_weighted_projected_graph(self.N, "ABCDE")
|
| 351 |
+
assert edges_equal(list(P.edges()), Panswer.edges())
|
| 352 |
+
for u, v in P.edges():
|
| 353 |
+
assert P[u][v]["weight"] == Panswer[u][v]["weight"]
|
| 354 |
+
|
| 355 |
+
def test_generic_weighted_projected_graph_simple(self):
|
| 356 |
+
def shared(G, u, v):
|
| 357 |
+
return len(set(G[u]) & set(G[v]))
|
| 358 |
+
|
| 359 |
+
B = nx.path_graph(5)
|
| 360 |
+
G = bipartite.generic_weighted_projected_graph(
|
| 361 |
+
B, [0, 2, 4], weight_function=shared
|
| 362 |
+
)
|
| 363 |
+
assert nodes_equal(list(G), [0, 2, 4])
|
| 364 |
+
assert edges_equal(
|
| 365 |
+
list(G.edges(data=True)),
|
| 366 |
+
[(0, 2, {"weight": 1}), (2, 4, {"weight": 1})],
|
| 367 |
+
)
|
| 368 |
+
|
| 369 |
+
G = bipartite.generic_weighted_projected_graph(B, [0, 2, 4])
|
| 370 |
+
assert nodes_equal(list(G), [0, 2, 4])
|
| 371 |
+
assert edges_equal(
|
| 372 |
+
list(G.edges(data=True)),
|
| 373 |
+
[(0, 2, {"weight": 1}), (2, 4, {"weight": 1})],
|
| 374 |
+
)
|
| 375 |
+
B = nx.DiGraph()
|
| 376 |
+
nx.add_path(B, range(5))
|
| 377 |
+
G = bipartite.generic_weighted_projected_graph(B, [0, 2, 4])
|
| 378 |
+
assert nodes_equal(list(G), [0, 2, 4])
|
| 379 |
+
assert edges_equal(
|
| 380 |
+
list(G.edges(data=True)), [(0, 2, {"weight": 1}), (2, 4, {"weight": 1})]
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
def test_generic_weighted_projected_graph_custom(self):
|
| 384 |
+
def jaccard(G, u, v):
|
| 385 |
+
unbrs = set(G[u])
|
| 386 |
+
vnbrs = set(G[v])
|
| 387 |
+
return len(unbrs & vnbrs) / len(unbrs | vnbrs)
|
| 388 |
+
|
| 389 |
+
def my_weight(G, u, v, weight="weight"):
|
| 390 |
+
w = 0
|
| 391 |
+
for nbr in set(G[u]) & set(G[v]):
|
| 392 |
+
w += G.edges[u, nbr].get(weight, 1) + G.edges[v, nbr].get(weight, 1)
|
| 393 |
+
return w
|
| 394 |
+
|
| 395 |
+
B = nx.bipartite.complete_bipartite_graph(2, 2)
|
| 396 |
+
for i, (u, v) in enumerate(B.edges()):
|
| 397 |
+
B.edges[u, v]["weight"] = i + 1
|
| 398 |
+
G = bipartite.generic_weighted_projected_graph(
|
| 399 |
+
B, [0, 1], weight_function=jaccard
|
| 400 |
+
)
|
| 401 |
+
assert edges_equal(list(G.edges(data=True)), [(0, 1, {"weight": 1.0})])
|
| 402 |
+
G = bipartite.generic_weighted_projected_graph(
|
| 403 |
+
B, [0, 1], weight_function=my_weight
|
| 404 |
+
)
|
| 405 |
+
assert edges_equal(list(G.edges(data=True)), [(0, 1, {"weight": 10})])
|
| 406 |
+
G = bipartite.generic_weighted_projected_graph(B, [0, 1])
|
| 407 |
+
assert edges_equal(list(G.edges(data=True)), [(0, 1, {"weight": 2})])
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_redundancy.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit tests for the :mod:`networkx.algorithms.bipartite.redundancy` module."""
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
from networkx import NetworkXError, cycle_graph
|
| 6 |
+
from networkx.algorithms.bipartite import complete_bipartite_graph, node_redundancy
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_no_redundant_nodes():
|
| 10 |
+
G = complete_bipartite_graph(2, 2)
|
| 11 |
+
|
| 12 |
+
# when nodes is None
|
| 13 |
+
rc = node_redundancy(G)
|
| 14 |
+
assert all(redundancy == 1 for redundancy in rc.values())
|
| 15 |
+
|
| 16 |
+
# when set of nodes is specified
|
| 17 |
+
rc = node_redundancy(G, (2, 3))
|
| 18 |
+
assert rc == {2: 1.0, 3: 1.0}
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def test_redundant_nodes():
|
| 22 |
+
G = cycle_graph(6)
|
| 23 |
+
edge = {0, 3}
|
| 24 |
+
G.add_edge(*edge)
|
| 25 |
+
redundancy = node_redundancy(G)
|
| 26 |
+
for v in edge:
|
| 27 |
+
assert redundancy[v] == 2 / 3
|
| 28 |
+
for v in set(G) - edge:
|
| 29 |
+
assert redundancy[v] == 1
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def test_not_enough_neighbors():
|
| 33 |
+
with pytest.raises(NetworkXError):
|
| 34 |
+
G = complete_bipartite_graph(1, 2)
|
| 35 |
+
node_redundancy(G)
|
.venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
|
| 3 |
+
pytest.importorskip("scipy")
|
| 4 |
+
|
| 5 |
+
import networkx as nx
|
| 6 |
+
from networkx.algorithms.bipartite import spectral_bipartivity as sb
|
| 7 |
+
|
| 8 |
+
# Examples from Figure 1
|
| 9 |
+
# E. Estrada and J. A. Rodríguez-Velázquez, "Spectral measures of
|
| 10 |
+
# bipartivity in complex networks", PhysRev E 72, 046105 (2005)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class TestSpectralBipartivity:
|
| 14 |
+
def test_star_like(self):
|
| 15 |
+
# star-like
|
| 16 |
+
|
| 17 |
+
G = nx.star_graph(2)
|
| 18 |
+
G.add_edge(1, 2)
|
| 19 |
+
assert sb(G) == pytest.approx(0.843, abs=1e-3)
|
| 20 |
+
|
| 21 |
+
G = nx.star_graph(3)
|
| 22 |
+
G.add_edge(1, 2)
|
| 23 |
+
assert sb(G) == pytest.approx(0.871, abs=1e-3)
|
| 24 |
+
|
| 25 |
+
G = nx.star_graph(4)
|
| 26 |
+
G.add_edge(1, 2)
|
| 27 |
+
assert sb(G) == pytest.approx(0.890, abs=1e-3)
|
| 28 |
+
|
| 29 |
+
def test_k23_like(self):
|
| 30 |
+
# K2,3-like
|
| 31 |
+
G = nx.complete_bipartite_graph(2, 3)
|
| 32 |
+
G.add_edge(0, 1)
|
| 33 |
+
assert sb(G) == pytest.approx(0.769, abs=1e-3)
|
| 34 |
+
|
| 35 |
+
G = nx.complete_bipartite_graph(2, 3)
|
| 36 |
+
G.add_edge(2, 4)
|
| 37 |
+
assert sb(G) == pytest.approx(0.829, abs=1e-3)
|
| 38 |
+
|
| 39 |
+
G = nx.complete_bipartite_graph(2, 3)
|
| 40 |
+
G.add_edge(2, 4)
|
| 41 |
+
G.add_edge(3, 4)
|
| 42 |
+
assert sb(G) == pytest.approx(0.731, abs=1e-3)
|
| 43 |
+
|
| 44 |
+
G = nx.complete_bipartite_graph(2, 3)
|
| 45 |
+
G.add_edge(0, 1)
|
| 46 |
+
G.add_edge(2, 4)
|
| 47 |
+
assert sb(G) == pytest.approx(0.692, abs=1e-3)
|
| 48 |
+
|
| 49 |
+
G = nx.complete_bipartite_graph(2, 3)
|
| 50 |
+
G.add_edge(2, 4)
|
| 51 |
+
G.add_edge(3, 4)
|
| 52 |
+
G.add_edge(0, 1)
|
| 53 |
+
assert sb(G) == pytest.approx(0.645, abs=1e-3)
|
| 54 |
+
|
| 55 |
+
G = nx.complete_bipartite_graph(2, 3)
|
| 56 |
+
G.add_edge(2, 4)
|
| 57 |
+
G.add_edge(3, 4)
|
| 58 |
+
G.add_edge(2, 3)
|
| 59 |
+
assert sb(G) == pytest.approx(0.645, abs=1e-3)
|
| 60 |
+
|
| 61 |
+
G = nx.complete_bipartite_graph(2, 3)
|
| 62 |
+
G.add_edge(2, 4)
|
| 63 |
+
G.add_edge(3, 4)
|
| 64 |
+
G.add_edge(2, 3)
|
| 65 |
+
G.add_edge(0, 1)
|
| 66 |
+
assert sb(G) == pytest.approx(0.597, abs=1e-3)
|
| 67 |
+
|
| 68 |
+
def test_single_nodes(self):
|
| 69 |
+
# single nodes
|
| 70 |
+
G = nx.complete_bipartite_graph(2, 3)
|
| 71 |
+
G.add_edge(2, 4)
|
| 72 |
+
sbn = sb(G, nodes=[1, 2])
|
| 73 |
+
assert sbn[1] == pytest.approx(0.85, abs=1e-2)
|
| 74 |
+
assert sbn[2] == pytest.approx(0.77, abs=1e-2)
|
| 75 |
+
|
| 76 |
+
G = nx.complete_bipartite_graph(2, 3)
|
| 77 |
+
G.add_edge(0, 1)
|
| 78 |
+
sbn = sb(G, nodes=[1, 2])
|
| 79 |
+
assert sbn[1] == pytest.approx(0.73, abs=1e-2)
|
| 80 |
+
assert sbn[2] == pytest.approx(0.82, abs=1e-2)
|
.venv/lib/python3.11/site-packages/networkx/generators/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
A package for generating various graphs in networkx.
|
| 3 |
+
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from networkx.generators.atlas import *
|
| 7 |
+
from networkx.generators.classic import *
|
| 8 |
+
from networkx.generators.cographs import *
|
| 9 |
+
from networkx.generators.community import *
|
| 10 |
+
from networkx.generators.degree_seq import *
|
| 11 |
+
from networkx.generators.directed import *
|
| 12 |
+
from networkx.generators.duplication import *
|
| 13 |
+
from networkx.generators.ego import *
|
| 14 |
+
from networkx.generators.expanders import *
|
| 15 |
+
from networkx.generators.geometric import *
|
| 16 |
+
from networkx.generators.harary_graph import *
|
| 17 |
+
from networkx.generators.internet_as_graphs import *
|
| 18 |
+
from networkx.generators.intersection import *
|
| 19 |
+
from networkx.generators.interval_graph import *
|
| 20 |
+
from networkx.generators.joint_degree_seq import *
|
| 21 |
+
from networkx.generators.lattice import *
|
| 22 |
+
from networkx.generators.line import *
|
| 23 |
+
from networkx.generators.mycielski import *
|
| 24 |
+
from networkx.generators.nonisomorphic_trees import *
|
| 25 |
+
from networkx.generators.random_clustered import *
|
| 26 |
+
from networkx.generators.random_graphs import *
|
| 27 |
+
from networkx.generators.small import *
|
| 28 |
+
from networkx.generators.social import *
|
| 29 |
+
from networkx.generators.spectral_graph_forge import *
|
| 30 |
+
from networkx.generators.stochastic import *
|
| 31 |
+
from networkx.generators.sudoku import *
|
| 32 |
+
from networkx.generators.time_series import *
|
| 33 |
+
from networkx.generators.trees import *
|
| 34 |
+
from networkx.generators.triads import *
|
.venv/lib/python3.11/site-packages/networkx/generators/__pycache__/directed.cpython-311.pyc
ADDED
|
Binary file (20.9 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/generators/__pycache__/duplication.cpython-311.pyc
ADDED
|
Binary file (6.81 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/generators/__pycache__/geometric.cpython-311.pyc
ADDED
|
Binary file (48.5 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/generators/__pycache__/joint_degree_seq.cpython-311.pyc
ADDED
|
Binary file (25.4 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/generators/__pycache__/mycielski.cpython-311.pyc
ADDED
|
Binary file (5.25 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/generators/__pycache__/social.cpython-311.pyc
ADDED
|
Binary file (29.5 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/generators/__pycache__/spectral_graph_forge.cpython-311.pyc
ADDED
|
Binary file (5.67 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/generators/__pycache__/trees.cpython-311.pyc
ADDED
|
Binary file (44.4 kB). View file
|
|
|
.venv/lib/python3.11/site-packages/networkx/generators/atlas.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Generators for the small graph atlas.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import gzip
|
| 6 |
+
import importlib.resources
|
| 7 |
+
import os
|
| 8 |
+
import os.path
|
| 9 |
+
from itertools import islice
|
| 10 |
+
|
| 11 |
+
import networkx as nx
|
| 12 |
+
|
| 13 |
+
__all__ = ["graph_atlas", "graph_atlas_g"]
|
| 14 |
+
|
| 15 |
+
#: The total number of graphs in the atlas.
|
| 16 |
+
#:
|
| 17 |
+
#: The graphs are labeled starting from 0 and extending to (but not
|
| 18 |
+
#: including) this number.
|
| 19 |
+
NUM_GRAPHS = 1253
|
| 20 |
+
|
| 21 |
+
#: The path to the data file containing the graph edge lists.
|
| 22 |
+
#:
|
| 23 |
+
#: This is the absolute path of the gzipped text file containing the
|
| 24 |
+
#: edge list for each graph in the atlas. The file contains one entry
|
| 25 |
+
#: per graph in the atlas, in sequential order, starting from graph
|
| 26 |
+
#: number 0 and extending through graph number 1252 (see
|
| 27 |
+
#: :data:`NUM_GRAPHS`). Each entry looks like
|
| 28 |
+
#:
|
| 29 |
+
#: .. sourcecode:: text
|
| 30 |
+
#:
|
| 31 |
+
#: GRAPH 6
|
| 32 |
+
#: NODES 3
|
| 33 |
+
#: 0 1
|
| 34 |
+
#: 0 2
|
| 35 |
+
#:
|
| 36 |
+
#: where the first two lines are the graph's index in the atlas and the
|
| 37 |
+
#: number of nodes in the graph, and the remaining lines are the edge
|
| 38 |
+
#: list.
|
| 39 |
+
#:
|
| 40 |
+
#: This file was generated from a Python list of graphs via code like
|
| 41 |
+
#: the following::
|
| 42 |
+
#:
|
| 43 |
+
#: import gzip
|
| 44 |
+
#: from networkx.generators.atlas import graph_atlas_g
|
| 45 |
+
#: from networkx.readwrite.edgelist import write_edgelist
|
| 46 |
+
#:
|
| 47 |
+
#: with gzip.open('atlas.dat.gz', 'wb') as f:
|
| 48 |
+
#: for i, G in enumerate(graph_atlas_g()):
|
| 49 |
+
#: f.write(bytes(f'GRAPH {i}\n', encoding='utf-8'))
|
| 50 |
+
#: f.write(bytes(f'NODES {len(G)}\n', encoding='utf-8'))
|
| 51 |
+
#: write_edgelist(G, f, data=False)
|
| 52 |
+
#:
|
| 53 |
+
|
| 54 |
+
# Path to the atlas file
|
| 55 |
+
ATLAS_FILE = importlib.resources.files("networkx.generators") / "atlas.dat.gz"
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _generate_graphs():
|
| 59 |
+
"""Sequentially read the file containing the edge list data for the
|
| 60 |
+
graphs in the atlas and generate the graphs one at a time.
|
| 61 |
+
|
| 62 |
+
This function reads the file given in :data:`.ATLAS_FILE`.
|
| 63 |
+
|
| 64 |
+
"""
|
| 65 |
+
with gzip.open(ATLAS_FILE, "rb") as f:
|
| 66 |
+
line = f.readline()
|
| 67 |
+
while line and line.startswith(b"GRAPH"):
|
| 68 |
+
# The first two lines of each entry tell us the index of the
|
| 69 |
+
# graph in the list and the number of nodes in the graph.
|
| 70 |
+
# They look like this:
|
| 71 |
+
#
|
| 72 |
+
# GRAPH 3
|
| 73 |
+
# NODES 2
|
| 74 |
+
#
|
| 75 |
+
graph_index = int(line[6:].rstrip())
|
| 76 |
+
line = f.readline()
|
| 77 |
+
num_nodes = int(line[6:].rstrip())
|
| 78 |
+
# The remaining lines contain the edge list, until the next
|
| 79 |
+
# GRAPH line (or until the end of the file).
|
| 80 |
+
edgelist = []
|
| 81 |
+
line = f.readline()
|
| 82 |
+
while line and not line.startswith(b"GRAPH"):
|
| 83 |
+
edgelist.append(line.rstrip())
|
| 84 |
+
line = f.readline()
|
| 85 |
+
G = nx.Graph()
|
| 86 |
+
G.name = f"G{graph_index}"
|
| 87 |
+
G.add_nodes_from(range(num_nodes))
|
| 88 |
+
G.add_edges_from(tuple(map(int, e.split())) for e in edgelist)
|
| 89 |
+
yield G
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 93 |
+
def graph_atlas(i):
|
| 94 |
+
"""Returns graph number `i` from the Graph Atlas.
|
| 95 |
+
|
| 96 |
+
For more information, see :func:`.graph_atlas_g`.
|
| 97 |
+
|
| 98 |
+
Parameters
|
| 99 |
+
----------
|
| 100 |
+
i : int
|
| 101 |
+
The index of the graph from the atlas to get. The graph at index
|
| 102 |
+
0 is assumed to be the null graph.
|
| 103 |
+
|
| 104 |
+
Returns
|
| 105 |
+
-------
|
| 106 |
+
list
|
| 107 |
+
A list of :class:`~networkx.Graph` objects, the one at index *i*
|
| 108 |
+
corresponding to the graph *i* in the Graph Atlas.
|
| 109 |
+
|
| 110 |
+
See also
|
| 111 |
+
--------
|
| 112 |
+
graph_atlas_g
|
| 113 |
+
|
| 114 |
+
Notes
|
| 115 |
+
-----
|
| 116 |
+
The time required by this function increases linearly with the
|
| 117 |
+
argument `i`, since it reads a large file sequentially in order to
|
| 118 |
+
generate the graph [1]_.
|
| 119 |
+
|
| 120 |
+
References
|
| 121 |
+
----------
|
| 122 |
+
.. [1] Ronald C. Read and Robin J. Wilson, *An Atlas of Graphs*.
|
| 123 |
+
Oxford University Press, 1998.
|
| 124 |
+
|
| 125 |
+
"""
|
| 126 |
+
if not (0 <= i < NUM_GRAPHS):
|
| 127 |
+
raise ValueError(f"index must be between 0 and {NUM_GRAPHS}")
|
| 128 |
+
return next(islice(_generate_graphs(), i, None))
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 132 |
+
def graph_atlas_g():
|
| 133 |
+
"""Returns the list of all graphs with up to seven nodes named in the
|
| 134 |
+
Graph Atlas.
|
| 135 |
+
|
| 136 |
+
The graphs are listed in increasing order by
|
| 137 |
+
|
| 138 |
+
1. number of nodes,
|
| 139 |
+
2. number of edges,
|
| 140 |
+
3. degree sequence (for example 111223 < 112222),
|
| 141 |
+
4. number of automorphisms,
|
| 142 |
+
|
| 143 |
+
in that order, with three exceptions as described in the *Notes*
|
| 144 |
+
section below. This causes the list to correspond with the index of
|
| 145 |
+
the graphs in the Graph Atlas [atlas]_, with the first graph,
|
| 146 |
+
``G[0]``, being the null graph.
|
| 147 |
+
|
| 148 |
+
Returns
|
| 149 |
+
-------
|
| 150 |
+
list
|
| 151 |
+
A list of :class:`~networkx.Graph` objects, the one at index *i*
|
| 152 |
+
corresponding to the graph *i* in the Graph Atlas.
|
| 153 |
+
|
| 154 |
+
See also
|
| 155 |
+
--------
|
| 156 |
+
graph_atlas
|
| 157 |
+
|
| 158 |
+
Notes
|
| 159 |
+
-----
|
| 160 |
+
This function may be expensive in both time and space, since it
|
| 161 |
+
reads a large file sequentially in order to populate the list.
|
| 162 |
+
|
| 163 |
+
Although the NetworkX atlas functions match the order of graphs
|
| 164 |
+
given in the "Atlas of Graphs" book, there are (at least) three
|
| 165 |
+
errors in the ordering described in the book. The following three
|
| 166 |
+
pairs of nodes violate the lexicographically nondecreasing sorted
|
| 167 |
+
degree sequence rule:
|
| 168 |
+
|
| 169 |
+
- graphs 55 and 56 with degree sequences 001111 and 000112,
|
| 170 |
+
- graphs 1007 and 1008 with degree sequences 3333444 and 3333336,
|
| 171 |
+
- graphs 1012 and 1213 with degree sequences 1244555 and 1244456.
|
| 172 |
+
|
| 173 |
+
References
|
| 174 |
+
----------
|
| 175 |
+
.. [atlas] Ronald C. Read and Robin J. Wilson,
|
| 176 |
+
*An Atlas of Graphs*.
|
| 177 |
+
Oxford University Press, 1998.
|
| 178 |
+
|
| 179 |
+
"""
|
| 180 |
+
return list(_generate_graphs())
|
.venv/lib/python3.11/site-packages/networkx/generators/classic.py
ADDED
|
@@ -0,0 +1,1068 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Generators for some classic graphs.
|
| 2 |
+
|
| 3 |
+
The typical graph builder function is called as follows:
|
| 4 |
+
|
| 5 |
+
>>> G = nx.complete_graph(100)
|
| 6 |
+
|
| 7 |
+
returning the complete graph on n nodes labeled 0, .., 99
|
| 8 |
+
as a simple graph. Except for `empty_graph`, all the functions
|
| 9 |
+
in this module return a Graph class (i.e. a simple, undirected graph).
|
| 10 |
+
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import itertools
|
| 14 |
+
import numbers
|
| 15 |
+
|
| 16 |
+
import networkx as nx
|
| 17 |
+
from networkx.classes import Graph
|
| 18 |
+
from networkx.exception import NetworkXError
|
| 19 |
+
from networkx.utils import nodes_or_number, pairwise
|
| 20 |
+
|
| 21 |
+
__all__ = [
|
| 22 |
+
"balanced_tree",
|
| 23 |
+
"barbell_graph",
|
| 24 |
+
"binomial_tree",
|
| 25 |
+
"complete_graph",
|
| 26 |
+
"complete_multipartite_graph",
|
| 27 |
+
"circular_ladder_graph",
|
| 28 |
+
"circulant_graph",
|
| 29 |
+
"cycle_graph",
|
| 30 |
+
"dorogovtsev_goltsev_mendes_graph",
|
| 31 |
+
"empty_graph",
|
| 32 |
+
"full_rary_tree",
|
| 33 |
+
"kneser_graph",
|
| 34 |
+
"ladder_graph",
|
| 35 |
+
"lollipop_graph",
|
| 36 |
+
"null_graph",
|
| 37 |
+
"path_graph",
|
| 38 |
+
"star_graph",
|
| 39 |
+
"tadpole_graph",
|
| 40 |
+
"trivial_graph",
|
| 41 |
+
"turan_graph",
|
| 42 |
+
"wheel_graph",
|
| 43 |
+
]
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
# -------------------------------------------------------------------
|
| 47 |
+
# Some Classic Graphs
|
| 48 |
+
# -------------------------------------------------------------------
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _tree_edges(n, r):
|
| 52 |
+
if n == 0:
|
| 53 |
+
return
|
| 54 |
+
# helper function for trees
|
| 55 |
+
# yields edges in rooted tree at 0 with n nodes and branching ratio r
|
| 56 |
+
nodes = iter(range(n))
|
| 57 |
+
parents = [next(nodes)] # stack of max length r
|
| 58 |
+
while parents:
|
| 59 |
+
source = parents.pop(0)
|
| 60 |
+
for i in range(r):
|
| 61 |
+
try:
|
| 62 |
+
target = next(nodes)
|
| 63 |
+
parents.append(target)
|
| 64 |
+
yield source, target
|
| 65 |
+
except StopIteration:
|
| 66 |
+
break
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 70 |
+
def full_rary_tree(r, n, create_using=None):
|
| 71 |
+
"""Creates a full r-ary tree of `n` nodes.
|
| 72 |
+
|
| 73 |
+
Sometimes called a k-ary, n-ary, or m-ary tree.
|
| 74 |
+
"... all non-leaf nodes have exactly r children and all levels
|
| 75 |
+
are full except for some rightmost position of the bottom level
|
| 76 |
+
(if a leaf at the bottom level is missing, then so are all of the
|
| 77 |
+
leaves to its right." [1]_
|
| 78 |
+
|
| 79 |
+
.. plot::
|
| 80 |
+
|
| 81 |
+
>>> nx.draw(nx.full_rary_tree(2, 10))
|
| 82 |
+
|
| 83 |
+
Parameters
|
| 84 |
+
----------
|
| 85 |
+
r : int
|
| 86 |
+
branching factor of the tree
|
| 87 |
+
n : int
|
| 88 |
+
Number of nodes in the tree
|
| 89 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 90 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 91 |
+
|
| 92 |
+
Returns
|
| 93 |
+
-------
|
| 94 |
+
G : networkx Graph
|
| 95 |
+
An r-ary tree with n nodes
|
| 96 |
+
|
| 97 |
+
References
|
| 98 |
+
----------
|
| 99 |
+
.. [1] An introduction to data structures and algorithms,
|
| 100 |
+
James Andrew Storer, Birkhauser Boston 2001, (page 225).
|
| 101 |
+
"""
|
| 102 |
+
G = empty_graph(n, create_using)
|
| 103 |
+
G.add_edges_from(_tree_edges(n, r))
|
| 104 |
+
return G
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 108 |
+
def kneser_graph(n, k):
|
| 109 |
+
"""Returns the Kneser Graph with parameters `n` and `k`.
|
| 110 |
+
|
| 111 |
+
The Kneser Graph has nodes that are k-tuples (subsets) of the integers
|
| 112 |
+
between 0 and ``n-1``. Nodes are adjacent if their corresponding sets are disjoint.
|
| 113 |
+
|
| 114 |
+
Parameters
|
| 115 |
+
----------
|
| 116 |
+
n: int
|
| 117 |
+
Number of integers from which to make node subsets.
|
| 118 |
+
Subsets are drawn from ``set(range(n))``.
|
| 119 |
+
k: int
|
| 120 |
+
Size of the subsets.
|
| 121 |
+
|
| 122 |
+
Returns
|
| 123 |
+
-------
|
| 124 |
+
G : NetworkX Graph
|
| 125 |
+
|
| 126 |
+
Examples
|
| 127 |
+
--------
|
| 128 |
+
>>> G = nx.kneser_graph(5, 2)
|
| 129 |
+
>>> G.number_of_nodes()
|
| 130 |
+
10
|
| 131 |
+
>>> G.number_of_edges()
|
| 132 |
+
15
|
| 133 |
+
>>> nx.is_isomorphic(G, nx.petersen_graph())
|
| 134 |
+
True
|
| 135 |
+
"""
|
| 136 |
+
if n <= 0:
|
| 137 |
+
raise NetworkXError("n should be greater than zero")
|
| 138 |
+
if k <= 0 or k > n:
|
| 139 |
+
raise NetworkXError("k should be greater than zero and smaller than n")
|
| 140 |
+
|
| 141 |
+
G = nx.Graph()
|
| 142 |
+
# Create all k-subsets of [0, 1, ..., n-1]
|
| 143 |
+
subsets = list(itertools.combinations(range(n), k))
|
| 144 |
+
|
| 145 |
+
if 2 * k > n:
|
| 146 |
+
G.add_nodes_from(subsets)
|
| 147 |
+
|
| 148 |
+
universe = set(range(n))
|
| 149 |
+
comb = itertools.combinations # only to make it all fit on one line
|
| 150 |
+
G.add_edges_from((s, t) for s in subsets for t in comb(universe - set(s), k))
|
| 151 |
+
return G
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 155 |
+
def balanced_tree(r, h, create_using=None):
|
| 156 |
+
"""Returns the perfectly balanced `r`-ary tree of height `h`.
|
| 157 |
+
|
| 158 |
+
.. plot::
|
| 159 |
+
|
| 160 |
+
>>> nx.draw(nx.balanced_tree(2, 3))
|
| 161 |
+
|
| 162 |
+
Parameters
|
| 163 |
+
----------
|
| 164 |
+
r : int
|
| 165 |
+
Branching factor of the tree; each node will have `r`
|
| 166 |
+
children.
|
| 167 |
+
|
| 168 |
+
h : int
|
| 169 |
+
Height of the tree.
|
| 170 |
+
|
| 171 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 172 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 173 |
+
|
| 174 |
+
Returns
|
| 175 |
+
-------
|
| 176 |
+
G : NetworkX graph
|
| 177 |
+
A balanced `r`-ary tree of height `h`.
|
| 178 |
+
|
| 179 |
+
Notes
|
| 180 |
+
-----
|
| 181 |
+
This is the rooted tree where all leaves are at distance `h` from
|
| 182 |
+
the root. The root has degree `r` and all other internal nodes
|
| 183 |
+
have degree `r + 1`.
|
| 184 |
+
|
| 185 |
+
Node labels are integers, starting from zero.
|
| 186 |
+
|
| 187 |
+
A balanced tree is also known as a *complete r-ary tree*.
|
| 188 |
+
|
| 189 |
+
"""
|
| 190 |
+
# The number of nodes in the balanced tree is `1 + r + ... + r^h`,
|
| 191 |
+
# which is computed by using the closed-form formula for a geometric
|
| 192 |
+
# sum with ratio `r`. In the special case that `r` is 1, the number
|
| 193 |
+
# of nodes is simply `h + 1` (since the tree is actually a path
|
| 194 |
+
# graph).
|
| 195 |
+
if r == 1:
|
| 196 |
+
n = h + 1
|
| 197 |
+
else:
|
| 198 |
+
# This must be an integer if both `r` and `h` are integers. If
|
| 199 |
+
# they are not, we force integer division anyway.
|
| 200 |
+
n = (1 - r ** (h + 1)) // (1 - r)
|
| 201 |
+
return full_rary_tree(r, n, create_using=create_using)
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 205 |
+
def barbell_graph(m1, m2, create_using=None):
|
| 206 |
+
"""Returns the Barbell Graph: two complete graphs connected by a path.
|
| 207 |
+
|
| 208 |
+
.. plot::
|
| 209 |
+
|
| 210 |
+
>>> nx.draw(nx.barbell_graph(4, 2))
|
| 211 |
+
|
| 212 |
+
Parameters
|
| 213 |
+
----------
|
| 214 |
+
m1 : int
|
| 215 |
+
Size of the left and right barbells, must be greater than 2.
|
| 216 |
+
|
| 217 |
+
m2 : int
|
| 218 |
+
Length of the path connecting the barbells.
|
| 219 |
+
|
| 220 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 221 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 222 |
+
Only undirected Graphs are supported.
|
| 223 |
+
|
| 224 |
+
Returns
|
| 225 |
+
-------
|
| 226 |
+
G : NetworkX graph
|
| 227 |
+
A barbell graph.
|
| 228 |
+
|
| 229 |
+
Notes
|
| 230 |
+
-----
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
Two identical complete graphs $K_{m1}$ form the left and right bells,
|
| 234 |
+
and are connected by a path $P_{m2}$.
|
| 235 |
+
|
| 236 |
+
The `2*m1+m2` nodes are numbered
|
| 237 |
+
`0, ..., m1-1` for the left barbell,
|
| 238 |
+
`m1, ..., m1+m2-1` for the path,
|
| 239 |
+
and `m1+m2, ..., 2*m1+m2-1` for the right barbell.
|
| 240 |
+
|
| 241 |
+
The 3 subgraphs are joined via the edges `(m1-1, m1)` and
|
| 242 |
+
`(m1+m2-1, m1+m2)`. If `m2=0`, this is merely two complete
|
| 243 |
+
graphs joined together.
|
| 244 |
+
|
| 245 |
+
This graph is an extremal example in David Aldous
|
| 246 |
+
and Jim Fill's e-text on Random Walks on Graphs.
|
| 247 |
+
|
| 248 |
+
"""
|
| 249 |
+
if m1 < 2:
|
| 250 |
+
raise NetworkXError("Invalid graph description, m1 should be >=2")
|
| 251 |
+
if m2 < 0:
|
| 252 |
+
raise NetworkXError("Invalid graph description, m2 should be >=0")
|
| 253 |
+
|
| 254 |
+
# left barbell
|
| 255 |
+
G = complete_graph(m1, create_using)
|
| 256 |
+
if G.is_directed():
|
| 257 |
+
raise NetworkXError("Directed Graph not supported")
|
| 258 |
+
|
| 259 |
+
# connecting path
|
| 260 |
+
G.add_nodes_from(range(m1, m1 + m2 - 1))
|
| 261 |
+
if m2 > 1:
|
| 262 |
+
G.add_edges_from(pairwise(range(m1, m1 + m2)))
|
| 263 |
+
|
| 264 |
+
# right barbell
|
| 265 |
+
G.add_edges_from(
|
| 266 |
+
(u, v) for u in range(m1 + m2, 2 * m1 + m2) for v in range(u + 1, 2 * m1 + m2)
|
| 267 |
+
)
|
| 268 |
+
|
| 269 |
+
# connect it up
|
| 270 |
+
G.add_edge(m1 - 1, m1)
|
| 271 |
+
if m2 > 0:
|
| 272 |
+
G.add_edge(m1 + m2 - 1, m1 + m2)
|
| 273 |
+
|
| 274 |
+
return G
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 278 |
+
def binomial_tree(n, create_using=None):
|
| 279 |
+
"""Returns the Binomial Tree of order n.
|
| 280 |
+
|
| 281 |
+
The binomial tree of order 0 consists of a single node. A binomial tree of order k
|
| 282 |
+
is defined recursively by linking two binomial trees of order k-1: the root of one is
|
| 283 |
+
the leftmost child of the root of the other.
|
| 284 |
+
|
| 285 |
+
.. plot::
|
| 286 |
+
|
| 287 |
+
>>> nx.draw(nx.binomial_tree(3))
|
| 288 |
+
|
| 289 |
+
Parameters
|
| 290 |
+
----------
|
| 291 |
+
n : int
|
| 292 |
+
Order of the binomial tree.
|
| 293 |
+
|
| 294 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 295 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 296 |
+
|
| 297 |
+
Returns
|
| 298 |
+
-------
|
| 299 |
+
G : NetworkX graph
|
| 300 |
+
A binomial tree of $2^n$ nodes and $2^n - 1$ edges.
|
| 301 |
+
|
| 302 |
+
"""
|
| 303 |
+
G = nx.empty_graph(1, create_using)
|
| 304 |
+
|
| 305 |
+
N = 1
|
| 306 |
+
for i in range(n):
|
| 307 |
+
# Use G.edges() to ensure 2-tuples. G.edges is 3-tuple for MultiGraph
|
| 308 |
+
edges = [(u + N, v + N) for (u, v) in G.edges()]
|
| 309 |
+
G.add_edges_from(edges)
|
| 310 |
+
G.add_edge(0, N)
|
| 311 |
+
N *= 2
|
| 312 |
+
return G
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 316 |
+
@nodes_or_number(0)
|
| 317 |
+
def complete_graph(n, create_using=None):
|
| 318 |
+
"""Return the complete graph `K_n` with n nodes.
|
| 319 |
+
|
| 320 |
+
A complete graph on `n` nodes means that all pairs
|
| 321 |
+
of distinct nodes have an edge connecting them.
|
| 322 |
+
|
| 323 |
+
.. plot::
|
| 324 |
+
|
| 325 |
+
>>> nx.draw(nx.complete_graph(5))
|
| 326 |
+
|
| 327 |
+
Parameters
|
| 328 |
+
----------
|
| 329 |
+
n : int or iterable container of nodes
|
| 330 |
+
If n is an integer, nodes are from range(n).
|
| 331 |
+
If n is a container of nodes, those nodes appear in the graph.
|
| 332 |
+
Warning: n is not checked for duplicates and if present the
|
| 333 |
+
resulting graph may not be as desired. Make sure you have no duplicates.
|
| 334 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 335 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 336 |
+
|
| 337 |
+
Examples
|
| 338 |
+
--------
|
| 339 |
+
>>> G = nx.complete_graph(9)
|
| 340 |
+
>>> len(G)
|
| 341 |
+
9
|
| 342 |
+
>>> G.size()
|
| 343 |
+
36
|
| 344 |
+
>>> G = nx.complete_graph(range(11, 14))
|
| 345 |
+
>>> list(G.nodes())
|
| 346 |
+
[11, 12, 13]
|
| 347 |
+
>>> G = nx.complete_graph(4, nx.DiGraph())
|
| 348 |
+
>>> G.is_directed()
|
| 349 |
+
True
|
| 350 |
+
|
| 351 |
+
"""
|
| 352 |
+
_, nodes = n
|
| 353 |
+
G = empty_graph(nodes, create_using)
|
| 354 |
+
if len(nodes) > 1:
|
| 355 |
+
if G.is_directed():
|
| 356 |
+
edges = itertools.permutations(nodes, 2)
|
| 357 |
+
else:
|
| 358 |
+
edges = itertools.combinations(nodes, 2)
|
| 359 |
+
G.add_edges_from(edges)
|
| 360 |
+
return G
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 364 |
+
def circular_ladder_graph(n, create_using=None):
|
| 365 |
+
"""Returns the circular ladder graph $CL_n$ of length n.
|
| 366 |
+
|
| 367 |
+
$CL_n$ consists of two concentric n-cycles in which
|
| 368 |
+
each of the n pairs of concentric nodes are joined by an edge.
|
| 369 |
+
|
| 370 |
+
Node labels are the integers 0 to n-1
|
| 371 |
+
|
| 372 |
+
.. plot::
|
| 373 |
+
|
| 374 |
+
>>> nx.draw(nx.circular_ladder_graph(5))
|
| 375 |
+
|
| 376 |
+
"""
|
| 377 |
+
G = ladder_graph(n, create_using)
|
| 378 |
+
G.add_edge(0, n - 1)
|
| 379 |
+
G.add_edge(n, 2 * n - 1)
|
| 380 |
+
return G
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 384 |
+
def circulant_graph(n, offsets, create_using=None):
|
| 385 |
+
r"""Returns the circulant graph $Ci_n(x_1, x_2, ..., x_m)$ with $n$ nodes.
|
| 386 |
+
|
| 387 |
+
The circulant graph $Ci_n(x_1, ..., x_m)$ consists of $n$ nodes $0, ..., n-1$
|
| 388 |
+
such that node $i$ is connected to nodes $(i + x) \mod n$ and $(i - x) \mod n$
|
| 389 |
+
for all $x$ in $x_1, ..., x_m$. Thus $Ci_n(1)$ is a cycle graph.
|
| 390 |
+
|
| 391 |
+
.. plot::
|
| 392 |
+
|
| 393 |
+
>>> nx.draw(nx.circulant_graph(10, [1]))
|
| 394 |
+
|
| 395 |
+
Parameters
|
| 396 |
+
----------
|
| 397 |
+
n : integer
|
| 398 |
+
The number of nodes in the graph.
|
| 399 |
+
offsets : list of integers
|
| 400 |
+
A list of node offsets, $x_1$ up to $x_m$, as described above.
|
| 401 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 402 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 403 |
+
|
| 404 |
+
Returns
|
| 405 |
+
-------
|
| 406 |
+
NetworkX Graph of type create_using
|
| 407 |
+
|
| 408 |
+
Examples
|
| 409 |
+
--------
|
| 410 |
+
Many well-known graph families are subfamilies of the circulant graphs;
|
| 411 |
+
for example, to create the cycle graph on n points, we connect every
|
| 412 |
+
node to nodes on either side (with offset plus or minus one). For n = 10,
|
| 413 |
+
|
| 414 |
+
>>> G = nx.circulant_graph(10, [1])
|
| 415 |
+
>>> edges = [
|
| 416 |
+
... (0, 9),
|
| 417 |
+
... (0, 1),
|
| 418 |
+
... (1, 2),
|
| 419 |
+
... (2, 3),
|
| 420 |
+
... (3, 4),
|
| 421 |
+
... (4, 5),
|
| 422 |
+
... (5, 6),
|
| 423 |
+
... (6, 7),
|
| 424 |
+
... (7, 8),
|
| 425 |
+
... (8, 9),
|
| 426 |
+
... ]
|
| 427 |
+
>>> sorted(edges) == sorted(G.edges())
|
| 428 |
+
True
|
| 429 |
+
|
| 430 |
+
Similarly, we can create the complete graph
|
| 431 |
+
on 5 points with the set of offsets [1, 2]:
|
| 432 |
+
|
| 433 |
+
>>> G = nx.circulant_graph(5, [1, 2])
|
| 434 |
+
>>> edges = [
|
| 435 |
+
... (0, 1),
|
| 436 |
+
... (0, 2),
|
| 437 |
+
... (0, 3),
|
| 438 |
+
... (0, 4),
|
| 439 |
+
... (1, 2),
|
| 440 |
+
... (1, 3),
|
| 441 |
+
... (1, 4),
|
| 442 |
+
... (2, 3),
|
| 443 |
+
... (2, 4),
|
| 444 |
+
... (3, 4),
|
| 445 |
+
... ]
|
| 446 |
+
>>> sorted(edges) == sorted(G.edges())
|
| 447 |
+
True
|
| 448 |
+
|
| 449 |
+
"""
|
| 450 |
+
G = empty_graph(n, create_using)
|
| 451 |
+
for i in range(n):
|
| 452 |
+
for j in offsets:
|
| 453 |
+
G.add_edge(i, (i - j) % n)
|
| 454 |
+
G.add_edge(i, (i + j) % n)
|
| 455 |
+
return G
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 459 |
+
@nodes_or_number(0)
|
| 460 |
+
def cycle_graph(n, create_using=None):
|
| 461 |
+
"""Returns the cycle graph $C_n$ of cyclically connected nodes.
|
| 462 |
+
|
| 463 |
+
$C_n$ is a path with its two end-nodes connected.
|
| 464 |
+
|
| 465 |
+
.. plot::
|
| 466 |
+
|
| 467 |
+
>>> nx.draw(nx.cycle_graph(5))
|
| 468 |
+
|
| 469 |
+
Parameters
|
| 470 |
+
----------
|
| 471 |
+
n : int or iterable container of nodes
|
| 472 |
+
If n is an integer, nodes are from `range(n)`.
|
| 473 |
+
If n is a container of nodes, those nodes appear in the graph.
|
| 474 |
+
Warning: n is not checked for duplicates and if present the
|
| 475 |
+
resulting graph may not be as desired. Make sure you have no duplicates.
|
| 476 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 477 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 478 |
+
|
| 479 |
+
Notes
|
| 480 |
+
-----
|
| 481 |
+
If create_using is directed, the direction is in increasing order.
|
| 482 |
+
|
| 483 |
+
"""
|
| 484 |
+
_, nodes = n
|
| 485 |
+
G = empty_graph(nodes, create_using)
|
| 486 |
+
G.add_edges_from(pairwise(nodes, cyclic=True))
|
| 487 |
+
return G
|
| 488 |
+
|
| 489 |
+
|
| 490 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 491 |
+
def dorogovtsev_goltsev_mendes_graph(n, create_using=None):
|
| 492 |
+
"""Returns the hierarchically constructed Dorogovtsev--Goltsev--Mendes graph.
|
| 493 |
+
|
| 494 |
+
The Dorogovtsev--Goltsev--Mendes [1]_ procedure deterministically produces a
|
| 495 |
+
scale-free graph with ``3/2 * (3**(n-1) + 1)`` nodes
|
| 496 |
+
and ``3**n`` edges for a given `n`.
|
| 497 |
+
|
| 498 |
+
Note that `n` denotes the number of times the state transition is applied,
|
| 499 |
+
starting from the base graph with ``n = 0`` (no transitions), as in [2]_.
|
| 500 |
+
This is different from the parameter ``t = n - 1`` in [1]_.
|
| 501 |
+
|
| 502 |
+
.. plot::
|
| 503 |
+
|
| 504 |
+
>>> nx.draw(nx.dorogovtsev_goltsev_mendes_graph(3))
|
| 505 |
+
|
| 506 |
+
Parameters
|
| 507 |
+
----------
|
| 508 |
+
n : integer
|
| 509 |
+
The generation number.
|
| 510 |
+
|
| 511 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 512 |
+
Graph type to create. Directed graphs and multigraphs are not supported.
|
| 513 |
+
|
| 514 |
+
Returns
|
| 515 |
+
-------
|
| 516 |
+
G : NetworkX `Graph`
|
| 517 |
+
|
| 518 |
+
Raises
|
| 519 |
+
------
|
| 520 |
+
NetworkXError
|
| 521 |
+
If `n` is less than zero.
|
| 522 |
+
|
| 523 |
+
If `create_using` is a directed graph or multigraph.
|
| 524 |
+
|
| 525 |
+
Examples
|
| 526 |
+
--------
|
| 527 |
+
>>> G = nx.dorogovtsev_goltsev_mendes_graph(3)
|
| 528 |
+
>>> G.number_of_nodes()
|
| 529 |
+
15
|
| 530 |
+
>>> G.number_of_edges()
|
| 531 |
+
27
|
| 532 |
+
>>> nx.is_planar(G)
|
| 533 |
+
True
|
| 534 |
+
|
| 535 |
+
References
|
| 536 |
+
----------
|
| 537 |
+
.. [1] S. N. Dorogovtsev, A. V. Goltsev and J. F. F. Mendes,
|
| 538 |
+
"Pseudofractal scale-free web", Physical Review E 65, 066122, 2002.
|
| 539 |
+
https://arxiv.org/pdf/cond-mat/0112143.pdf
|
| 540 |
+
.. [2] Weisstein, Eric W. "Dorogovtsev--Goltsev--Mendes Graph".
|
| 541 |
+
From MathWorld--A Wolfram Web Resource.
|
| 542 |
+
https://mathworld.wolfram.com/Dorogovtsev-Goltsev-MendesGraph.html
|
| 543 |
+
"""
|
| 544 |
+
if n < 0:
|
| 545 |
+
raise NetworkXError("n must be greater than or equal to 0")
|
| 546 |
+
|
| 547 |
+
G = empty_graph(0, create_using)
|
| 548 |
+
if G.is_directed():
|
| 549 |
+
raise NetworkXError("directed graph not supported")
|
| 550 |
+
if G.is_multigraph():
|
| 551 |
+
raise NetworkXError("multigraph not supported")
|
| 552 |
+
|
| 553 |
+
G.add_edge(0, 1)
|
| 554 |
+
new_node = 2 # next node to be added
|
| 555 |
+
for _ in range(n): # iterate over number of generations.
|
| 556 |
+
new_edges = []
|
| 557 |
+
for u, v in G.edges():
|
| 558 |
+
new_edges.append((u, new_node))
|
| 559 |
+
new_edges.append((v, new_node))
|
| 560 |
+
new_node += 1
|
| 561 |
+
|
| 562 |
+
G.add_edges_from(new_edges)
|
| 563 |
+
return G
|
| 564 |
+
|
| 565 |
+
|
| 566 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 567 |
+
@nodes_or_number(0)
|
| 568 |
+
def empty_graph(n=0, create_using=None, default=Graph):
|
| 569 |
+
"""Returns the empty graph with n nodes and zero edges.
|
| 570 |
+
|
| 571 |
+
.. plot::
|
| 572 |
+
|
| 573 |
+
>>> nx.draw(nx.empty_graph(5))
|
| 574 |
+
|
| 575 |
+
Parameters
|
| 576 |
+
----------
|
| 577 |
+
n : int or iterable container of nodes (default = 0)
|
| 578 |
+
If n is an integer, nodes are from `range(n)`.
|
| 579 |
+
If n is a container of nodes, those nodes appear in the graph.
|
| 580 |
+
create_using : Graph Instance, Constructor or None
|
| 581 |
+
Indicator of type of graph to return.
|
| 582 |
+
If a Graph-type instance, then clear and use it.
|
| 583 |
+
If None, use the `default` constructor.
|
| 584 |
+
If a constructor, call it to create an empty graph.
|
| 585 |
+
default : Graph constructor (optional, default = nx.Graph)
|
| 586 |
+
The constructor to use if create_using is None.
|
| 587 |
+
If None, then nx.Graph is used.
|
| 588 |
+
This is used when passing an unknown `create_using` value
|
| 589 |
+
through your home-grown function to `empty_graph` and
|
| 590 |
+
you want a default constructor other than nx.Graph.
|
| 591 |
+
|
| 592 |
+
Examples
|
| 593 |
+
--------
|
| 594 |
+
>>> G = nx.empty_graph(10)
|
| 595 |
+
>>> G.number_of_nodes()
|
| 596 |
+
10
|
| 597 |
+
>>> G.number_of_edges()
|
| 598 |
+
0
|
| 599 |
+
>>> G = nx.empty_graph("ABC")
|
| 600 |
+
>>> G.number_of_nodes()
|
| 601 |
+
3
|
| 602 |
+
>>> sorted(G)
|
| 603 |
+
['A', 'B', 'C']
|
| 604 |
+
|
| 605 |
+
Notes
|
| 606 |
+
-----
|
| 607 |
+
The variable create_using should be a Graph Constructor or a
|
| 608 |
+
"graph"-like object. Constructors, e.g. `nx.Graph` or `nx.MultiGraph`
|
| 609 |
+
will be used to create the returned graph. "graph"-like objects
|
| 610 |
+
will be cleared (nodes and edges will be removed) and refitted as
|
| 611 |
+
an empty "graph" with nodes specified in n. This capability
|
| 612 |
+
is useful for specifying the class-nature of the resulting empty
|
| 613 |
+
"graph" (i.e. Graph, DiGraph, MyWeirdGraphClass, etc.).
|
| 614 |
+
|
| 615 |
+
The variable create_using has three main uses:
|
| 616 |
+
Firstly, the variable create_using can be used to create an
|
| 617 |
+
empty digraph, multigraph, etc. For example,
|
| 618 |
+
|
| 619 |
+
>>> n = 10
|
| 620 |
+
>>> G = nx.empty_graph(n, create_using=nx.DiGraph)
|
| 621 |
+
|
| 622 |
+
will create an empty digraph on n nodes.
|
| 623 |
+
|
| 624 |
+
Secondly, one can pass an existing graph (digraph, multigraph,
|
| 625 |
+
etc.) via create_using. For example, if G is an existing graph
|
| 626 |
+
(resp. digraph, multigraph, etc.), then empty_graph(n, create_using=G)
|
| 627 |
+
will empty G (i.e. delete all nodes and edges using G.clear())
|
| 628 |
+
and then add n nodes and zero edges, and return the modified graph.
|
| 629 |
+
|
| 630 |
+
Thirdly, when constructing your home-grown graph creation function
|
| 631 |
+
you can use empty_graph to construct the graph by passing a user
|
| 632 |
+
defined create_using to empty_graph. In this case, if you want the
|
| 633 |
+
default constructor to be other than nx.Graph, specify `default`.
|
| 634 |
+
|
| 635 |
+
>>> def mygraph(n, create_using=None):
|
| 636 |
+
... G = nx.empty_graph(n, create_using, nx.MultiGraph)
|
| 637 |
+
... G.add_edges_from([(0, 1), (0, 1)])
|
| 638 |
+
... return G
|
| 639 |
+
>>> G = mygraph(3)
|
| 640 |
+
>>> G.is_multigraph()
|
| 641 |
+
True
|
| 642 |
+
>>> G = mygraph(3, nx.Graph)
|
| 643 |
+
>>> G.is_multigraph()
|
| 644 |
+
False
|
| 645 |
+
|
| 646 |
+
See also create_empty_copy(G).
|
| 647 |
+
|
| 648 |
+
"""
|
| 649 |
+
if create_using is None:
|
| 650 |
+
G = default()
|
| 651 |
+
elif isinstance(create_using, type):
|
| 652 |
+
G = create_using()
|
| 653 |
+
elif not hasattr(create_using, "adj"):
|
| 654 |
+
raise TypeError("create_using is not a valid NetworkX graph type or instance")
|
| 655 |
+
else:
|
| 656 |
+
# create_using is a NetworkX style Graph
|
| 657 |
+
create_using.clear()
|
| 658 |
+
G = create_using
|
| 659 |
+
|
| 660 |
+
_, nodes = n
|
| 661 |
+
G.add_nodes_from(nodes)
|
| 662 |
+
return G
|
| 663 |
+
|
| 664 |
+
|
| 665 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 666 |
+
def ladder_graph(n, create_using=None):
|
| 667 |
+
"""Returns the Ladder graph of length n.
|
| 668 |
+
|
| 669 |
+
This is two paths of n nodes, with
|
| 670 |
+
each pair connected by a single edge.
|
| 671 |
+
|
| 672 |
+
Node labels are the integers 0 to 2*n - 1.
|
| 673 |
+
|
| 674 |
+
.. plot::
|
| 675 |
+
|
| 676 |
+
>>> nx.draw(nx.ladder_graph(5))
|
| 677 |
+
|
| 678 |
+
"""
|
| 679 |
+
G = empty_graph(2 * n, create_using)
|
| 680 |
+
if G.is_directed():
|
| 681 |
+
raise NetworkXError("Directed Graph not supported")
|
| 682 |
+
G.add_edges_from(pairwise(range(n)))
|
| 683 |
+
G.add_edges_from(pairwise(range(n, 2 * n)))
|
| 684 |
+
G.add_edges_from((v, v + n) for v in range(n))
|
| 685 |
+
return G
|
| 686 |
+
|
| 687 |
+
|
| 688 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 689 |
+
@nodes_or_number([0, 1])
|
| 690 |
+
def lollipop_graph(m, n, create_using=None):
|
| 691 |
+
"""Returns the Lollipop Graph; ``K_m`` connected to ``P_n``.
|
| 692 |
+
|
| 693 |
+
This is the Barbell Graph without the right barbell.
|
| 694 |
+
|
| 695 |
+
.. plot::
|
| 696 |
+
|
| 697 |
+
>>> nx.draw(nx.lollipop_graph(3, 4))
|
| 698 |
+
|
| 699 |
+
Parameters
|
| 700 |
+
----------
|
| 701 |
+
m, n : int or iterable container of nodes
|
| 702 |
+
If an integer, nodes are from ``range(m)`` and ``range(m, m+n)``.
|
| 703 |
+
If a container of nodes, those nodes appear in the graph.
|
| 704 |
+
Warning: `m` and `n` are not checked for duplicates and if present the
|
| 705 |
+
resulting graph may not be as desired. Make sure you have no duplicates.
|
| 706 |
+
|
| 707 |
+
The nodes for `m` appear in the complete graph $K_m$ and the nodes
|
| 708 |
+
for `n` appear in the path $P_n$
|
| 709 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 710 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 711 |
+
|
| 712 |
+
Returns
|
| 713 |
+
-------
|
| 714 |
+
Networkx graph
|
| 715 |
+
A complete graph with `m` nodes connected to a path of length `n`.
|
| 716 |
+
|
| 717 |
+
Notes
|
| 718 |
+
-----
|
| 719 |
+
The 2 subgraphs are joined via an edge ``(m-1, m)``.
|
| 720 |
+
If ``n=0``, this is merely a complete graph.
|
| 721 |
+
|
| 722 |
+
(This graph is an extremal example in David Aldous and Jim
|
| 723 |
+
Fill's etext on Random Walks on Graphs.)
|
| 724 |
+
|
| 725 |
+
"""
|
| 726 |
+
m, m_nodes = m
|
| 727 |
+
M = len(m_nodes)
|
| 728 |
+
if M < 2:
|
| 729 |
+
raise NetworkXError("Invalid description: m should indicate at least 2 nodes")
|
| 730 |
+
|
| 731 |
+
n, n_nodes = n
|
| 732 |
+
if isinstance(m, numbers.Integral) and isinstance(n, numbers.Integral):
|
| 733 |
+
n_nodes = list(range(M, M + n))
|
| 734 |
+
N = len(n_nodes)
|
| 735 |
+
|
| 736 |
+
# the ball
|
| 737 |
+
G = complete_graph(m_nodes, create_using)
|
| 738 |
+
if G.is_directed():
|
| 739 |
+
raise NetworkXError("Directed Graph not supported")
|
| 740 |
+
|
| 741 |
+
# the stick
|
| 742 |
+
G.add_nodes_from(n_nodes)
|
| 743 |
+
if N > 1:
|
| 744 |
+
G.add_edges_from(pairwise(n_nodes))
|
| 745 |
+
|
| 746 |
+
if len(G) != M + N:
|
| 747 |
+
raise NetworkXError("Nodes must be distinct in containers m and n")
|
| 748 |
+
|
| 749 |
+
# connect ball to stick
|
| 750 |
+
if M > 0 and N > 0:
|
| 751 |
+
G.add_edge(m_nodes[-1], n_nodes[0])
|
| 752 |
+
return G
|
| 753 |
+
|
| 754 |
+
|
| 755 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 756 |
+
def null_graph(create_using=None):
|
| 757 |
+
"""Returns the Null graph with no nodes or edges.
|
| 758 |
+
|
| 759 |
+
See empty_graph for the use of create_using.
|
| 760 |
+
|
| 761 |
+
"""
|
| 762 |
+
G = empty_graph(0, create_using)
|
| 763 |
+
return G
|
| 764 |
+
|
| 765 |
+
|
| 766 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 767 |
+
@nodes_or_number(0)
|
| 768 |
+
def path_graph(n, create_using=None):
|
| 769 |
+
"""Returns the Path graph `P_n` of linearly connected nodes.
|
| 770 |
+
|
| 771 |
+
.. plot::
|
| 772 |
+
|
| 773 |
+
>>> nx.draw(nx.path_graph(5))
|
| 774 |
+
|
| 775 |
+
Parameters
|
| 776 |
+
----------
|
| 777 |
+
n : int or iterable
|
| 778 |
+
If an integer, nodes are 0 to n - 1.
|
| 779 |
+
If an iterable of nodes, in the order they appear in the path.
|
| 780 |
+
Warning: n is not checked for duplicates and if present the
|
| 781 |
+
resulting graph may not be as desired. Make sure you have no duplicates.
|
| 782 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 783 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 784 |
+
|
| 785 |
+
"""
|
| 786 |
+
_, nodes = n
|
| 787 |
+
G = empty_graph(nodes, create_using)
|
| 788 |
+
G.add_edges_from(pairwise(nodes))
|
| 789 |
+
return G
|
| 790 |
+
|
| 791 |
+
|
| 792 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 793 |
+
@nodes_or_number(0)
|
| 794 |
+
def star_graph(n, create_using=None):
|
| 795 |
+
"""Return the star graph
|
| 796 |
+
|
| 797 |
+
The star graph consists of one center node connected to n outer nodes.
|
| 798 |
+
|
| 799 |
+
.. plot::
|
| 800 |
+
|
| 801 |
+
>>> nx.draw(nx.star_graph(6))
|
| 802 |
+
|
| 803 |
+
Parameters
|
| 804 |
+
----------
|
| 805 |
+
n : int or iterable
|
| 806 |
+
If an integer, node labels are 0 to n with center 0.
|
| 807 |
+
If an iterable of nodes, the center is the first.
|
| 808 |
+
Warning: n is not checked for duplicates and if present the
|
| 809 |
+
resulting graph may not be as desired. Make sure you have no duplicates.
|
| 810 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 811 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 812 |
+
|
| 813 |
+
Notes
|
| 814 |
+
-----
|
| 815 |
+
The graph has n+1 nodes for integer n.
|
| 816 |
+
So star_graph(3) is the same as star_graph(range(4)).
|
| 817 |
+
"""
|
| 818 |
+
n, nodes = n
|
| 819 |
+
if isinstance(n, numbers.Integral):
|
| 820 |
+
nodes.append(int(n)) # there should be n+1 nodes
|
| 821 |
+
G = empty_graph(nodes, create_using)
|
| 822 |
+
if G.is_directed():
|
| 823 |
+
raise NetworkXError("Directed Graph not supported")
|
| 824 |
+
|
| 825 |
+
if len(nodes) > 1:
|
| 826 |
+
hub, *spokes = nodes
|
| 827 |
+
G.add_edges_from((hub, node) for node in spokes)
|
| 828 |
+
return G
|
| 829 |
+
|
| 830 |
+
|
| 831 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 832 |
+
@nodes_or_number([0, 1])
|
| 833 |
+
def tadpole_graph(m, n, create_using=None):
|
| 834 |
+
"""Returns the (m,n)-tadpole graph; ``C_m`` connected to ``P_n``.
|
| 835 |
+
|
| 836 |
+
This graph on m+n nodes connects a cycle of size `m` to a path of length `n`.
|
| 837 |
+
It looks like a tadpole. It is also called a kite graph or a dragon graph.
|
| 838 |
+
|
| 839 |
+
.. plot::
|
| 840 |
+
|
| 841 |
+
>>> nx.draw(nx.tadpole_graph(3, 5))
|
| 842 |
+
|
| 843 |
+
Parameters
|
| 844 |
+
----------
|
| 845 |
+
m, n : int or iterable container of nodes
|
| 846 |
+
If an integer, nodes are from ``range(m)`` and ``range(m,m+n)``.
|
| 847 |
+
If a container of nodes, those nodes appear in the graph.
|
| 848 |
+
Warning: `m` and `n` are not checked for duplicates and if present the
|
| 849 |
+
resulting graph may not be as desired.
|
| 850 |
+
|
| 851 |
+
The nodes for `m` appear in the cycle graph $C_m$ and the nodes
|
| 852 |
+
for `n` appear in the path $P_n$.
|
| 853 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 854 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 855 |
+
|
| 856 |
+
Returns
|
| 857 |
+
-------
|
| 858 |
+
Networkx graph
|
| 859 |
+
A cycle of size `m` connected to a path of length `n`.
|
| 860 |
+
|
| 861 |
+
Raises
|
| 862 |
+
------
|
| 863 |
+
NetworkXError
|
| 864 |
+
If ``m < 2``. The tadpole graph is undefined for ``m<2``.
|
| 865 |
+
|
| 866 |
+
Notes
|
| 867 |
+
-----
|
| 868 |
+
The 2 subgraphs are joined via an edge ``(m-1, m)``.
|
| 869 |
+
If ``n=0``, this is a cycle graph.
|
| 870 |
+
`m` and/or `n` can be a container of nodes instead of an integer.
|
| 871 |
+
|
| 872 |
+
"""
|
| 873 |
+
m, m_nodes = m
|
| 874 |
+
M = len(m_nodes)
|
| 875 |
+
if M < 2:
|
| 876 |
+
raise NetworkXError("Invalid description: m should indicate at least 2 nodes")
|
| 877 |
+
|
| 878 |
+
n, n_nodes = n
|
| 879 |
+
if isinstance(m, numbers.Integral) and isinstance(n, numbers.Integral):
|
| 880 |
+
n_nodes = list(range(M, M + n))
|
| 881 |
+
|
| 882 |
+
# the circle
|
| 883 |
+
G = cycle_graph(m_nodes, create_using)
|
| 884 |
+
if G.is_directed():
|
| 885 |
+
raise NetworkXError("Directed Graph not supported")
|
| 886 |
+
|
| 887 |
+
# the stick
|
| 888 |
+
nx.add_path(G, [m_nodes[-1]] + list(n_nodes))
|
| 889 |
+
|
| 890 |
+
return G
|
| 891 |
+
|
| 892 |
+
|
| 893 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 894 |
+
def trivial_graph(create_using=None):
|
| 895 |
+
"""Return the Trivial graph with one node (with label 0) and no edges.
|
| 896 |
+
|
| 897 |
+
.. plot::
|
| 898 |
+
|
| 899 |
+
>>> nx.draw(nx.trivial_graph(), with_labels=True)
|
| 900 |
+
|
| 901 |
+
"""
|
| 902 |
+
G = empty_graph(1, create_using)
|
| 903 |
+
return G
|
| 904 |
+
|
| 905 |
+
|
| 906 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 907 |
+
def turan_graph(n, r):
|
| 908 |
+
r"""Return the Turan Graph
|
| 909 |
+
|
| 910 |
+
The Turan Graph is a complete multipartite graph on $n$ nodes
|
| 911 |
+
with $r$ disjoint subsets. That is, edges connect each node to
|
| 912 |
+
every node not in its subset.
|
| 913 |
+
|
| 914 |
+
Given $n$ and $r$, we create a complete multipartite graph with
|
| 915 |
+
$r-(n \mod r)$ partitions of size $n/r$, rounded down, and
|
| 916 |
+
$n \mod r$ partitions of size $n/r+1$, rounded down.
|
| 917 |
+
|
| 918 |
+
.. plot::
|
| 919 |
+
|
| 920 |
+
>>> nx.draw(nx.turan_graph(6, 2))
|
| 921 |
+
|
| 922 |
+
Parameters
|
| 923 |
+
----------
|
| 924 |
+
n : int
|
| 925 |
+
The number of nodes.
|
| 926 |
+
r : int
|
| 927 |
+
The number of partitions.
|
| 928 |
+
Must be less than or equal to n.
|
| 929 |
+
|
| 930 |
+
Notes
|
| 931 |
+
-----
|
| 932 |
+
Must satisfy $1 <= r <= n$.
|
| 933 |
+
The graph has $(r-1)(n^2)/(2r)$ edges, rounded down.
|
| 934 |
+
"""
|
| 935 |
+
|
| 936 |
+
if not 1 <= r <= n:
|
| 937 |
+
raise NetworkXError("Must satisfy 1 <= r <= n")
|
| 938 |
+
|
| 939 |
+
partitions = [n // r] * (r - (n % r)) + [n // r + 1] * (n % r)
|
| 940 |
+
G = complete_multipartite_graph(*partitions)
|
| 941 |
+
return G
|
| 942 |
+
|
| 943 |
+
|
| 944 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 945 |
+
@nodes_or_number(0)
|
| 946 |
+
def wheel_graph(n, create_using=None):
|
| 947 |
+
"""Return the wheel graph
|
| 948 |
+
|
| 949 |
+
The wheel graph consists of a hub node connected to a cycle of (n-1) nodes.
|
| 950 |
+
|
| 951 |
+
.. plot::
|
| 952 |
+
|
| 953 |
+
>>> nx.draw(nx.wheel_graph(5))
|
| 954 |
+
|
| 955 |
+
Parameters
|
| 956 |
+
----------
|
| 957 |
+
n : int or iterable
|
| 958 |
+
If an integer, node labels are 0 to n with center 0.
|
| 959 |
+
If an iterable of nodes, the center is the first.
|
| 960 |
+
Warning: n is not checked for duplicates and if present the
|
| 961 |
+
resulting graph may not be as desired. Make sure you have no duplicates.
|
| 962 |
+
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
| 963 |
+
Graph type to create. If graph instance, then cleared before populated.
|
| 964 |
+
|
| 965 |
+
Node labels are the integers 0 to n - 1.
|
| 966 |
+
"""
|
| 967 |
+
_, nodes = n
|
| 968 |
+
G = empty_graph(nodes, create_using)
|
| 969 |
+
if G.is_directed():
|
| 970 |
+
raise NetworkXError("Directed Graph not supported")
|
| 971 |
+
|
| 972 |
+
if len(nodes) > 1:
|
| 973 |
+
hub, *rim = nodes
|
| 974 |
+
G.add_edges_from((hub, node) for node in rim)
|
| 975 |
+
if len(rim) > 1:
|
| 976 |
+
G.add_edges_from(pairwise(rim, cyclic=True))
|
| 977 |
+
return G
|
| 978 |
+
|
| 979 |
+
|
| 980 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 981 |
+
def complete_multipartite_graph(*subset_sizes):
|
| 982 |
+
"""Returns the complete multipartite graph with the specified subset sizes.
|
| 983 |
+
|
| 984 |
+
.. plot::
|
| 985 |
+
|
| 986 |
+
>>> nx.draw(nx.complete_multipartite_graph(1, 2, 3))
|
| 987 |
+
|
| 988 |
+
Parameters
|
| 989 |
+
----------
|
| 990 |
+
subset_sizes : tuple of integers or tuple of node iterables
|
| 991 |
+
The arguments can either all be integer number of nodes or they
|
| 992 |
+
can all be iterables of nodes. If integers, they represent the
|
| 993 |
+
number of nodes in each subset of the multipartite graph.
|
| 994 |
+
If iterables, each is used to create the nodes for that subset.
|
| 995 |
+
The length of subset_sizes is the number of subsets.
|
| 996 |
+
|
| 997 |
+
Returns
|
| 998 |
+
-------
|
| 999 |
+
G : NetworkX Graph
|
| 1000 |
+
Returns the complete multipartite graph with the specified subsets.
|
| 1001 |
+
|
| 1002 |
+
For each node, the node attribute 'subset' is an integer
|
| 1003 |
+
indicating which subset contains the node.
|
| 1004 |
+
|
| 1005 |
+
Examples
|
| 1006 |
+
--------
|
| 1007 |
+
Creating a complete tripartite graph, with subsets of one, two, and three
|
| 1008 |
+
nodes, respectively.
|
| 1009 |
+
|
| 1010 |
+
>>> G = nx.complete_multipartite_graph(1, 2, 3)
|
| 1011 |
+
>>> [G.nodes[u]["subset"] for u in G]
|
| 1012 |
+
[0, 1, 1, 2, 2, 2]
|
| 1013 |
+
>>> list(G.edges(0))
|
| 1014 |
+
[(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]
|
| 1015 |
+
>>> list(G.edges(2))
|
| 1016 |
+
[(2, 0), (2, 3), (2, 4), (2, 5)]
|
| 1017 |
+
>>> list(G.edges(4))
|
| 1018 |
+
[(4, 0), (4, 1), (4, 2)]
|
| 1019 |
+
|
| 1020 |
+
>>> G = nx.complete_multipartite_graph("a", "bc", "def")
|
| 1021 |
+
>>> [G.nodes[u]["subset"] for u in sorted(G)]
|
| 1022 |
+
[0, 1, 1, 2, 2, 2]
|
| 1023 |
+
|
| 1024 |
+
Notes
|
| 1025 |
+
-----
|
| 1026 |
+
This function generalizes several other graph builder functions.
|
| 1027 |
+
|
| 1028 |
+
- If no subset sizes are given, this returns the null graph.
|
| 1029 |
+
- If a single subset size `n` is given, this returns the empty graph on
|
| 1030 |
+
`n` nodes.
|
| 1031 |
+
- If two subset sizes `m` and `n` are given, this returns the complete
|
| 1032 |
+
bipartite graph on `m + n` nodes.
|
| 1033 |
+
- If subset sizes `1` and `n` are given, this returns the star graph on
|
| 1034 |
+
`n + 1` nodes.
|
| 1035 |
+
|
| 1036 |
+
See also
|
| 1037 |
+
--------
|
| 1038 |
+
complete_bipartite_graph
|
| 1039 |
+
"""
|
| 1040 |
+
# The complete multipartite graph is an undirected simple graph.
|
| 1041 |
+
G = Graph()
|
| 1042 |
+
|
| 1043 |
+
if len(subset_sizes) == 0:
|
| 1044 |
+
return G
|
| 1045 |
+
|
| 1046 |
+
# set up subsets of nodes
|
| 1047 |
+
try:
|
| 1048 |
+
extents = pairwise(itertools.accumulate((0,) + subset_sizes))
|
| 1049 |
+
subsets = [range(start, end) for start, end in extents]
|
| 1050 |
+
except TypeError:
|
| 1051 |
+
subsets = subset_sizes
|
| 1052 |
+
else:
|
| 1053 |
+
if any(size < 0 for size in subset_sizes):
|
| 1054 |
+
raise NetworkXError(f"Negative number of nodes not valid: {subset_sizes}")
|
| 1055 |
+
|
| 1056 |
+
# add nodes with subset attribute
|
| 1057 |
+
# while checking that ints are not mixed with iterables
|
| 1058 |
+
try:
|
| 1059 |
+
for i, subset in enumerate(subsets):
|
| 1060 |
+
G.add_nodes_from(subset, subset=i)
|
| 1061 |
+
except TypeError as err:
|
| 1062 |
+
raise NetworkXError("Arguments must be all ints or all iterables") from err
|
| 1063 |
+
|
| 1064 |
+
# Across subsets, all nodes should be adjacent.
|
| 1065 |
+
# We can use itertools.combinations() because undirected.
|
| 1066 |
+
for subset1, subset2 in itertools.combinations(subsets, 2):
|
| 1067 |
+
G.add_edges_from(itertools.product(subset1, subset2))
|
| 1068 |
+
return G
|
.venv/lib/python3.11/site-packages/networkx/generators/cographs.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
r"""Generators for cographs
|
| 2 |
+
|
| 3 |
+
A cograph is a graph containing no path on four vertices.
|
| 4 |
+
Cographs or $P_4$-free graphs can be obtained from a single vertex
|
| 5 |
+
by disjoint union and complementation operations.
|
| 6 |
+
|
| 7 |
+
References
|
| 8 |
+
----------
|
| 9 |
+
.. [0] D.G. Corneil, H. Lerchs, L.Stewart Burlingham,
|
| 10 |
+
"Complement reducible graphs",
|
| 11 |
+
Discrete Applied Mathematics, Volume 3, Issue 3, 1981, Pages 163-174,
|
| 12 |
+
ISSN 0166-218X.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import networkx as nx
|
| 16 |
+
from networkx.utils import py_random_state
|
| 17 |
+
|
| 18 |
+
__all__ = ["random_cograph"]
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@py_random_state(1)
|
| 22 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 23 |
+
def random_cograph(n, seed=None):
|
| 24 |
+
r"""Returns a random cograph with $2 ^ n$ nodes.
|
| 25 |
+
|
| 26 |
+
A cograph is a graph containing no path on four vertices.
|
| 27 |
+
Cographs or $P_4$-free graphs can be obtained from a single vertex
|
| 28 |
+
by disjoint union and complementation operations.
|
| 29 |
+
|
| 30 |
+
This generator starts off from a single vertex and performs disjoint
|
| 31 |
+
union and full join operations on itself.
|
| 32 |
+
The decision on which operation will take place is random.
|
| 33 |
+
|
| 34 |
+
Parameters
|
| 35 |
+
----------
|
| 36 |
+
n : int
|
| 37 |
+
The order of the cograph.
|
| 38 |
+
seed : integer, random_state, or None (default)
|
| 39 |
+
Indicator of random number generation state.
|
| 40 |
+
See :ref:`Randomness<randomness>`.
|
| 41 |
+
|
| 42 |
+
Returns
|
| 43 |
+
-------
|
| 44 |
+
G : A random graph containing no path on four vertices.
|
| 45 |
+
|
| 46 |
+
See Also
|
| 47 |
+
--------
|
| 48 |
+
full_join
|
| 49 |
+
union
|
| 50 |
+
|
| 51 |
+
References
|
| 52 |
+
----------
|
| 53 |
+
.. [1] D.G. Corneil, H. Lerchs, L.Stewart Burlingham,
|
| 54 |
+
"Complement reducible graphs",
|
| 55 |
+
Discrete Applied Mathematics, Volume 3, Issue 3, 1981, Pages 163-174,
|
| 56 |
+
ISSN 0166-218X.
|
| 57 |
+
"""
|
| 58 |
+
R = nx.empty_graph(1)
|
| 59 |
+
|
| 60 |
+
for i in range(n):
|
| 61 |
+
RR = nx.relabel_nodes(R.copy(), lambda x: x + len(R))
|
| 62 |
+
|
| 63 |
+
if seed.randint(0, 1) == 0:
|
| 64 |
+
R = nx.full_join(R, RR)
|
| 65 |
+
else:
|
| 66 |
+
R = nx.disjoint_union(R, RR)
|
| 67 |
+
|
| 68 |
+
return R
|
.venv/lib/python3.11/site-packages/networkx/generators/community.py
ADDED
|
@@ -0,0 +1,1070 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Generators for classes of graphs used in studying social networks."""
|
| 2 |
+
|
| 3 |
+
import itertools
|
| 4 |
+
import math
|
| 5 |
+
|
| 6 |
+
import networkx as nx
|
| 7 |
+
from networkx.utils import py_random_state
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"caveman_graph",
|
| 11 |
+
"connected_caveman_graph",
|
| 12 |
+
"relaxed_caveman_graph",
|
| 13 |
+
"random_partition_graph",
|
| 14 |
+
"planted_partition_graph",
|
| 15 |
+
"gaussian_random_partition_graph",
|
| 16 |
+
"ring_of_cliques",
|
| 17 |
+
"windmill_graph",
|
| 18 |
+
"stochastic_block_model",
|
| 19 |
+
"LFR_benchmark_graph",
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 24 |
+
def caveman_graph(l, k):
|
| 25 |
+
"""Returns a caveman graph of `l` cliques of size `k`.
|
| 26 |
+
|
| 27 |
+
Parameters
|
| 28 |
+
----------
|
| 29 |
+
l : int
|
| 30 |
+
Number of cliques
|
| 31 |
+
k : int
|
| 32 |
+
Size of cliques
|
| 33 |
+
|
| 34 |
+
Returns
|
| 35 |
+
-------
|
| 36 |
+
G : NetworkX Graph
|
| 37 |
+
caveman graph
|
| 38 |
+
|
| 39 |
+
Notes
|
| 40 |
+
-----
|
| 41 |
+
This returns an undirected graph, it can be converted to a directed
|
| 42 |
+
graph using :func:`nx.to_directed`, or a multigraph using
|
| 43 |
+
``nx.MultiGraph(nx.caveman_graph(l, k))``. Only the undirected version is
|
| 44 |
+
described in [1]_ and it is unclear which of the directed
|
| 45 |
+
generalizations is most useful.
|
| 46 |
+
|
| 47 |
+
Examples
|
| 48 |
+
--------
|
| 49 |
+
>>> G = nx.caveman_graph(3, 3)
|
| 50 |
+
|
| 51 |
+
See also
|
| 52 |
+
--------
|
| 53 |
+
|
| 54 |
+
connected_caveman_graph
|
| 55 |
+
|
| 56 |
+
References
|
| 57 |
+
----------
|
| 58 |
+
.. [1] Watts, D. J. 'Networks, Dynamics, and the Small-World Phenomenon.'
|
| 59 |
+
Amer. J. Soc. 105, 493-527, 1999.
|
| 60 |
+
"""
|
| 61 |
+
# l disjoint cliques of size k
|
| 62 |
+
G = nx.empty_graph(l * k)
|
| 63 |
+
if k > 1:
|
| 64 |
+
for start in range(0, l * k, k):
|
| 65 |
+
edges = itertools.combinations(range(start, start + k), 2)
|
| 66 |
+
G.add_edges_from(edges)
|
| 67 |
+
return G
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 71 |
+
def connected_caveman_graph(l, k):
|
| 72 |
+
"""Returns a connected caveman graph of `l` cliques of size `k`.
|
| 73 |
+
|
| 74 |
+
The connected caveman graph is formed by creating `n` cliques of size
|
| 75 |
+
`k`, then a single edge in each clique is rewired to a node in an
|
| 76 |
+
adjacent clique.
|
| 77 |
+
|
| 78 |
+
Parameters
|
| 79 |
+
----------
|
| 80 |
+
l : int
|
| 81 |
+
number of cliques
|
| 82 |
+
k : int
|
| 83 |
+
size of cliques (k at least 2 or NetworkXError is raised)
|
| 84 |
+
|
| 85 |
+
Returns
|
| 86 |
+
-------
|
| 87 |
+
G : NetworkX Graph
|
| 88 |
+
connected caveman graph
|
| 89 |
+
|
| 90 |
+
Raises
|
| 91 |
+
------
|
| 92 |
+
NetworkXError
|
| 93 |
+
If the size of cliques `k` is smaller than 2.
|
| 94 |
+
|
| 95 |
+
Notes
|
| 96 |
+
-----
|
| 97 |
+
This returns an undirected graph, it can be converted to a directed
|
| 98 |
+
graph using :func:`nx.to_directed`, or a multigraph using
|
| 99 |
+
``nx.MultiGraph(nx.caveman_graph(l, k))``. Only the undirected version is
|
| 100 |
+
described in [1]_ and it is unclear which of the directed
|
| 101 |
+
generalizations is most useful.
|
| 102 |
+
|
| 103 |
+
Examples
|
| 104 |
+
--------
|
| 105 |
+
>>> G = nx.connected_caveman_graph(3, 3)
|
| 106 |
+
|
| 107 |
+
References
|
| 108 |
+
----------
|
| 109 |
+
.. [1] Watts, D. J. 'Networks, Dynamics, and the Small-World Phenomenon.'
|
| 110 |
+
Amer. J. Soc. 105, 493-527, 1999.
|
| 111 |
+
"""
|
| 112 |
+
if k < 2:
|
| 113 |
+
raise nx.NetworkXError(
|
| 114 |
+
"The size of cliques in a connected caveman graph must be at least 2."
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
G = nx.caveman_graph(l, k)
|
| 118 |
+
for start in range(0, l * k, k):
|
| 119 |
+
G.remove_edge(start, start + 1)
|
| 120 |
+
G.add_edge(start, (start - 1) % (l * k))
|
| 121 |
+
return G
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
@py_random_state(3)
|
| 125 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 126 |
+
def relaxed_caveman_graph(l, k, p, seed=None):
|
| 127 |
+
"""Returns a relaxed caveman graph.
|
| 128 |
+
|
| 129 |
+
A relaxed caveman graph starts with `l` cliques of size `k`. Edges are
|
| 130 |
+
then randomly rewired with probability `p` to link different cliques.
|
| 131 |
+
|
| 132 |
+
Parameters
|
| 133 |
+
----------
|
| 134 |
+
l : int
|
| 135 |
+
Number of groups
|
| 136 |
+
k : int
|
| 137 |
+
Size of cliques
|
| 138 |
+
p : float
|
| 139 |
+
Probability of rewiring each edge.
|
| 140 |
+
seed : integer, random_state, or None (default)
|
| 141 |
+
Indicator of random number generation state.
|
| 142 |
+
See :ref:`Randomness<randomness>`.
|
| 143 |
+
|
| 144 |
+
Returns
|
| 145 |
+
-------
|
| 146 |
+
G : NetworkX Graph
|
| 147 |
+
Relaxed Caveman Graph
|
| 148 |
+
|
| 149 |
+
Raises
|
| 150 |
+
------
|
| 151 |
+
NetworkXError
|
| 152 |
+
If p is not in [0,1]
|
| 153 |
+
|
| 154 |
+
Examples
|
| 155 |
+
--------
|
| 156 |
+
>>> G = nx.relaxed_caveman_graph(2, 3, 0.1, seed=42)
|
| 157 |
+
|
| 158 |
+
References
|
| 159 |
+
----------
|
| 160 |
+
.. [1] Santo Fortunato, Community Detection in Graphs,
|
| 161 |
+
Physics Reports Volume 486, Issues 3-5, February 2010, Pages 75-174.
|
| 162 |
+
https://arxiv.org/abs/0906.0612
|
| 163 |
+
"""
|
| 164 |
+
G = nx.caveman_graph(l, k)
|
| 165 |
+
nodes = list(G)
|
| 166 |
+
for u, v in G.edges():
|
| 167 |
+
if seed.random() < p: # rewire the edge
|
| 168 |
+
x = seed.choice(nodes)
|
| 169 |
+
if G.has_edge(u, x):
|
| 170 |
+
continue
|
| 171 |
+
G.remove_edge(u, v)
|
| 172 |
+
G.add_edge(u, x)
|
| 173 |
+
return G
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
@py_random_state(3)
|
| 177 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 178 |
+
def random_partition_graph(sizes, p_in, p_out, seed=None, directed=False):
|
| 179 |
+
"""Returns the random partition graph with a partition of sizes.
|
| 180 |
+
|
| 181 |
+
A partition graph is a graph of communities with sizes defined by
|
| 182 |
+
s in sizes. Nodes in the same group are connected with probability
|
| 183 |
+
p_in and nodes of different groups are connected with probability
|
| 184 |
+
p_out.
|
| 185 |
+
|
| 186 |
+
Parameters
|
| 187 |
+
----------
|
| 188 |
+
sizes : list of ints
|
| 189 |
+
Sizes of groups
|
| 190 |
+
p_in : float
|
| 191 |
+
probability of edges with in groups
|
| 192 |
+
p_out : float
|
| 193 |
+
probability of edges between groups
|
| 194 |
+
directed : boolean optional, default=False
|
| 195 |
+
Whether to create a directed graph
|
| 196 |
+
seed : integer, random_state, or None (default)
|
| 197 |
+
Indicator of random number generation state.
|
| 198 |
+
See :ref:`Randomness<randomness>`.
|
| 199 |
+
|
| 200 |
+
Returns
|
| 201 |
+
-------
|
| 202 |
+
G : NetworkX Graph or DiGraph
|
| 203 |
+
random partition graph of size sum(gs)
|
| 204 |
+
|
| 205 |
+
Raises
|
| 206 |
+
------
|
| 207 |
+
NetworkXError
|
| 208 |
+
If p_in or p_out is not in [0,1]
|
| 209 |
+
|
| 210 |
+
Examples
|
| 211 |
+
--------
|
| 212 |
+
>>> G = nx.random_partition_graph([10, 10, 10], 0.25, 0.01)
|
| 213 |
+
>>> len(G)
|
| 214 |
+
30
|
| 215 |
+
>>> partition = G.graph["partition"]
|
| 216 |
+
>>> len(partition)
|
| 217 |
+
3
|
| 218 |
+
|
| 219 |
+
Notes
|
| 220 |
+
-----
|
| 221 |
+
This is a generalization of the planted-l-partition described in
|
| 222 |
+
[1]_. It allows for the creation of groups of any size.
|
| 223 |
+
|
| 224 |
+
The partition is store as a graph attribute 'partition'.
|
| 225 |
+
|
| 226 |
+
References
|
| 227 |
+
----------
|
| 228 |
+
.. [1] Santo Fortunato 'Community Detection in Graphs' Physical Reports
|
| 229 |
+
Volume 486, Issue 3-5 p. 75-174. https://arxiv.org/abs/0906.0612
|
| 230 |
+
"""
|
| 231 |
+
# Use geometric method for O(n+m) complexity algorithm
|
| 232 |
+
# partition = nx.community_sets(nx.get_node_attributes(G, 'affiliation'))
|
| 233 |
+
if not 0.0 <= p_in <= 1.0:
|
| 234 |
+
raise nx.NetworkXError("p_in must be in [0,1]")
|
| 235 |
+
if not 0.0 <= p_out <= 1.0:
|
| 236 |
+
raise nx.NetworkXError("p_out must be in [0,1]")
|
| 237 |
+
|
| 238 |
+
# create connection matrix
|
| 239 |
+
num_blocks = len(sizes)
|
| 240 |
+
p = [[p_out for s in range(num_blocks)] for r in range(num_blocks)]
|
| 241 |
+
for r in range(num_blocks):
|
| 242 |
+
p[r][r] = p_in
|
| 243 |
+
|
| 244 |
+
return stochastic_block_model(
|
| 245 |
+
sizes,
|
| 246 |
+
p,
|
| 247 |
+
nodelist=None,
|
| 248 |
+
seed=seed,
|
| 249 |
+
directed=directed,
|
| 250 |
+
selfloops=False,
|
| 251 |
+
sparse=True,
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
@py_random_state(4)
|
| 256 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 257 |
+
def planted_partition_graph(l, k, p_in, p_out, seed=None, directed=False):
|
| 258 |
+
"""Returns the planted l-partition graph.
|
| 259 |
+
|
| 260 |
+
This model partitions a graph with n=l*k vertices in
|
| 261 |
+
l groups with k vertices each. Vertices of the same
|
| 262 |
+
group are linked with a probability p_in, and vertices
|
| 263 |
+
of different groups are linked with probability p_out.
|
| 264 |
+
|
| 265 |
+
Parameters
|
| 266 |
+
----------
|
| 267 |
+
l : int
|
| 268 |
+
Number of groups
|
| 269 |
+
k : int
|
| 270 |
+
Number of vertices in each group
|
| 271 |
+
p_in : float
|
| 272 |
+
probability of connecting vertices within a group
|
| 273 |
+
p_out : float
|
| 274 |
+
probability of connected vertices between groups
|
| 275 |
+
seed : integer, random_state, or None (default)
|
| 276 |
+
Indicator of random number generation state.
|
| 277 |
+
See :ref:`Randomness<randomness>`.
|
| 278 |
+
directed : bool,optional (default=False)
|
| 279 |
+
If True return a directed graph
|
| 280 |
+
|
| 281 |
+
Returns
|
| 282 |
+
-------
|
| 283 |
+
G : NetworkX Graph or DiGraph
|
| 284 |
+
planted l-partition graph
|
| 285 |
+
|
| 286 |
+
Raises
|
| 287 |
+
------
|
| 288 |
+
NetworkXError
|
| 289 |
+
If `p_in`, `p_out` are not in `[0, 1]`
|
| 290 |
+
|
| 291 |
+
Examples
|
| 292 |
+
--------
|
| 293 |
+
>>> G = nx.planted_partition_graph(4, 3, 0.5, 0.1, seed=42)
|
| 294 |
+
|
| 295 |
+
See Also
|
| 296 |
+
--------
|
| 297 |
+
random_partition_model
|
| 298 |
+
|
| 299 |
+
References
|
| 300 |
+
----------
|
| 301 |
+
.. [1] A. Condon, R.M. Karp, Algorithms for graph partitioning
|
| 302 |
+
on the planted partition model,
|
| 303 |
+
Random Struct. Algor. 18 (2001) 116-140.
|
| 304 |
+
|
| 305 |
+
.. [2] Santo Fortunato 'Community Detection in Graphs' Physical Reports
|
| 306 |
+
Volume 486, Issue 3-5 p. 75-174. https://arxiv.org/abs/0906.0612
|
| 307 |
+
"""
|
| 308 |
+
return random_partition_graph([k] * l, p_in, p_out, seed=seed, directed=directed)
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
@py_random_state(6)
|
| 312 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 313 |
+
def gaussian_random_partition_graph(n, s, v, p_in, p_out, directed=False, seed=None):
|
| 314 |
+
"""Generate a Gaussian random partition graph.
|
| 315 |
+
|
| 316 |
+
A Gaussian random partition graph is created by creating k partitions
|
| 317 |
+
each with a size drawn from a normal distribution with mean s and variance
|
| 318 |
+
s/v. Nodes are connected within clusters with probability p_in and
|
| 319 |
+
between clusters with probability p_out[1]
|
| 320 |
+
|
| 321 |
+
Parameters
|
| 322 |
+
----------
|
| 323 |
+
n : int
|
| 324 |
+
Number of nodes in the graph
|
| 325 |
+
s : float
|
| 326 |
+
Mean cluster size
|
| 327 |
+
v : float
|
| 328 |
+
Shape parameter. The variance of cluster size distribution is s/v.
|
| 329 |
+
p_in : float
|
| 330 |
+
Probability of intra cluster connection.
|
| 331 |
+
p_out : float
|
| 332 |
+
Probability of inter cluster connection.
|
| 333 |
+
directed : boolean, optional default=False
|
| 334 |
+
Whether to create a directed graph or not
|
| 335 |
+
seed : integer, random_state, or None (default)
|
| 336 |
+
Indicator of random number generation state.
|
| 337 |
+
See :ref:`Randomness<randomness>`.
|
| 338 |
+
|
| 339 |
+
Returns
|
| 340 |
+
-------
|
| 341 |
+
G : NetworkX Graph or DiGraph
|
| 342 |
+
gaussian random partition graph
|
| 343 |
+
|
| 344 |
+
Raises
|
| 345 |
+
------
|
| 346 |
+
NetworkXError
|
| 347 |
+
If s is > n
|
| 348 |
+
If p_in or p_out is not in [0,1]
|
| 349 |
+
|
| 350 |
+
Notes
|
| 351 |
+
-----
|
| 352 |
+
Note the number of partitions is dependent on s,v and n, and that the
|
| 353 |
+
last partition may be considerably smaller, as it is sized to simply
|
| 354 |
+
fill out the nodes [1]
|
| 355 |
+
|
| 356 |
+
See Also
|
| 357 |
+
--------
|
| 358 |
+
random_partition_graph
|
| 359 |
+
|
| 360 |
+
Examples
|
| 361 |
+
--------
|
| 362 |
+
>>> G = nx.gaussian_random_partition_graph(100, 10, 10, 0.25, 0.1)
|
| 363 |
+
>>> len(G)
|
| 364 |
+
100
|
| 365 |
+
|
| 366 |
+
References
|
| 367 |
+
----------
|
| 368 |
+
.. [1] Ulrik Brandes, Marco Gaertler, Dorothea Wagner,
|
| 369 |
+
Experiments on Graph Clustering Algorithms,
|
| 370 |
+
In the proceedings of the 11th Europ. Symp. Algorithms, 2003.
|
| 371 |
+
"""
|
| 372 |
+
if s > n:
|
| 373 |
+
raise nx.NetworkXError("s must be <= n")
|
| 374 |
+
assigned = 0
|
| 375 |
+
sizes = []
|
| 376 |
+
while True:
|
| 377 |
+
size = int(seed.gauss(s, s / v + 0.5))
|
| 378 |
+
if size < 1: # how to handle 0 or negative sizes?
|
| 379 |
+
continue
|
| 380 |
+
if assigned + size >= n:
|
| 381 |
+
sizes.append(n - assigned)
|
| 382 |
+
break
|
| 383 |
+
assigned += size
|
| 384 |
+
sizes.append(size)
|
| 385 |
+
return random_partition_graph(sizes, p_in, p_out, seed=seed, directed=directed)
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 389 |
+
def ring_of_cliques(num_cliques, clique_size):
|
| 390 |
+
"""Defines a "ring of cliques" graph.
|
| 391 |
+
|
| 392 |
+
A ring of cliques graph is consisting of cliques, connected through single
|
| 393 |
+
links. Each clique is a complete graph.
|
| 394 |
+
|
| 395 |
+
Parameters
|
| 396 |
+
----------
|
| 397 |
+
num_cliques : int
|
| 398 |
+
Number of cliques
|
| 399 |
+
clique_size : int
|
| 400 |
+
Size of cliques
|
| 401 |
+
|
| 402 |
+
Returns
|
| 403 |
+
-------
|
| 404 |
+
G : NetworkX Graph
|
| 405 |
+
ring of cliques graph
|
| 406 |
+
|
| 407 |
+
Raises
|
| 408 |
+
------
|
| 409 |
+
NetworkXError
|
| 410 |
+
If the number of cliques is lower than 2 or
|
| 411 |
+
if the size of cliques is smaller than 2.
|
| 412 |
+
|
| 413 |
+
Examples
|
| 414 |
+
--------
|
| 415 |
+
>>> G = nx.ring_of_cliques(8, 4)
|
| 416 |
+
|
| 417 |
+
See Also
|
| 418 |
+
--------
|
| 419 |
+
connected_caveman_graph
|
| 420 |
+
|
| 421 |
+
Notes
|
| 422 |
+
-----
|
| 423 |
+
The `connected_caveman_graph` graph removes a link from each clique to
|
| 424 |
+
connect it with the next clique. Instead, the `ring_of_cliques` graph
|
| 425 |
+
simply adds the link without removing any link from the cliques.
|
| 426 |
+
"""
|
| 427 |
+
if num_cliques < 2:
|
| 428 |
+
raise nx.NetworkXError("A ring of cliques must have at least two cliques")
|
| 429 |
+
if clique_size < 2:
|
| 430 |
+
raise nx.NetworkXError("The cliques must have at least two nodes")
|
| 431 |
+
|
| 432 |
+
G = nx.Graph()
|
| 433 |
+
for i in range(num_cliques):
|
| 434 |
+
edges = itertools.combinations(
|
| 435 |
+
range(i * clique_size, i * clique_size + clique_size), 2
|
| 436 |
+
)
|
| 437 |
+
G.add_edges_from(edges)
|
| 438 |
+
G.add_edge(
|
| 439 |
+
i * clique_size + 1, (i + 1) * clique_size % (num_cliques * clique_size)
|
| 440 |
+
)
|
| 441 |
+
return G
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 445 |
+
def windmill_graph(n, k):
|
| 446 |
+
"""Generate a windmill graph.
|
| 447 |
+
A windmill graph is a graph of `n` cliques each of size `k` that are all
|
| 448 |
+
joined at one node.
|
| 449 |
+
It can be thought of as taking a disjoint union of `n` cliques of size `k`,
|
| 450 |
+
selecting one point from each, and contracting all of the selected points.
|
| 451 |
+
Alternatively, one could generate `n` cliques of size `k-1` and one node
|
| 452 |
+
that is connected to all other nodes in the graph.
|
| 453 |
+
|
| 454 |
+
Parameters
|
| 455 |
+
----------
|
| 456 |
+
n : int
|
| 457 |
+
Number of cliques
|
| 458 |
+
k : int
|
| 459 |
+
Size of cliques
|
| 460 |
+
|
| 461 |
+
Returns
|
| 462 |
+
-------
|
| 463 |
+
G : NetworkX Graph
|
| 464 |
+
windmill graph with n cliques of size k
|
| 465 |
+
|
| 466 |
+
Raises
|
| 467 |
+
------
|
| 468 |
+
NetworkXError
|
| 469 |
+
If the number of cliques is less than two
|
| 470 |
+
If the size of the cliques are less than two
|
| 471 |
+
|
| 472 |
+
Examples
|
| 473 |
+
--------
|
| 474 |
+
>>> G = nx.windmill_graph(4, 5)
|
| 475 |
+
|
| 476 |
+
Notes
|
| 477 |
+
-----
|
| 478 |
+
The node labeled `0` will be the node connected to all other nodes.
|
| 479 |
+
Note that windmill graphs are usually denoted `Wd(k,n)`, so the parameters
|
| 480 |
+
are in the opposite order as the parameters of this method.
|
| 481 |
+
"""
|
| 482 |
+
if n < 2:
|
| 483 |
+
msg = "A windmill graph must have at least two cliques"
|
| 484 |
+
raise nx.NetworkXError(msg)
|
| 485 |
+
if k < 2:
|
| 486 |
+
raise nx.NetworkXError("The cliques must have at least two nodes")
|
| 487 |
+
|
| 488 |
+
G = nx.disjoint_union_all(
|
| 489 |
+
itertools.chain(
|
| 490 |
+
[nx.complete_graph(k)], (nx.complete_graph(k - 1) for _ in range(n - 1))
|
| 491 |
+
)
|
| 492 |
+
)
|
| 493 |
+
G.add_edges_from((0, i) for i in range(k, G.number_of_nodes()))
|
| 494 |
+
return G
|
| 495 |
+
|
| 496 |
+
|
| 497 |
+
@py_random_state(3)
|
| 498 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 499 |
+
def stochastic_block_model(
|
| 500 |
+
sizes, p, nodelist=None, seed=None, directed=False, selfloops=False, sparse=True
|
| 501 |
+
):
|
| 502 |
+
"""Returns a stochastic block model graph.
|
| 503 |
+
|
| 504 |
+
This model partitions the nodes in blocks of arbitrary sizes, and places
|
| 505 |
+
edges between pairs of nodes independently, with a probability that depends
|
| 506 |
+
on the blocks.
|
| 507 |
+
|
| 508 |
+
Parameters
|
| 509 |
+
----------
|
| 510 |
+
sizes : list of ints
|
| 511 |
+
Sizes of blocks
|
| 512 |
+
p : list of list of floats
|
| 513 |
+
Element (r,s) gives the density of edges going from the nodes
|
| 514 |
+
of group r to nodes of group s.
|
| 515 |
+
p must match the number of groups (len(sizes) == len(p)),
|
| 516 |
+
and it must be symmetric if the graph is undirected.
|
| 517 |
+
nodelist : list, optional
|
| 518 |
+
The block tags are assigned according to the node identifiers
|
| 519 |
+
in nodelist. If nodelist is None, then the ordering is the
|
| 520 |
+
range [0,sum(sizes)-1].
|
| 521 |
+
seed : integer, random_state, or None (default)
|
| 522 |
+
Indicator of random number generation state.
|
| 523 |
+
See :ref:`Randomness<randomness>`.
|
| 524 |
+
directed : boolean optional, default=False
|
| 525 |
+
Whether to create a directed graph or not.
|
| 526 |
+
selfloops : boolean optional, default=False
|
| 527 |
+
Whether to include self-loops or not.
|
| 528 |
+
sparse: boolean optional, default=True
|
| 529 |
+
Use the sparse heuristic to speed up the generator.
|
| 530 |
+
|
| 531 |
+
Returns
|
| 532 |
+
-------
|
| 533 |
+
g : NetworkX Graph or DiGraph
|
| 534 |
+
Stochastic block model graph of size sum(sizes)
|
| 535 |
+
|
| 536 |
+
Raises
|
| 537 |
+
------
|
| 538 |
+
NetworkXError
|
| 539 |
+
If probabilities are not in [0,1].
|
| 540 |
+
If the probability matrix is not square (directed case).
|
| 541 |
+
If the probability matrix is not symmetric (undirected case).
|
| 542 |
+
If the sizes list does not match nodelist or the probability matrix.
|
| 543 |
+
If nodelist contains duplicate.
|
| 544 |
+
|
| 545 |
+
Examples
|
| 546 |
+
--------
|
| 547 |
+
>>> sizes = [75, 75, 300]
|
| 548 |
+
>>> probs = [[0.25, 0.05, 0.02], [0.05, 0.35, 0.07], [0.02, 0.07, 0.40]]
|
| 549 |
+
>>> g = nx.stochastic_block_model(sizes, probs, seed=0)
|
| 550 |
+
>>> len(g)
|
| 551 |
+
450
|
| 552 |
+
>>> H = nx.quotient_graph(g, g.graph["partition"], relabel=True)
|
| 553 |
+
>>> for v in H.nodes(data=True):
|
| 554 |
+
... print(round(v[1]["density"], 3))
|
| 555 |
+
0.245
|
| 556 |
+
0.348
|
| 557 |
+
0.405
|
| 558 |
+
>>> for v in H.edges(data=True):
|
| 559 |
+
... print(round(1.0 * v[2]["weight"] / (sizes[v[0]] * sizes[v[1]]), 3))
|
| 560 |
+
0.051
|
| 561 |
+
0.022
|
| 562 |
+
0.07
|
| 563 |
+
|
| 564 |
+
See Also
|
| 565 |
+
--------
|
| 566 |
+
random_partition_graph
|
| 567 |
+
planted_partition_graph
|
| 568 |
+
gaussian_random_partition_graph
|
| 569 |
+
gnp_random_graph
|
| 570 |
+
|
| 571 |
+
References
|
| 572 |
+
----------
|
| 573 |
+
.. [1] Holland, P. W., Laskey, K. B., & Leinhardt, S.,
|
| 574 |
+
"Stochastic blockmodels: First steps",
|
| 575 |
+
Social networks, 5(2), 109-137, 1983.
|
| 576 |
+
"""
|
| 577 |
+
# Check if dimensions match
|
| 578 |
+
if len(sizes) != len(p):
|
| 579 |
+
raise nx.NetworkXException("'sizes' and 'p' do not match.")
|
| 580 |
+
# Check for probability symmetry (undirected) and shape (directed)
|
| 581 |
+
for row in p:
|
| 582 |
+
if len(p) != len(row):
|
| 583 |
+
raise nx.NetworkXException("'p' must be a square matrix.")
|
| 584 |
+
if not directed:
|
| 585 |
+
p_transpose = [list(i) for i in zip(*p)]
|
| 586 |
+
for i in zip(p, p_transpose):
|
| 587 |
+
for j in zip(i[0], i[1]):
|
| 588 |
+
if abs(j[0] - j[1]) > 1e-08:
|
| 589 |
+
raise nx.NetworkXException("'p' must be symmetric.")
|
| 590 |
+
# Check for probability range
|
| 591 |
+
for row in p:
|
| 592 |
+
for prob in row:
|
| 593 |
+
if prob < 0 or prob > 1:
|
| 594 |
+
raise nx.NetworkXException("Entries of 'p' not in [0,1].")
|
| 595 |
+
# Check for nodelist consistency
|
| 596 |
+
if nodelist is not None:
|
| 597 |
+
if len(nodelist) != sum(sizes):
|
| 598 |
+
raise nx.NetworkXException("'nodelist' and 'sizes' do not match.")
|
| 599 |
+
if len(nodelist) != len(set(nodelist)):
|
| 600 |
+
raise nx.NetworkXException("nodelist contains duplicate.")
|
| 601 |
+
else:
|
| 602 |
+
nodelist = range(sum(sizes))
|
| 603 |
+
|
| 604 |
+
# Setup the graph conditionally to the directed switch.
|
| 605 |
+
block_range = range(len(sizes))
|
| 606 |
+
if directed:
|
| 607 |
+
g = nx.DiGraph()
|
| 608 |
+
block_iter = itertools.product(block_range, block_range)
|
| 609 |
+
else:
|
| 610 |
+
g = nx.Graph()
|
| 611 |
+
block_iter = itertools.combinations_with_replacement(block_range, 2)
|
| 612 |
+
# Split nodelist in a partition (list of sets).
|
| 613 |
+
size_cumsum = [sum(sizes[0:x]) for x in range(len(sizes) + 1)]
|
| 614 |
+
g.graph["partition"] = [
|
| 615 |
+
set(nodelist[size_cumsum[x] : size_cumsum[x + 1]])
|
| 616 |
+
for x in range(len(size_cumsum) - 1)
|
| 617 |
+
]
|
| 618 |
+
# Setup nodes and graph name
|
| 619 |
+
for block_id, nodes in enumerate(g.graph["partition"]):
|
| 620 |
+
for node in nodes:
|
| 621 |
+
g.add_node(node, block=block_id)
|
| 622 |
+
|
| 623 |
+
g.name = "stochastic_block_model"
|
| 624 |
+
|
| 625 |
+
# Test for edge existence
|
| 626 |
+
parts = g.graph["partition"]
|
| 627 |
+
for i, j in block_iter:
|
| 628 |
+
if i == j:
|
| 629 |
+
if directed:
|
| 630 |
+
if selfloops:
|
| 631 |
+
edges = itertools.product(parts[i], parts[i])
|
| 632 |
+
else:
|
| 633 |
+
edges = itertools.permutations(parts[i], 2)
|
| 634 |
+
else:
|
| 635 |
+
edges = itertools.combinations(parts[i], 2)
|
| 636 |
+
if selfloops:
|
| 637 |
+
edges = itertools.chain(edges, zip(parts[i], parts[i]))
|
| 638 |
+
for e in edges:
|
| 639 |
+
if seed.random() < p[i][j]:
|
| 640 |
+
g.add_edge(*e)
|
| 641 |
+
else:
|
| 642 |
+
edges = itertools.product(parts[i], parts[j])
|
| 643 |
+
if sparse:
|
| 644 |
+
if p[i][j] == 1: # Test edges cases p_ij = 0 or 1
|
| 645 |
+
for e in edges:
|
| 646 |
+
g.add_edge(*e)
|
| 647 |
+
elif p[i][j] > 0:
|
| 648 |
+
while True:
|
| 649 |
+
try:
|
| 650 |
+
logrand = math.log(seed.random())
|
| 651 |
+
skip = math.floor(logrand / math.log(1 - p[i][j]))
|
| 652 |
+
# consume "skip" edges
|
| 653 |
+
next(itertools.islice(edges, skip, skip), None)
|
| 654 |
+
e = next(edges)
|
| 655 |
+
g.add_edge(*e) # __safe
|
| 656 |
+
except StopIteration:
|
| 657 |
+
break
|
| 658 |
+
else:
|
| 659 |
+
for e in edges:
|
| 660 |
+
if seed.random() < p[i][j]:
|
| 661 |
+
g.add_edge(*e) # __safe
|
| 662 |
+
return g
|
| 663 |
+
|
| 664 |
+
|
| 665 |
+
def _zipf_rv_below(gamma, xmin, threshold, seed):
|
| 666 |
+
"""Returns a random value chosen from the bounded Zipf distribution.
|
| 667 |
+
|
| 668 |
+
Repeatedly draws values from the Zipf distribution until the
|
| 669 |
+
threshold is met, then returns that value.
|
| 670 |
+
"""
|
| 671 |
+
result = nx.utils.zipf_rv(gamma, xmin, seed)
|
| 672 |
+
while result > threshold:
|
| 673 |
+
result = nx.utils.zipf_rv(gamma, xmin, seed)
|
| 674 |
+
return result
|
| 675 |
+
|
| 676 |
+
|
| 677 |
+
def _powerlaw_sequence(gamma, low, high, condition, length, max_iters, seed):
|
| 678 |
+
"""Returns a list of numbers obeying a constrained power law distribution.
|
| 679 |
+
|
| 680 |
+
``gamma`` and ``low`` are the parameters for the Zipf distribution.
|
| 681 |
+
|
| 682 |
+
``high`` is the maximum allowed value for values draw from the Zipf
|
| 683 |
+
distribution. For more information, see :func:`_zipf_rv_below`.
|
| 684 |
+
|
| 685 |
+
``condition`` and ``length`` are Boolean-valued functions on
|
| 686 |
+
lists. While generating the list, random values are drawn and
|
| 687 |
+
appended to the list until ``length`` is satisfied by the created
|
| 688 |
+
list. Once ``condition`` is satisfied, the sequence generated in
|
| 689 |
+
this way is returned.
|
| 690 |
+
|
| 691 |
+
``max_iters`` indicates the number of times to generate a list
|
| 692 |
+
satisfying ``length``. If the number of iterations exceeds this
|
| 693 |
+
value, :exc:`~networkx.exception.ExceededMaxIterations` is raised.
|
| 694 |
+
|
| 695 |
+
seed : integer, random_state, or None (default)
|
| 696 |
+
Indicator of random number generation state.
|
| 697 |
+
See :ref:`Randomness<randomness>`.
|
| 698 |
+
"""
|
| 699 |
+
for i in range(max_iters):
|
| 700 |
+
seq = []
|
| 701 |
+
while not length(seq):
|
| 702 |
+
seq.append(_zipf_rv_below(gamma, low, high, seed))
|
| 703 |
+
if condition(seq):
|
| 704 |
+
return seq
|
| 705 |
+
raise nx.ExceededMaxIterations("Could not create power law sequence")
|
| 706 |
+
|
| 707 |
+
|
| 708 |
+
def _hurwitz_zeta(x, q, tolerance):
|
| 709 |
+
"""The Hurwitz zeta function, or the Riemann zeta function of two arguments.
|
| 710 |
+
|
| 711 |
+
``x`` must be greater than one and ``q`` must be positive.
|
| 712 |
+
|
| 713 |
+
This function repeatedly computes subsequent partial sums until
|
| 714 |
+
convergence, as decided by ``tolerance``.
|
| 715 |
+
"""
|
| 716 |
+
z = 0
|
| 717 |
+
z_prev = -float("inf")
|
| 718 |
+
k = 0
|
| 719 |
+
while abs(z - z_prev) > tolerance:
|
| 720 |
+
z_prev = z
|
| 721 |
+
z += 1 / ((k + q) ** x)
|
| 722 |
+
k += 1
|
| 723 |
+
return z
|
| 724 |
+
|
| 725 |
+
|
| 726 |
+
def _generate_min_degree(gamma, average_degree, max_degree, tolerance, max_iters):
|
| 727 |
+
"""Returns a minimum degree from the given average degree."""
|
| 728 |
+
# Defines zeta function whether or not Scipy is available
|
| 729 |
+
try:
|
| 730 |
+
from scipy.special import zeta
|
| 731 |
+
except ImportError:
|
| 732 |
+
|
| 733 |
+
def zeta(x, q):
|
| 734 |
+
return _hurwitz_zeta(x, q, tolerance)
|
| 735 |
+
|
| 736 |
+
min_deg_top = max_degree
|
| 737 |
+
min_deg_bot = 1
|
| 738 |
+
min_deg_mid = (min_deg_top - min_deg_bot) / 2 + min_deg_bot
|
| 739 |
+
itrs = 0
|
| 740 |
+
mid_avg_deg = 0
|
| 741 |
+
while abs(mid_avg_deg - average_degree) > tolerance:
|
| 742 |
+
if itrs > max_iters:
|
| 743 |
+
raise nx.ExceededMaxIterations("Could not match average_degree")
|
| 744 |
+
mid_avg_deg = 0
|
| 745 |
+
for x in range(int(min_deg_mid), max_degree + 1):
|
| 746 |
+
mid_avg_deg += (x ** (-gamma + 1)) / zeta(gamma, min_deg_mid)
|
| 747 |
+
if mid_avg_deg > average_degree:
|
| 748 |
+
min_deg_top = min_deg_mid
|
| 749 |
+
min_deg_mid = (min_deg_top - min_deg_bot) / 2 + min_deg_bot
|
| 750 |
+
else:
|
| 751 |
+
min_deg_bot = min_deg_mid
|
| 752 |
+
min_deg_mid = (min_deg_top - min_deg_bot) / 2 + min_deg_bot
|
| 753 |
+
itrs += 1
|
| 754 |
+
# return int(min_deg_mid + 0.5)
|
| 755 |
+
return round(min_deg_mid)
|
| 756 |
+
|
| 757 |
+
|
| 758 |
+
def _generate_communities(degree_seq, community_sizes, mu, max_iters, seed):
|
| 759 |
+
"""Returns a list of sets, each of which represents a community.
|
| 760 |
+
|
| 761 |
+
``degree_seq`` is the degree sequence that must be met by the
|
| 762 |
+
graph.
|
| 763 |
+
|
| 764 |
+
``community_sizes`` is the community size distribution that must be
|
| 765 |
+
met by the generated list of sets.
|
| 766 |
+
|
| 767 |
+
``mu`` is a float in the interval [0, 1] indicating the fraction of
|
| 768 |
+
intra-community edges incident to each node.
|
| 769 |
+
|
| 770 |
+
``max_iters`` is the number of times to try to add a node to a
|
| 771 |
+
community. This must be greater than the length of
|
| 772 |
+
``degree_seq``, otherwise this function will always fail. If
|
| 773 |
+
the number of iterations exceeds this value,
|
| 774 |
+
:exc:`~networkx.exception.ExceededMaxIterations` is raised.
|
| 775 |
+
|
| 776 |
+
seed : integer, random_state, or None (default)
|
| 777 |
+
Indicator of random number generation state.
|
| 778 |
+
See :ref:`Randomness<randomness>`.
|
| 779 |
+
|
| 780 |
+
The communities returned by this are sets of integers in the set {0,
|
| 781 |
+
..., *n* - 1}, where *n* is the length of ``degree_seq``.
|
| 782 |
+
|
| 783 |
+
"""
|
| 784 |
+
# This assumes the nodes in the graph will be natural numbers.
|
| 785 |
+
result = [set() for _ in community_sizes]
|
| 786 |
+
n = len(degree_seq)
|
| 787 |
+
free = list(range(n))
|
| 788 |
+
for i in range(max_iters):
|
| 789 |
+
v = free.pop()
|
| 790 |
+
c = seed.choice(range(len(community_sizes)))
|
| 791 |
+
# s = int(degree_seq[v] * (1 - mu) + 0.5)
|
| 792 |
+
s = round(degree_seq[v] * (1 - mu))
|
| 793 |
+
# If the community is large enough, add the node to the chosen
|
| 794 |
+
# community. Otherwise, return it to the list of unaffiliated
|
| 795 |
+
# nodes.
|
| 796 |
+
if s < community_sizes[c]:
|
| 797 |
+
result[c].add(v)
|
| 798 |
+
else:
|
| 799 |
+
free.append(v)
|
| 800 |
+
# If the community is too big, remove a node from it.
|
| 801 |
+
if len(result[c]) > community_sizes[c]:
|
| 802 |
+
free.append(result[c].pop())
|
| 803 |
+
if not free:
|
| 804 |
+
return result
|
| 805 |
+
msg = "Could not assign communities; try increasing min_community"
|
| 806 |
+
raise nx.ExceededMaxIterations(msg)
|
| 807 |
+
|
| 808 |
+
|
| 809 |
+
@py_random_state(11)
|
| 810 |
+
@nx._dispatchable(graphs=None, returns_graph=True)
|
| 811 |
+
def LFR_benchmark_graph(
|
| 812 |
+
n,
|
| 813 |
+
tau1,
|
| 814 |
+
tau2,
|
| 815 |
+
mu,
|
| 816 |
+
average_degree=None,
|
| 817 |
+
min_degree=None,
|
| 818 |
+
max_degree=None,
|
| 819 |
+
min_community=None,
|
| 820 |
+
max_community=None,
|
| 821 |
+
tol=1.0e-7,
|
| 822 |
+
max_iters=500,
|
| 823 |
+
seed=None,
|
| 824 |
+
):
|
| 825 |
+
r"""Returns the LFR benchmark graph.
|
| 826 |
+
|
| 827 |
+
This algorithm proceeds as follows:
|
| 828 |
+
|
| 829 |
+
1) Find a degree sequence with a power law distribution, and minimum
|
| 830 |
+
value ``min_degree``, which has approximate average degree
|
| 831 |
+
``average_degree``. This is accomplished by either
|
| 832 |
+
|
| 833 |
+
a) specifying ``min_degree`` and not ``average_degree``,
|
| 834 |
+
b) specifying ``average_degree`` and not ``min_degree``, in which
|
| 835 |
+
case a suitable minimum degree will be found.
|
| 836 |
+
|
| 837 |
+
``max_degree`` can also be specified, otherwise it will be set to
|
| 838 |
+
``n``. Each node *u* will have $\mu \mathrm{deg}(u)$ edges
|
| 839 |
+
joining it to nodes in communities other than its own and $(1 -
|
| 840 |
+
\mu) \mathrm{deg}(u)$ edges joining it to nodes in its own
|
| 841 |
+
community.
|
| 842 |
+
2) Generate community sizes according to a power law distribution
|
| 843 |
+
with exponent ``tau2``. If ``min_community`` and
|
| 844 |
+
``max_community`` are not specified they will be selected to be
|
| 845 |
+
``min_degree`` and ``max_degree``, respectively. Community sizes
|
| 846 |
+
are generated until the sum of their sizes equals ``n``.
|
| 847 |
+
3) Each node will be randomly assigned a community with the
|
| 848 |
+
condition that the community is large enough for the node's
|
| 849 |
+
intra-community degree, $(1 - \mu) \mathrm{deg}(u)$ as
|
| 850 |
+
described in step 2. If a community grows too large, a random node
|
| 851 |
+
will be selected for reassignment to a new community, until all
|
| 852 |
+
nodes have been assigned a community.
|
| 853 |
+
4) Each node *u* then adds $(1 - \mu) \mathrm{deg}(u)$
|
| 854 |
+
intra-community edges and $\mu \mathrm{deg}(u)$ inter-community
|
| 855 |
+
edges.
|
| 856 |
+
|
| 857 |
+
Parameters
|
| 858 |
+
----------
|
| 859 |
+
n : int
|
| 860 |
+
Number of nodes in the created graph.
|
| 861 |
+
|
| 862 |
+
tau1 : float
|
| 863 |
+
Power law exponent for the degree distribution of the created
|
| 864 |
+
graph. This value must be strictly greater than one.
|
| 865 |
+
|
| 866 |
+
tau2 : float
|
| 867 |
+
Power law exponent for the community size distribution in the
|
| 868 |
+
created graph. This value must be strictly greater than one.
|
| 869 |
+
|
| 870 |
+
mu : float
|
| 871 |
+
Fraction of inter-community edges incident to each node. This
|
| 872 |
+
value must be in the interval [0, 1].
|
| 873 |
+
|
| 874 |
+
average_degree : float
|
| 875 |
+
Desired average degree of nodes in the created graph. This value
|
| 876 |
+
must be in the interval [0, *n*]. Exactly one of this and
|
| 877 |
+
``min_degree`` must be specified, otherwise a
|
| 878 |
+
:exc:`NetworkXError` is raised.
|
| 879 |
+
|
| 880 |
+
min_degree : int
|
| 881 |
+
Minimum degree of nodes in the created graph. This value must be
|
| 882 |
+
in the interval [0, *n*]. Exactly one of this and
|
| 883 |
+
``average_degree`` must be specified, otherwise a
|
| 884 |
+
:exc:`NetworkXError` is raised.
|
| 885 |
+
|
| 886 |
+
max_degree : int
|
| 887 |
+
Maximum degree of nodes in the created graph. If not specified,
|
| 888 |
+
this is set to ``n``, the total number of nodes in the graph.
|
| 889 |
+
|
| 890 |
+
min_community : int
|
| 891 |
+
Minimum size of communities in the graph. If not specified, this
|
| 892 |
+
is set to ``min_degree``.
|
| 893 |
+
|
| 894 |
+
max_community : int
|
| 895 |
+
Maximum size of communities in the graph. If not specified, this
|
| 896 |
+
is set to ``n``, the total number of nodes in the graph.
|
| 897 |
+
|
| 898 |
+
tol : float
|
| 899 |
+
Tolerance when comparing floats, specifically when comparing
|
| 900 |
+
average degree values.
|
| 901 |
+
|
| 902 |
+
max_iters : int
|
| 903 |
+
Maximum number of iterations to try to create the community sizes,
|
| 904 |
+
degree distribution, and community affiliations.
|
| 905 |
+
|
| 906 |
+
seed : integer, random_state, or None (default)
|
| 907 |
+
Indicator of random number generation state.
|
| 908 |
+
See :ref:`Randomness<randomness>`.
|
| 909 |
+
|
| 910 |
+
Returns
|
| 911 |
+
-------
|
| 912 |
+
G : NetworkX graph
|
| 913 |
+
The LFR benchmark graph generated according to the specified
|
| 914 |
+
parameters.
|
| 915 |
+
|
| 916 |
+
Each node in the graph has a node attribute ``'community'`` that
|
| 917 |
+
stores the community (that is, the set of nodes) that includes
|
| 918 |
+
it.
|
| 919 |
+
|
| 920 |
+
Raises
|
| 921 |
+
------
|
| 922 |
+
NetworkXError
|
| 923 |
+
If any of the parameters do not meet their upper and lower bounds:
|
| 924 |
+
|
| 925 |
+
- ``tau1`` and ``tau2`` must be strictly greater than 1.
|
| 926 |
+
- ``mu`` must be in [0, 1].
|
| 927 |
+
- ``max_degree`` must be in {1, ..., *n*}.
|
| 928 |
+
- ``min_community`` and ``max_community`` must be in {0, ...,
|
| 929 |
+
*n*}.
|
| 930 |
+
|
| 931 |
+
If not exactly one of ``average_degree`` and ``min_degree`` is
|
| 932 |
+
specified.
|
| 933 |
+
|
| 934 |
+
If ``min_degree`` is not specified and a suitable ``min_degree``
|
| 935 |
+
cannot be found.
|
| 936 |
+
|
| 937 |
+
ExceededMaxIterations
|
| 938 |
+
If a valid degree sequence cannot be created within
|
| 939 |
+
``max_iters`` number of iterations.
|
| 940 |
+
|
| 941 |
+
If a valid set of community sizes cannot be created within
|
| 942 |
+
``max_iters`` number of iterations.
|
| 943 |
+
|
| 944 |
+
If a valid community assignment cannot be created within ``10 *
|
| 945 |
+
n * max_iters`` number of iterations.
|
| 946 |
+
|
| 947 |
+
Examples
|
| 948 |
+
--------
|
| 949 |
+
Basic usage::
|
| 950 |
+
|
| 951 |
+
>>> from networkx.generators.community import LFR_benchmark_graph
|
| 952 |
+
>>> n = 250
|
| 953 |
+
>>> tau1 = 3
|
| 954 |
+
>>> tau2 = 1.5
|
| 955 |
+
>>> mu = 0.1
|
| 956 |
+
>>> G = LFR_benchmark_graph(
|
| 957 |
+
... n, tau1, tau2, mu, average_degree=5, min_community=20, seed=10
|
| 958 |
+
... )
|
| 959 |
+
|
| 960 |
+
Continuing the example above, you can get the communities from the
|
| 961 |
+
node attributes of the graph::
|
| 962 |
+
|
| 963 |
+
>>> communities = {frozenset(G.nodes[v]["community"]) for v in G}
|
| 964 |
+
|
| 965 |
+
Notes
|
| 966 |
+
-----
|
| 967 |
+
This algorithm differs slightly from the original way it was
|
| 968 |
+
presented in [1].
|
| 969 |
+
|
| 970 |
+
1) Rather than connecting the graph via a configuration model then
|
| 971 |
+
rewiring to match the intra-community and inter-community
|
| 972 |
+
degrees, we do this wiring explicitly at the end, which should be
|
| 973 |
+
equivalent.
|
| 974 |
+
2) The code posted on the author's website [2] calculates the random
|
| 975 |
+
power law distributed variables and their average using
|
| 976 |
+
continuous approximations, whereas we use the discrete
|
| 977 |
+
distributions here as both degree and community size are
|
| 978 |
+
discrete.
|
| 979 |
+
|
| 980 |
+
Though the authors describe the algorithm as quite robust, testing
|
| 981 |
+
during development indicates that a somewhat narrower parameter set
|
| 982 |
+
is likely to successfully produce a graph. Some suggestions have
|
| 983 |
+
been provided in the event of exceptions.
|
| 984 |
+
|
| 985 |
+
References
|
| 986 |
+
----------
|
| 987 |
+
.. [1] "Benchmark graphs for testing community detection algorithms",
|
| 988 |
+
Andrea Lancichinetti, Santo Fortunato, and Filippo Radicchi,
|
| 989 |
+
Phys. Rev. E 78, 046110 2008
|
| 990 |
+
.. [2] https://www.santofortunato.net/resources
|
| 991 |
+
|
| 992 |
+
"""
|
| 993 |
+
# Perform some basic parameter validation.
|
| 994 |
+
if not tau1 > 1:
|
| 995 |
+
raise nx.NetworkXError("tau1 must be greater than one")
|
| 996 |
+
if not tau2 > 1:
|
| 997 |
+
raise nx.NetworkXError("tau2 must be greater than one")
|
| 998 |
+
if not 0 <= mu <= 1:
|
| 999 |
+
raise nx.NetworkXError("mu must be in the interval [0, 1]")
|
| 1000 |
+
|
| 1001 |
+
# Validate parameters for generating the degree sequence.
|
| 1002 |
+
if max_degree is None:
|
| 1003 |
+
max_degree = n
|
| 1004 |
+
elif not 0 < max_degree <= n:
|
| 1005 |
+
raise nx.NetworkXError("max_degree must be in the interval (0, n]")
|
| 1006 |
+
if not ((min_degree is None) ^ (average_degree is None)):
|
| 1007 |
+
raise nx.NetworkXError(
|
| 1008 |
+
"Must assign exactly one of min_degree and average_degree"
|
| 1009 |
+
)
|
| 1010 |
+
if min_degree is None:
|
| 1011 |
+
min_degree = _generate_min_degree(
|
| 1012 |
+
tau1, average_degree, max_degree, tol, max_iters
|
| 1013 |
+
)
|
| 1014 |
+
|
| 1015 |
+
# Generate a degree sequence with a power law distribution.
|
| 1016 |
+
low, high = min_degree, max_degree
|
| 1017 |
+
|
| 1018 |
+
def condition(seq):
|
| 1019 |
+
return sum(seq) % 2 == 0
|
| 1020 |
+
|
| 1021 |
+
def length(seq):
|
| 1022 |
+
return len(seq) >= n
|
| 1023 |
+
|
| 1024 |
+
deg_seq = _powerlaw_sequence(tau1, low, high, condition, length, max_iters, seed)
|
| 1025 |
+
|
| 1026 |
+
# Validate parameters for generating the community size sequence.
|
| 1027 |
+
if min_community is None:
|
| 1028 |
+
min_community = min(deg_seq)
|
| 1029 |
+
if max_community is None:
|
| 1030 |
+
max_community = max(deg_seq)
|
| 1031 |
+
|
| 1032 |
+
# Generate a community size sequence with a power law distribution.
|
| 1033 |
+
#
|
| 1034 |
+
# TODO The original code incremented the number of iterations each
|
| 1035 |
+
# time a new Zipf random value was drawn from the distribution. This
|
| 1036 |
+
# differed from the way the number of iterations was incremented in
|
| 1037 |
+
# `_powerlaw_degree_sequence`, so this code was changed to match
|
| 1038 |
+
# that one. As a result, this code is allowed many more chances to
|
| 1039 |
+
# generate a valid community size sequence.
|
| 1040 |
+
low, high = min_community, max_community
|
| 1041 |
+
|
| 1042 |
+
def condition(seq):
|
| 1043 |
+
return sum(seq) == n
|
| 1044 |
+
|
| 1045 |
+
def length(seq):
|
| 1046 |
+
return sum(seq) >= n
|
| 1047 |
+
|
| 1048 |
+
comms = _powerlaw_sequence(tau2, low, high, condition, length, max_iters, seed)
|
| 1049 |
+
|
| 1050 |
+
# Generate the communities based on the given degree sequence and
|
| 1051 |
+
# community sizes.
|
| 1052 |
+
max_iters *= 10 * n
|
| 1053 |
+
communities = _generate_communities(deg_seq, comms, mu, max_iters, seed)
|
| 1054 |
+
|
| 1055 |
+
# Finally, generate the benchmark graph based on the given
|
| 1056 |
+
# communities, joining nodes according to the intra- and
|
| 1057 |
+
# inter-community degrees.
|
| 1058 |
+
G = nx.Graph()
|
| 1059 |
+
G.add_nodes_from(range(n))
|
| 1060 |
+
for c in communities:
|
| 1061 |
+
for u in c:
|
| 1062 |
+
while G.degree(u) < round(deg_seq[u] * (1 - mu)):
|
| 1063 |
+
v = seed.choice(list(c))
|
| 1064 |
+
G.add_edge(u, v)
|
| 1065 |
+
while G.degree(u) < deg_seq[u]:
|
| 1066 |
+
v = seed.choice(range(n))
|
| 1067 |
+
if v not in c:
|
| 1068 |
+
G.add_edge(u, v)
|
| 1069 |
+
G.nodes[u]["community"] = c
|
| 1070 |
+
return G
|