koichi12 commited on
Commit
375266d
·
verified ·
1 Parent(s): 1f8330b

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .venv/lib/python3.11/site-packages/networkx/__pycache__/__init__.cpython-311.pyc +0 -0
  2. .venv/lib/python3.11/site-packages/networkx/__pycache__/conftest.cpython-311.pyc +0 -0
  3. .venv/lib/python3.11/site-packages/networkx/__pycache__/convert.cpython-311.pyc +0 -0
  4. .venv/lib/python3.11/site-packages/networkx/__pycache__/convert_matrix.cpython-311.pyc +0 -0
  5. .venv/lib/python3.11/site-packages/networkx/__pycache__/exception.cpython-311.pyc +0 -0
  6. .venv/lib/python3.11/site-packages/networkx/__pycache__/lazy_imports.cpython-311.pyc +0 -0
  7. .venv/lib/python3.11/site-packages/networkx/__pycache__/relabel.cpython-311.pyc +0 -0
  8. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/__init__.cpython-311.pyc +0 -0
  9. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/centrality.cpython-311.pyc +0 -0
  10. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/extendability.cpython-311.pyc +0 -0
  11. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/__pycache__/redundancy.cpython-311.pyc +0 -0
  12. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__init__.py +0 -0
  13. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  14. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_basic.cpython-311.pyc +0 -0
  15. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_centrality.cpython-311.pyc +0 -0
  16. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_cluster.cpython-311.pyc +0 -0
  17. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_covering.cpython-311.pyc +0 -0
  18. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_edgelist.cpython-311.pyc +0 -0
  19. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_extendability.cpython-311.pyc +0 -0
  20. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_generators.cpython-311.pyc +0 -0
  21. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_matching.cpython-311.pyc +0 -0
  22. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_matrix.cpython-311.pyc +0 -0
  23. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_project.cpython-311.pyc +0 -0
  24. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_redundancy.cpython-311.pyc +0 -0
  25. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_spectral_bipartivity.cpython-311.pyc +0 -0
  26. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_basic.py +125 -0
  27. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_centrality.py +192 -0
  28. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_cluster.py +84 -0
  29. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_covering.py +33 -0
  30. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_edgelist.py +240 -0
  31. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_extendability.py +334 -0
  32. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_generators.py +409 -0
  33. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_matching.py +327 -0
  34. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_matrix.py +84 -0
  35. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_project.py +407 -0
  36. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_redundancy.py +35 -0
  37. .venv/lib/python3.11/site-packages/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py +80 -0
  38. .venv/lib/python3.11/site-packages/networkx/generators/__init__.py +34 -0
  39. .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/directed.cpython-311.pyc +0 -0
  40. .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/duplication.cpython-311.pyc +0 -0
  41. .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/geometric.cpython-311.pyc +0 -0
  42. .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/joint_degree_seq.cpython-311.pyc +0 -0
  43. .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/mycielski.cpython-311.pyc +0 -0
  44. .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/social.cpython-311.pyc +0 -0
  45. .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/spectral_graph_forge.cpython-311.pyc +0 -0
  46. .venv/lib/python3.11/site-packages/networkx/generators/__pycache__/trees.cpython-311.pyc +0 -0
  47. .venv/lib/python3.11/site-packages/networkx/generators/atlas.py +180 -0
  48. .venv/lib/python3.11/site-packages/networkx/generators/classic.py +1068 -0
  49. .venv/lib/python3.11/site-packages/networkx/generators/cographs.py +68 -0
  50. .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