aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_basic.py125
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_centrality.py192
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_cluster.py84
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_covering.py33
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_edgelist.py240
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_extendability.py334
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_generators.py409
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matching.py327
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matrix.py84
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_project.py407
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_redundancy.py35
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py80
13 files changed, 2350 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__init__.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_basic.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_basic.py
new file mode 100644
index 00000000..655506b4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_basic.py
@@ -0,0 +1,125 @@
+import pytest
+
+import networkx as nx
+from networkx.algorithms import bipartite
+
+
+class TestBipartiteBasic:
+ def test_is_bipartite(self):
+ assert bipartite.is_bipartite(nx.path_graph(4))
+ assert bipartite.is_bipartite(nx.DiGraph([(1, 0)]))
+ assert not bipartite.is_bipartite(nx.complete_graph(3))
+
+ def test_bipartite_color(self):
+ G = nx.path_graph(4)
+ c = bipartite.color(G)
+ assert c == {0: 1, 1: 0, 2: 1, 3: 0}
+
+ def test_not_bipartite_color(self):
+ with pytest.raises(nx.NetworkXError):
+ c = bipartite.color(nx.complete_graph(4))
+
+ def test_bipartite_directed(self):
+ G = bipartite.random_graph(10, 10, 0.1, directed=True)
+ assert bipartite.is_bipartite(G)
+
+ def test_bipartite_sets(self):
+ G = nx.path_graph(4)
+ X, Y = bipartite.sets(G)
+ assert X == {0, 2}
+ assert Y == {1, 3}
+
+ def test_bipartite_sets_directed(self):
+ G = nx.path_graph(4)
+ D = G.to_directed()
+ X, Y = bipartite.sets(D)
+ assert X == {0, 2}
+ assert Y == {1, 3}
+
+ def test_bipartite_sets_given_top_nodes(self):
+ G = nx.path_graph(4)
+ top_nodes = [0, 2]
+ X, Y = bipartite.sets(G, top_nodes)
+ assert X == {0, 2}
+ assert Y == {1, 3}
+
+ def test_bipartite_sets_disconnected(self):
+ with pytest.raises(nx.AmbiguousSolution):
+ G = nx.path_graph(4)
+ G.add_edges_from([(5, 6), (6, 7)])
+ X, Y = bipartite.sets(G)
+
+ def test_is_bipartite_node_set(self):
+ G = nx.path_graph(4)
+
+ with pytest.raises(nx.AmbiguousSolution):
+ bipartite.is_bipartite_node_set(G, [1, 1, 2, 3])
+
+ assert bipartite.is_bipartite_node_set(G, [0, 2])
+ assert bipartite.is_bipartite_node_set(G, [1, 3])
+ assert not bipartite.is_bipartite_node_set(G, [1, 2])
+ G.add_edge(10, 20)
+ assert bipartite.is_bipartite_node_set(G, [0, 2, 10])
+ assert bipartite.is_bipartite_node_set(G, [0, 2, 20])
+ assert bipartite.is_bipartite_node_set(G, [1, 3, 10])
+ assert bipartite.is_bipartite_node_set(G, [1, 3, 20])
+
+ def test_bipartite_density(self):
+ G = nx.path_graph(5)
+ X, Y = bipartite.sets(G)
+ density = len(list(G.edges())) / (len(X) * len(Y))
+ assert bipartite.density(G, X) == density
+ D = nx.DiGraph(G.edges())
+ assert bipartite.density(D, X) == density / 2.0
+ assert bipartite.density(nx.Graph(), {}) == 0.0
+
+ def test_bipartite_degrees(self):
+ G = nx.path_graph(5)
+ X = {1, 3}
+ Y = {0, 2, 4}
+ u, d = bipartite.degrees(G, Y)
+ assert dict(u) == {1: 2, 3: 2}
+ assert dict(d) == {0: 1, 2: 2, 4: 1}
+
+ def test_bipartite_weighted_degrees(self):
+ G = nx.path_graph(5)
+ G.add_edge(0, 1, weight=0.1, other=0.2)
+ X = {1, 3}
+ Y = {0, 2, 4}
+ u, d = bipartite.degrees(G, Y, weight="weight")
+ assert dict(u) == {1: 1.1, 3: 2}
+ assert dict(d) == {0: 0.1, 2: 2, 4: 1}
+ u, d = bipartite.degrees(G, Y, weight="other")
+ assert dict(u) == {1: 1.2, 3: 2}
+ assert dict(d) == {0: 0.2, 2: 2, 4: 1}
+
+ def test_biadjacency_matrix_weight(self):
+ pytest.importorskip("scipy")
+ G = nx.path_graph(5)
+ G.add_edge(0, 1, weight=2, other=4)
+ X = [1, 3]
+ Y = [0, 2, 4]
+ M = bipartite.biadjacency_matrix(G, X, weight="weight")
+ assert M[0, 0] == 2
+ M = bipartite.biadjacency_matrix(G, X, weight="other")
+ assert M[0, 0] == 4
+
+ def test_biadjacency_matrix(self):
+ pytest.importorskip("scipy")
+ tops = [2, 5, 10]
+ bots = [5, 10, 15]
+ for i in range(len(tops)):
+ G = bipartite.random_graph(tops[i], bots[i], 0.2)
+ top = [n for n, d in G.nodes(data=True) if d["bipartite"] == 0]
+ M = bipartite.biadjacency_matrix(G, top)
+ assert M.shape[0] == tops[i]
+ assert M.shape[1] == bots[i]
+
+ def test_biadjacency_matrix_order(self):
+ pytest.importorskip("scipy")
+ G = nx.path_graph(5)
+ G.add_edge(0, 1, weight=2)
+ X = [3, 1]
+ Y = [4, 2, 0]
+ M = bipartite.biadjacency_matrix(G, X, Y, weight="weight")
+ assert M[1, 2] == 2
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_centrality.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_centrality.py
new file mode 100644
index 00000000..19fb5d11
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_centrality.py
@@ -0,0 +1,192 @@
+import pytest
+
+import networkx as nx
+from networkx.algorithms import bipartite
+
+
+class TestBipartiteCentrality:
+ @classmethod
+ def setup_class(cls):
+ cls.P4 = nx.path_graph(4)
+ cls.K3 = nx.complete_bipartite_graph(3, 3)
+ cls.C4 = nx.cycle_graph(4)
+ cls.davis = nx.davis_southern_women_graph()
+ cls.top_nodes = [
+ n for n, d in cls.davis.nodes(data=True) if d["bipartite"] == 0
+ ]
+
+ def test_degree_centrality(self):
+ d = bipartite.degree_centrality(self.P4, [1, 3])
+ answer = {0: 0.5, 1: 1.0, 2: 1.0, 3: 0.5}
+ assert d == answer
+ d = bipartite.degree_centrality(self.K3, [0, 1, 2])
+ answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0, 5: 1.0}
+ assert d == answer
+ d = bipartite.degree_centrality(self.C4, [0, 2])
+ answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0}
+ assert d == answer
+
+ def test_betweenness_centrality(self):
+ c = bipartite.betweenness_centrality(self.P4, [1, 3])
+ answer = {0: 0.0, 1: 1.0, 2: 1.0, 3: 0.0}
+ assert c == answer
+ c = bipartite.betweenness_centrality(self.K3, [0, 1, 2])
+ answer = {0: 0.125, 1: 0.125, 2: 0.125, 3: 0.125, 4: 0.125, 5: 0.125}
+ assert c == answer
+ c = bipartite.betweenness_centrality(self.C4, [0, 2])
+ answer = {0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25}
+ assert c == answer
+
+ def test_closeness_centrality(self):
+ c = bipartite.closeness_centrality(self.P4, [1, 3])
+ answer = {0: 2.0 / 3, 1: 1.0, 2: 1.0, 3: 2.0 / 3}
+ assert c == answer
+ c = bipartite.closeness_centrality(self.K3, [0, 1, 2])
+ answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0, 5: 1.0}
+ assert c == answer
+ c = bipartite.closeness_centrality(self.C4, [0, 2])
+ answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0}
+ assert c == answer
+ G = nx.Graph()
+ G.add_node(0)
+ G.add_node(1)
+ c = bipartite.closeness_centrality(G, [0])
+ assert c == {0: 0.0, 1: 0.0}
+ c = bipartite.closeness_centrality(G, [1])
+ assert c == {0: 0.0, 1: 0.0}
+
+ def test_bipartite_closeness_centrality_unconnected(self):
+ G = nx.complete_bipartite_graph(3, 3)
+ G.add_edge(6, 7)
+ c = bipartite.closeness_centrality(G, [0, 2, 4, 6], normalized=False)
+ answer = {
+ 0: 10.0 / 7,
+ 2: 10.0 / 7,
+ 4: 10.0 / 7,
+ 6: 10.0,
+ 1: 10.0 / 7,
+ 3: 10.0 / 7,
+ 5: 10.0 / 7,
+ 7: 10.0,
+ }
+ assert c == answer
+
+ def test_davis_degree_centrality(self):
+ G = self.davis
+ deg = bipartite.degree_centrality(G, self.top_nodes)
+ answer = {
+ "E8": 0.78,
+ "E9": 0.67,
+ "E7": 0.56,
+ "Nora Fayette": 0.57,
+ "Evelyn Jefferson": 0.57,
+ "Theresa Anderson": 0.57,
+ "E6": 0.44,
+ "Sylvia Avondale": 0.50,
+ "Laura Mandeville": 0.50,
+ "Brenda Rogers": 0.50,
+ "Katherina Rogers": 0.43,
+ "E5": 0.44,
+ "Helen Lloyd": 0.36,
+ "E3": 0.33,
+ "Ruth DeSand": 0.29,
+ "Verne Sanderson": 0.29,
+ "E12": 0.33,
+ "Myra Liddel": 0.29,
+ "E11": 0.22,
+ "Eleanor Nye": 0.29,
+ "Frances Anderson": 0.29,
+ "Pearl Oglethorpe": 0.21,
+ "E4": 0.22,
+ "Charlotte McDowd": 0.29,
+ "E10": 0.28,
+ "Olivia Carleton": 0.14,
+ "Flora Price": 0.14,
+ "E2": 0.17,
+ "E1": 0.17,
+ "Dorothy Murchison": 0.14,
+ "E13": 0.17,
+ "E14": 0.17,
+ }
+ for node, value in answer.items():
+ assert value == pytest.approx(deg[node], abs=1e-2)
+
+ def test_davis_betweenness_centrality(self):
+ G = self.davis
+ bet = bipartite.betweenness_centrality(G, self.top_nodes)
+ answer = {
+ "E8": 0.24,
+ "E9": 0.23,
+ "E7": 0.13,
+ "Nora Fayette": 0.11,
+ "Evelyn Jefferson": 0.10,
+ "Theresa Anderson": 0.09,
+ "E6": 0.07,
+ "Sylvia Avondale": 0.07,
+ "Laura Mandeville": 0.05,
+ "Brenda Rogers": 0.05,
+ "Katherina Rogers": 0.05,
+ "E5": 0.04,
+ "Helen Lloyd": 0.04,
+ "E3": 0.02,
+ "Ruth DeSand": 0.02,
+ "Verne Sanderson": 0.02,
+ "E12": 0.02,
+ "Myra Liddel": 0.02,
+ "E11": 0.02,
+ "Eleanor Nye": 0.01,
+ "Frances Anderson": 0.01,
+ "Pearl Oglethorpe": 0.01,
+ "E4": 0.01,
+ "Charlotte McDowd": 0.01,
+ "E10": 0.01,
+ "Olivia Carleton": 0.01,
+ "Flora Price": 0.01,
+ "E2": 0.00,
+ "E1": 0.00,
+ "Dorothy Murchison": 0.00,
+ "E13": 0.00,
+ "E14": 0.00,
+ }
+ for node, value in answer.items():
+ assert value == pytest.approx(bet[node], abs=1e-2)
+
+ def test_davis_closeness_centrality(self):
+ G = self.davis
+ clos = bipartite.closeness_centrality(G, self.top_nodes)
+ answer = {
+ "E8": 0.85,
+ "E9": 0.79,
+ "E7": 0.73,
+ "Nora Fayette": 0.80,
+ "Evelyn Jefferson": 0.80,
+ "Theresa Anderson": 0.80,
+ "E6": 0.69,
+ "Sylvia Avondale": 0.77,
+ "Laura Mandeville": 0.73,
+ "Brenda Rogers": 0.73,
+ "Katherina Rogers": 0.73,
+ "E5": 0.59,
+ "Helen Lloyd": 0.73,
+ "E3": 0.56,
+ "Ruth DeSand": 0.71,
+ "Verne Sanderson": 0.71,
+ "E12": 0.56,
+ "Myra Liddel": 0.69,
+ "E11": 0.54,
+ "Eleanor Nye": 0.67,
+ "Frances Anderson": 0.67,
+ "Pearl Oglethorpe": 0.67,
+ "E4": 0.54,
+ "Charlotte McDowd": 0.60,
+ "E10": 0.55,
+ "Olivia Carleton": 0.59,
+ "Flora Price": 0.59,
+ "E2": 0.52,
+ "E1": 0.52,
+ "Dorothy Murchison": 0.65,
+ "E13": 0.52,
+ "E14": 0.52,
+ }
+ for node, value in answer.items():
+ assert value == pytest.approx(clos[node], abs=1e-2)
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_cluster.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_cluster.py
new file mode 100644
index 00000000..72e2dbad
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_cluster.py
@@ -0,0 +1,84 @@
+import pytest
+
+import networkx as nx
+from networkx.algorithms import bipartite
+from networkx.algorithms.bipartite.cluster import cc_dot, cc_max, cc_min
+
+
+def test_pairwise_bipartite_cc_functions():
+ # Test functions for different kinds of bipartite clustering coefficients
+ # between pairs of nodes using 3 example graphs from figure 5 p. 40
+ # Latapy et al (2008)
+ G1 = nx.Graph([(0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (1, 5), (1, 6), (1, 7)])
+ G2 = nx.Graph([(0, 2), (0, 3), (0, 4), (1, 3), (1, 4), (1, 5)])
+ G3 = nx.Graph(
+ [(0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9)]
+ )
+ result = {
+ 0: [1 / 3.0, 2 / 3.0, 2 / 5.0],
+ 1: [1 / 2.0, 2 / 3.0, 2 / 3.0],
+ 2: [2 / 8.0, 2 / 5.0, 2 / 5.0],
+ }
+ for i, G in enumerate([G1, G2, G3]):
+ assert bipartite.is_bipartite(G)
+ assert cc_dot(set(G[0]), set(G[1])) == result[i][0]
+ assert cc_min(set(G[0]), set(G[1])) == result[i][1]
+ assert cc_max(set(G[0]), set(G[1])) == result[i][2]
+
+
+def test_star_graph():
+ G = nx.star_graph(3)
+ # all modes are the same
+ answer = {0: 0, 1: 1, 2: 1, 3: 1}
+ assert bipartite.clustering(G, mode="dot") == answer
+ assert bipartite.clustering(G, mode="min") == answer
+ assert bipartite.clustering(G, mode="max") == answer
+
+
+def test_not_bipartite():
+ with pytest.raises(nx.NetworkXError):
+ bipartite.clustering(nx.complete_graph(4))
+
+
+def test_bad_mode():
+ with pytest.raises(nx.NetworkXError):
+ bipartite.clustering(nx.path_graph(4), mode="foo")
+
+
+def test_path_graph():
+ G = nx.path_graph(4)
+ answer = {0: 0.5, 1: 0.5, 2: 0.5, 3: 0.5}
+ assert bipartite.clustering(G, mode="dot") == answer
+ assert bipartite.clustering(G, mode="max") == answer
+ answer = {0: 1, 1: 1, 2: 1, 3: 1}
+ assert bipartite.clustering(G, mode="min") == answer
+
+
+def test_average_path_graph():
+ G = nx.path_graph(4)
+ assert bipartite.average_clustering(G, mode="dot") == 0.5
+ assert bipartite.average_clustering(G, mode="max") == 0.5
+ assert bipartite.average_clustering(G, mode="min") == 1
+
+
+def test_ra_clustering_davis():
+ G = nx.davis_southern_women_graph()
+ cc4 = round(bipartite.robins_alexander_clustering(G), 3)
+ assert cc4 == 0.468
+
+
+def test_ra_clustering_square():
+ G = nx.path_graph(4)
+ G.add_edge(0, 3)
+ assert bipartite.robins_alexander_clustering(G) == 1.0
+
+
+def test_ra_clustering_zero():
+ G = nx.Graph()
+ assert bipartite.robins_alexander_clustering(G) == 0
+ G.add_nodes_from(range(4))
+ assert bipartite.robins_alexander_clustering(G) == 0
+ G.add_edges_from([(0, 1), (2, 3), (3, 4)])
+ assert bipartite.robins_alexander_clustering(G) == 0
+ G.add_edge(1, 2)
+ assert bipartite.robins_alexander_clustering(G) == 0
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_covering.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_covering.py
new file mode 100644
index 00000000..9507e134
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_covering.py
@@ -0,0 +1,33 @@
+import networkx as nx
+from networkx.algorithms import bipartite
+
+
+class TestMinEdgeCover:
+ """Tests for :func:`networkx.algorithms.bipartite.min_edge_cover`"""
+
+ def test_empty_graph(self):
+ G = nx.Graph()
+ assert bipartite.min_edge_cover(G) == set()
+
+ def test_graph_single_edge(self):
+ G = nx.Graph()
+ G.add_edge(0, 1)
+ assert bipartite.min_edge_cover(G) == {(0, 1), (1, 0)}
+
+ def test_bipartite_default(self):
+ G = nx.Graph()
+ G.add_nodes_from([1, 2, 3, 4], bipartite=0)
+ G.add_nodes_from(["a", "b", "c"], bipartite=1)
+ G.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")])
+ min_cover = bipartite.min_edge_cover(G)
+ assert nx.is_edge_cover(G, min_cover)
+ assert len(min_cover) == 8
+
+ def test_bipartite_explicit(self):
+ G = nx.Graph()
+ G.add_nodes_from([1, 2, 3, 4], bipartite=0)
+ G.add_nodes_from(["a", "b", "c"], bipartite=1)
+ G.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")])
+ min_cover = bipartite.min_edge_cover(G, bipartite.eppstein_matching)
+ assert nx.is_edge_cover(G, min_cover)
+ assert len(min_cover) == 8
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_edgelist.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_edgelist.py
new file mode 100644
index 00000000..66be8a2f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_edgelist.py
@@ -0,0 +1,240 @@
+"""
+Unit tests for bipartite edgelists.
+"""
+
+import io
+
+import pytest
+
+import networkx as nx
+from networkx.algorithms import bipartite
+from networkx.utils import edges_equal, graphs_equal, nodes_equal
+
+
+class TestEdgelist:
+ @classmethod
+ def setup_class(cls):
+ cls.G = nx.Graph(name="test")
+ e = [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f"), ("a", "f")]
+ cls.G.add_edges_from(e)
+ cls.G.add_nodes_from(["a", "c", "e"], bipartite=0)
+ cls.G.add_nodes_from(["b", "d", "f"], bipartite=1)
+ cls.G.add_node("g", bipartite=0)
+ cls.DG = nx.DiGraph(cls.G)
+ cls.MG = nx.MultiGraph()
+ cls.MG.add_edges_from([(1, 2), (1, 2), (1, 2)])
+ cls.MG.add_node(1, bipartite=0)
+ cls.MG.add_node(2, bipartite=1)
+
+ def test_read_edgelist_1(self):
+ s = b"""\
+# comment line
+1 2
+# comment line
+2 3
+"""
+ bytesIO = io.BytesIO(s)
+ G = bipartite.read_edgelist(bytesIO, nodetype=int)
+ assert edges_equal(G.edges(), [(1, 2), (2, 3)])
+
+ def test_read_edgelist_3(self):
+ s = b"""\
+# comment line
+1 2 {'weight':2.0}
+# comment line
+2 3 {'weight':3.0}
+"""
+ bytesIO = io.BytesIO(s)
+ G = bipartite.read_edgelist(bytesIO, nodetype=int, data=False)
+ assert edges_equal(G.edges(), [(1, 2), (2, 3)])
+
+ bytesIO = io.BytesIO(s)
+ G = bipartite.read_edgelist(bytesIO, nodetype=int, data=True)
+ assert edges_equal(
+ G.edges(data=True), [(1, 2, {"weight": 2.0}), (2, 3, {"weight": 3.0})]
+ )
+
+ def test_write_edgelist_1(self):
+ fh = io.BytesIO()
+ G = nx.Graph()
+ G.add_edges_from([(1, 2), (2, 3)])
+ G.add_node(1, bipartite=0)
+ G.add_node(2, bipartite=1)
+ G.add_node(3, bipartite=0)
+ bipartite.write_edgelist(G, fh, data=False)
+ fh.seek(0)
+ assert fh.read() == b"1 2\n3 2\n"
+
+ def test_write_edgelist_2(self):
+ fh = io.BytesIO()
+ G = nx.Graph()
+ G.add_edges_from([(1, 2), (2, 3)])
+ G.add_node(1, bipartite=0)
+ G.add_node(2, bipartite=1)
+ G.add_node(3, bipartite=0)
+ bipartite.write_edgelist(G, fh, data=True)
+ fh.seek(0)
+ assert fh.read() == b"1 2 {}\n3 2 {}\n"
+
+ def test_write_edgelist_3(self):
+ fh = io.BytesIO()
+ G = nx.Graph()
+ G.add_edge(1, 2, weight=2.0)
+ G.add_edge(2, 3, weight=3.0)
+ G.add_node(1, bipartite=0)
+ G.add_node(2, bipartite=1)
+ G.add_node(3, bipartite=0)
+ bipartite.write_edgelist(G, fh, data=True)
+ fh.seek(0)
+ assert fh.read() == b"1 2 {'weight': 2.0}\n3 2 {'weight': 3.0}\n"
+
+ def test_write_edgelist_4(self):
+ fh = io.BytesIO()
+ G = nx.Graph()
+ G.add_edge(1, 2, weight=2.0)
+ G.add_edge(2, 3, weight=3.0)
+ G.add_node(1, bipartite=0)
+ G.add_node(2, bipartite=1)
+ G.add_node(3, bipartite=0)
+ bipartite.write_edgelist(G, fh, data=[("weight")])
+ fh.seek(0)
+ assert fh.read() == b"1 2 2.0\n3 2 3.0\n"
+
+ def test_unicode(self, tmp_path):
+ G = nx.Graph()
+ name1 = chr(2344) + chr(123) + chr(6543)
+ name2 = chr(5543) + chr(1543) + chr(324)
+ G.add_edge(name1, "Radiohead", **{name2: 3})
+ G.add_node(name1, bipartite=0)
+ G.add_node("Radiohead", bipartite=1)
+
+ fname = tmp_path / "edgelist.txt"
+ bipartite.write_edgelist(G, fname)
+ H = bipartite.read_edgelist(fname)
+ assert graphs_equal(G, H)
+
+ def test_latin1_issue(self, tmp_path):
+ G = nx.Graph()
+ name1 = chr(2344) + chr(123) + chr(6543)
+ name2 = chr(5543) + chr(1543) + chr(324)
+ G.add_edge(name1, "Radiohead", **{name2: 3})
+ G.add_node(name1, bipartite=0)
+ G.add_node("Radiohead", bipartite=1)
+
+ fname = tmp_path / "edgelist.txt"
+ with pytest.raises(UnicodeEncodeError):
+ bipartite.write_edgelist(G, fname, encoding="latin-1")
+
+ def test_latin1(self, tmp_path):
+ G = nx.Graph()
+ name1 = "Bj" + chr(246) + "rk"
+ name2 = chr(220) + "ber"
+ G.add_edge(name1, "Radiohead", **{name2: 3})
+ G.add_node(name1, bipartite=0)
+ G.add_node("Radiohead", bipartite=1)
+
+ fname = tmp_path / "edgelist.txt"
+ bipartite.write_edgelist(G, fname, encoding="latin-1")
+ H = bipartite.read_edgelist(fname, encoding="latin-1")
+ assert graphs_equal(G, H)
+
+ def test_edgelist_graph(self, tmp_path):
+ G = self.G
+ fname = tmp_path / "edgelist.txt"
+ bipartite.write_edgelist(G, fname)
+ H = bipartite.read_edgelist(fname)
+ H2 = bipartite.read_edgelist(fname)
+ assert H is not H2 # they should be different graphs
+ G.remove_node("g") # isolated nodes are not written in edgelist
+ assert nodes_equal(list(H), list(G))
+ assert edges_equal(list(H.edges()), list(G.edges()))
+
+ def test_edgelist_integers(self, tmp_path):
+ G = nx.convert_node_labels_to_integers(self.G)
+ fname = tmp_path / "edgelist.txt"
+ bipartite.write_edgelist(G, fname)
+ H = bipartite.read_edgelist(fname, nodetype=int)
+ # isolated nodes are not written in edgelist
+ G.remove_nodes_from(list(nx.isolates(G)))
+ assert nodes_equal(list(H), list(G))
+ assert edges_equal(list(H.edges()), list(G.edges()))
+
+ def test_edgelist_multigraph(self, tmp_path):
+ G = self.MG
+ fname = tmp_path / "edgelist.txt"
+ bipartite.write_edgelist(G, fname)
+ H = bipartite.read_edgelist(fname, nodetype=int, create_using=nx.MultiGraph())
+ H2 = bipartite.read_edgelist(fname, nodetype=int, create_using=nx.MultiGraph())
+ assert H is not H2 # they should be different graphs
+ assert nodes_equal(list(H), list(G))
+ assert edges_equal(list(H.edges()), list(G.edges()))
+
+ def test_empty_digraph(self):
+ with pytest.raises(nx.NetworkXNotImplemented):
+ bytesIO = io.BytesIO()
+ bipartite.write_edgelist(nx.DiGraph(), bytesIO)
+
+ def test_raise_attribute(self):
+ with pytest.raises(AttributeError):
+ G = nx.path_graph(4)
+ bytesIO = io.BytesIO()
+ bipartite.write_edgelist(G, bytesIO)
+
+ def test_parse_edgelist(self):
+ """Tests for conditions specific to
+ parse_edge_list method"""
+
+ # ignore strings of length less than 2
+ lines = ["1 2", "2 3", "3 1", "4", " "]
+ G = bipartite.parse_edgelist(lines, nodetype=int)
+ assert list(G.nodes) == [1, 2, 3]
+
+ # Exception raised when node is not convertible
+ # to specified data type
+ with pytest.raises(TypeError, match=".*Failed to convert nodes"):
+ lines = ["a b", "b c", "c a"]
+ G = bipartite.parse_edgelist(lines, nodetype=int)
+
+ # Exception raised when format of data is not
+ # convertible to dictionary object
+ with pytest.raises(TypeError, match=".*Failed to convert edge data"):
+ lines = ["1 2 3", "2 3 4", "3 1 2"]
+ G = bipartite.parse_edgelist(lines, nodetype=int)
+
+ # Exception raised when edge data and data
+ # keys are not of same length
+ with pytest.raises(IndexError):
+ lines = ["1 2 3 4", "2 3 4"]
+ G = bipartite.parse_edgelist(
+ lines, nodetype=int, data=[("weight", int), ("key", int)]
+ )
+
+ # Exception raised when edge data is not
+ # convertible to specified data type
+ with pytest.raises(TypeError, match=".*Failed to convert key data"):
+ lines = ["1 2 3 a", "2 3 4 b"]
+ G = bipartite.parse_edgelist(
+ lines, nodetype=int, data=[("weight", int), ("key", int)]
+ )
+
+
+def test_bipartite_edgelist_consistent_strip_handling():
+ """See gh-7462
+
+ Input when printed looks like:
+
+ A B interaction 2
+ B C interaction 4
+ C A interaction
+
+ Note the trailing \\t in the last line, which indicates the existence of
+ an empty data field.
+ """
+ lines = io.StringIO(
+ "A\tB\tinteraction\t2\nB\tC\tinteraction\t4\nC\tA\tinteraction\t"
+ )
+ descr = [("type", str), ("weight", str)]
+ # Should not raise
+ G = nx.bipartite.parse_edgelist(lines, delimiter="\t", data=descr)
+ expected = [("A", "B", "2"), ("A", "C", ""), ("B", "C", "4")]
+ assert sorted(G.edges(data="weight")) == expected
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_extendability.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_extendability.py
new file mode 100644
index 00000000..17b71243
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_extendability.py
@@ -0,0 +1,334 @@
+import pytest
+
+import networkx as nx
+
+
+def test_selfloops_raises():
+ G = nx.ladder_graph(3)
+ G.add_edge(0, 0)
+ with pytest.raises(nx.NetworkXError, match=".*not bipartite"):
+ nx.bipartite.maximal_extendability(G)
+
+
+def test_disconnected_raises():
+ G = nx.ladder_graph(3)
+ G.add_node("a")
+ with pytest.raises(nx.NetworkXError, match=".*not connected"):
+ nx.bipartite.maximal_extendability(G)
+
+
+def test_not_bipartite_raises():
+ G = nx.complete_graph(5)
+ with pytest.raises(nx.NetworkXError, match=".*not bipartite"):
+ nx.bipartite.maximal_extendability(G)
+
+
+def test_no_perfect_matching_raises():
+ G = nx.Graph([(0, 1), (0, 2)])
+ with pytest.raises(nx.NetworkXError, match=".*not contain a perfect matching"):
+ nx.bipartite.maximal_extendability(G)
+
+
+def test_residual_graph_not_strongly_connected_raises():
+ G = nx.Graph([(1, 2), (2, 3), (3, 4)])
+ with pytest.raises(
+ nx.NetworkXError, match="The residual graph of G is not strongly connected"
+ ):
+ nx.bipartite.maximal_extendability(G)
+
+
+def test_ladder_graph_is_1():
+ G = nx.ladder_graph(3)
+ assert nx.bipartite.maximal_extendability(G) == 1
+
+
+def test_cubical_graph_is_2():
+ G = nx.cubical_graph()
+ assert nx.bipartite.maximal_extendability(G) == 2
+
+
+def test_k_is_3():
+ G = nx.Graph(
+ [
+ (1, 6),
+ (1, 7),
+ (1, 8),
+ (1, 9),
+ (2, 6),
+ (2, 7),
+ (2, 8),
+ (2, 10),
+ (3, 6),
+ (3, 8),
+ (3, 9),
+ (3, 10),
+ (4, 7),
+ (4, 8),
+ (4, 9),
+ (4, 10),
+ (5, 6),
+ (5, 7),
+ (5, 9),
+ (5, 10),
+ ]
+ )
+ assert nx.bipartite.maximal_extendability(G) == 3
+
+
+def test_k_is_4():
+ G = nx.Graph(
+ [
+ (8, 1),
+ (8, 2),
+ (8, 3),
+ (8, 4),
+ (8, 5),
+ (9, 1),
+ (9, 2),
+ (9, 3),
+ (9, 4),
+ (9, 7),
+ (10, 1),
+ (10, 2),
+ (10, 3),
+ (10, 4),
+ (10, 6),
+ (11, 1),
+ (11, 2),
+ (11, 5),
+ (11, 6),
+ (11, 7),
+ (12, 1),
+ (12, 3),
+ (12, 5),
+ (12, 6),
+ (12, 7),
+ (13, 2),
+ (13, 4),
+ (13, 5),
+ (13, 6),
+ (13, 7),
+ (14, 3),
+ (14, 4),
+ (14, 5),
+ (14, 6),
+ (14, 7),
+ ]
+ )
+ assert nx.bipartite.maximal_extendability(G) == 4
+
+
+def test_k_is_5():
+ G = nx.Graph(
+ [
+ (8, 1),
+ (8, 2),
+ (8, 3),
+ (8, 4),
+ (8, 5),
+ (8, 6),
+ (9, 1),
+ (9, 2),
+ (9, 3),
+ (9, 4),
+ (9, 5),
+ (9, 7),
+ (10, 1),
+ (10, 2),
+ (10, 3),
+ (10, 4),
+ (10, 6),
+ (10, 7),
+ (11, 1),
+ (11, 2),
+ (11, 3),
+ (11, 5),
+ (11, 6),
+ (11, 7),
+ (12, 1),
+ (12, 2),
+ (12, 4),
+ (12, 5),
+ (12, 6),
+ (12, 7),
+ (13, 1),
+ (13, 3),
+ (13, 4),
+ (13, 5),
+ (13, 6),
+ (13, 7),
+ (14, 2),
+ (14, 3),
+ (14, 4),
+ (14, 5),
+ (14, 6),
+ (14, 7),
+ ]
+ )
+ assert nx.bipartite.maximal_extendability(G) == 5
+
+
+def test_k_is_6():
+ G = nx.Graph(
+ [
+ (9, 1),
+ (9, 2),
+ (9, 3),
+ (9, 4),
+ (9, 5),
+ (9, 6),
+ (9, 7),
+ (10, 1),
+ (10, 2),
+ (10, 3),
+ (10, 4),
+ (10, 5),
+ (10, 6),
+ (10, 8),
+ (11, 1),
+ (11, 2),
+ (11, 3),
+ (11, 4),
+ (11, 5),
+ (11, 7),
+ (11, 8),
+ (12, 1),
+ (12, 2),
+ (12, 3),
+ (12, 4),
+ (12, 6),
+ (12, 7),
+ (12, 8),
+ (13, 1),
+ (13, 2),
+ (13, 3),
+ (13, 5),
+ (13, 6),
+ (13, 7),
+ (13, 8),
+ (14, 1),
+ (14, 2),
+ (14, 4),
+ (14, 5),
+ (14, 6),
+ (14, 7),
+ (14, 8),
+ (15, 1),
+ (15, 3),
+ (15, 4),
+ (15, 5),
+ (15, 6),
+ (15, 7),
+ (15, 8),
+ (16, 2),
+ (16, 3),
+ (16, 4),
+ (16, 5),
+ (16, 6),
+ (16, 7),
+ (16, 8),
+ ]
+ )
+ assert nx.bipartite.maximal_extendability(G) == 6
+
+
+def test_k_is_7():
+ G = nx.Graph(
+ [
+ (1, 11),
+ (1, 12),
+ (1, 13),
+ (1, 14),
+ (1, 15),
+ (1, 16),
+ (1, 17),
+ (1, 18),
+ (2, 11),
+ (2, 12),
+ (2, 13),
+ (2, 14),
+ (2, 15),
+ (2, 16),
+ (2, 17),
+ (2, 19),
+ (3, 11),
+ (3, 12),
+ (3, 13),
+ (3, 14),
+ (3, 15),
+ (3, 16),
+ (3, 17),
+ (3, 20),
+ (4, 11),
+ (4, 12),
+ (4, 13),
+ (4, 14),
+ (4, 15),
+ (4, 16),
+ (4, 17),
+ (4, 18),
+ (4, 19),
+ (4, 20),
+ (5, 11),
+ (5, 12),
+ (5, 13),
+ (5, 14),
+ (5, 15),
+ (5, 16),
+ (5, 17),
+ (5, 18),
+ (5, 19),
+ (5, 20),
+ (6, 11),
+ (6, 12),
+ (6, 13),
+ (6, 14),
+ (6, 15),
+ (6, 16),
+ (6, 17),
+ (6, 18),
+ (6, 19),
+ (6, 20),
+ (7, 11),
+ (7, 12),
+ (7, 13),
+ (7, 14),
+ (7, 15),
+ (7, 16),
+ (7, 17),
+ (7, 18),
+ (7, 19),
+ (7, 20),
+ (8, 11),
+ (8, 12),
+ (8, 13),
+ (8, 14),
+ (8, 15),
+ (8, 16),
+ (8, 17),
+ (8, 18),
+ (8, 19),
+ (8, 20),
+ (9, 11),
+ (9, 12),
+ (9, 13),
+ (9, 14),
+ (9, 15),
+ (9, 16),
+ (9, 17),
+ (9, 18),
+ (9, 19),
+ (9, 20),
+ (10, 11),
+ (10, 12),
+ (10, 13),
+ (10, 14),
+ (10, 15),
+ (10, 16),
+ (10, 17),
+ (10, 18),
+ (10, 19),
+ (10, 20),
+ ]
+ )
+ assert nx.bipartite.maximal_extendability(G) == 7
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_generators.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_generators.py
new file mode 100644
index 00000000..8b1e7793
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_generators.py
@@ -0,0 +1,409 @@
+import numbers
+
+import pytest
+
+import networkx as nx
+
+from ..generators import (
+ alternating_havel_hakimi_graph,
+ complete_bipartite_graph,
+ configuration_model,
+ gnmk_random_graph,
+ havel_hakimi_graph,
+ preferential_attachment_graph,
+ random_graph,
+ reverse_havel_hakimi_graph,
+)
+
+"""
+Generators - Bipartite
+----------------------
+"""
+
+
+class TestGeneratorsBipartite:
+ def test_complete_bipartite_graph(self):
+ G = complete_bipartite_graph(0, 0)
+ assert nx.is_isomorphic(G, nx.null_graph())
+
+ for i in [1, 5]:
+ G = complete_bipartite_graph(i, 0)
+ assert nx.is_isomorphic(G, nx.empty_graph(i))
+ G = complete_bipartite_graph(0, i)
+ assert nx.is_isomorphic(G, nx.empty_graph(i))
+
+ G = complete_bipartite_graph(2, 2)
+ assert nx.is_isomorphic(G, nx.cycle_graph(4))
+
+ G = complete_bipartite_graph(1, 5)
+ assert nx.is_isomorphic(G, nx.star_graph(5))
+
+ G = complete_bipartite_graph(5, 1)
+ assert nx.is_isomorphic(G, nx.star_graph(5))
+
+ # complete_bipartite_graph(m1,m2) is a connected graph with
+ # m1+m2 nodes and m1*m2 edges
+ for m1, m2 in [(5, 11), (7, 3)]:
+ G = complete_bipartite_graph(m1, m2)
+ assert nx.number_of_nodes(G) == m1 + m2
+ assert nx.number_of_edges(G) == m1 * m2
+
+ with pytest.raises(nx.NetworkXError):
+ complete_bipartite_graph(7, 3, create_using=nx.DiGraph)
+ with pytest.raises(nx.NetworkXError):
+ complete_bipartite_graph(7, 3, create_using=nx.MultiDiGraph)
+
+ mG = complete_bipartite_graph(7, 3, create_using=nx.MultiGraph)
+ assert mG.is_multigraph()
+ assert sorted(mG.edges()) == sorted(G.edges())
+
+ mG = complete_bipartite_graph(7, 3, create_using=nx.MultiGraph)
+ assert mG.is_multigraph()
+ assert sorted(mG.edges()) == sorted(G.edges())
+
+ mG = complete_bipartite_graph(7, 3) # default to Graph
+ assert sorted(mG.edges()) == sorted(G.edges())
+ assert not mG.is_multigraph()
+ assert not mG.is_directed()
+
+ # specify nodes rather than number of nodes
+ for n1, n2 in [([1, 2], "ab"), (3, 2), (3, "ab"), ("ab", 3)]:
+ G = complete_bipartite_graph(n1, n2)
+ if isinstance(n1, numbers.Integral):
+ if isinstance(n2, numbers.Integral):
+ n2 = range(n1, n1 + n2)
+ n1 = range(n1)
+ elif isinstance(n2, numbers.Integral):
+ n2 = range(n2)
+ edges = {(u, v) for u in n1 for v in n2}
+ assert edges == set(G.edges)
+ assert G.size() == len(edges)
+
+ # raise when node sets are not distinct
+ for n1, n2 in [([1, 2], 3), (3, [1, 2]), ("abc", "bcd")]:
+ pytest.raises(nx.NetworkXError, complete_bipartite_graph, n1, n2)
+
+ def test_configuration_model(self):
+ aseq = []
+ bseq = []
+ G = configuration_model(aseq, bseq)
+ assert len(G) == 0
+
+ aseq = [0, 0]
+ bseq = [0, 0]
+ G = configuration_model(aseq, bseq)
+ assert len(G) == 4
+ assert G.number_of_edges() == 0
+
+ aseq = [3, 3, 3, 3]
+ bseq = [2, 2, 2, 2, 2]
+ pytest.raises(nx.NetworkXError, configuration_model, aseq, bseq)
+
+ aseq = [3, 3, 3, 3]
+ bseq = [2, 2, 2, 2, 2, 2]
+ G = configuration_model(aseq, bseq)
+ assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
+
+ aseq = [2, 2, 2, 2, 2, 2]
+ bseq = [3, 3, 3, 3]
+ G = configuration_model(aseq, bseq)
+ assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
+
+ aseq = [2, 2, 2, 1, 1, 1]
+ bseq = [3, 3, 3]
+ G = configuration_model(aseq, bseq)
+ assert G.is_multigraph()
+ assert not G.is_directed()
+ assert sorted(d for n, d in G.degree()) == [1, 1, 1, 2, 2, 2, 3, 3, 3]
+
+ GU = nx.projected_graph(nx.Graph(G), range(len(aseq)))
+ assert GU.number_of_nodes() == 6
+
+ GD = nx.projected_graph(nx.Graph(G), range(len(aseq), len(aseq) + len(bseq)))
+ assert GD.number_of_nodes() == 3
+
+ G = reverse_havel_hakimi_graph(aseq, bseq, create_using=nx.Graph)
+ assert not G.is_multigraph()
+ assert not G.is_directed()
+
+ pytest.raises(
+ nx.NetworkXError, configuration_model, aseq, bseq, create_using=nx.DiGraph()
+ )
+ pytest.raises(
+ nx.NetworkXError, configuration_model, aseq, bseq, create_using=nx.DiGraph
+ )
+ pytest.raises(
+ nx.NetworkXError,
+ configuration_model,
+ aseq,
+ bseq,
+ create_using=nx.MultiDiGraph,
+ )
+
+ def test_havel_hakimi_graph(self):
+ aseq = []
+ bseq = []
+ G = havel_hakimi_graph(aseq, bseq)
+ assert len(G) == 0
+
+ aseq = [0, 0]
+ bseq = [0, 0]
+ G = havel_hakimi_graph(aseq, bseq)
+ assert len(G) == 4
+ assert G.number_of_edges() == 0
+
+ aseq = [3, 3, 3, 3]
+ bseq = [2, 2, 2, 2, 2]
+ pytest.raises(nx.NetworkXError, havel_hakimi_graph, aseq, bseq)
+
+ bseq = [2, 2, 2, 2, 2, 2]
+ G = havel_hakimi_graph(aseq, bseq)
+ assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
+
+ aseq = [2, 2, 2, 2, 2, 2]
+ bseq = [3, 3, 3, 3]
+ G = havel_hakimi_graph(aseq, bseq)
+ assert G.is_multigraph()
+ assert not G.is_directed()
+ assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
+
+ GU = nx.projected_graph(nx.Graph(G), range(len(aseq)))
+ assert GU.number_of_nodes() == 6
+
+ GD = nx.projected_graph(nx.Graph(G), range(len(aseq), len(aseq) + len(bseq)))
+ assert GD.number_of_nodes() == 4
+
+ G = reverse_havel_hakimi_graph(aseq, bseq, create_using=nx.Graph)
+ assert not G.is_multigraph()
+ assert not G.is_directed()
+
+ pytest.raises(
+ nx.NetworkXError, havel_hakimi_graph, aseq, bseq, create_using=nx.DiGraph
+ )
+ pytest.raises(
+ nx.NetworkXError, havel_hakimi_graph, aseq, bseq, create_using=nx.DiGraph
+ )
+ pytest.raises(
+ nx.NetworkXError,
+ havel_hakimi_graph,
+ aseq,
+ bseq,
+ create_using=nx.MultiDiGraph,
+ )
+
+ def test_reverse_havel_hakimi_graph(self):
+ aseq = []
+ bseq = []
+ G = reverse_havel_hakimi_graph(aseq, bseq)
+ assert len(G) == 0
+
+ aseq = [0, 0]
+ bseq = [0, 0]
+ G = reverse_havel_hakimi_graph(aseq, bseq)
+ assert len(G) == 4
+ assert G.number_of_edges() == 0
+
+ aseq = [3, 3, 3, 3]
+ bseq = [2, 2, 2, 2, 2]
+ pytest.raises(nx.NetworkXError, reverse_havel_hakimi_graph, aseq, bseq)
+
+ bseq = [2, 2, 2, 2, 2, 2]
+ G = reverse_havel_hakimi_graph(aseq, bseq)
+ assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
+
+ aseq = [2, 2, 2, 2, 2, 2]
+ bseq = [3, 3, 3, 3]
+ G = reverse_havel_hakimi_graph(aseq, bseq)
+ assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
+
+ aseq = [2, 2, 2, 1, 1, 1]
+ bseq = [3, 3, 3]
+ G = reverse_havel_hakimi_graph(aseq, bseq)
+ assert G.is_multigraph()
+ assert not G.is_directed()
+ assert sorted(d for n, d in G.degree()) == [1, 1, 1, 2, 2, 2, 3, 3, 3]
+
+ GU = nx.projected_graph(nx.Graph(G), range(len(aseq)))
+ assert GU.number_of_nodes() == 6
+
+ GD = nx.projected_graph(nx.Graph(G), range(len(aseq), len(aseq) + len(bseq)))
+ assert GD.number_of_nodes() == 3
+
+ G = reverse_havel_hakimi_graph(aseq, bseq, create_using=nx.Graph)
+ assert not G.is_multigraph()
+ assert not G.is_directed()
+
+ pytest.raises(
+ nx.NetworkXError,
+ reverse_havel_hakimi_graph,
+ aseq,
+ bseq,
+ create_using=nx.DiGraph,
+ )
+ pytest.raises(
+ nx.NetworkXError,
+ reverse_havel_hakimi_graph,
+ aseq,
+ bseq,
+ create_using=nx.DiGraph,
+ )
+ pytest.raises(
+ nx.NetworkXError,
+ reverse_havel_hakimi_graph,
+ aseq,
+ bseq,
+ create_using=nx.MultiDiGraph,
+ )
+
+ def test_alternating_havel_hakimi_graph(self):
+ aseq = []
+ bseq = []
+ G = alternating_havel_hakimi_graph(aseq, bseq)
+ assert len(G) == 0
+
+ aseq = [0, 0]
+ bseq = [0, 0]
+ G = alternating_havel_hakimi_graph(aseq, bseq)
+ assert len(G) == 4
+ assert G.number_of_edges() == 0
+
+ aseq = [3, 3, 3, 3]
+ bseq = [2, 2, 2, 2, 2]
+ pytest.raises(nx.NetworkXError, alternating_havel_hakimi_graph, aseq, bseq)
+
+ bseq = [2, 2, 2, 2, 2, 2]
+ G = alternating_havel_hakimi_graph(aseq, bseq)
+ assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
+
+ aseq = [2, 2, 2, 2, 2, 2]
+ bseq = [3, 3, 3, 3]
+ G = alternating_havel_hakimi_graph(aseq, bseq)
+ assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
+
+ aseq = [2, 2, 2, 1, 1, 1]
+ bseq = [3, 3, 3]
+ G = alternating_havel_hakimi_graph(aseq, bseq)
+ assert G.is_multigraph()
+ assert not G.is_directed()
+ assert sorted(d for n, d in G.degree()) == [1, 1, 1, 2, 2, 2, 3, 3, 3]
+
+ GU = nx.projected_graph(nx.Graph(G), range(len(aseq)))
+ assert GU.number_of_nodes() == 6
+
+ GD = nx.projected_graph(nx.Graph(G), range(len(aseq), len(aseq) + len(bseq)))
+ assert GD.number_of_nodes() == 3
+
+ G = reverse_havel_hakimi_graph(aseq, bseq, create_using=nx.Graph)
+ assert not G.is_multigraph()
+ assert not G.is_directed()
+
+ pytest.raises(
+ nx.NetworkXError,
+ alternating_havel_hakimi_graph,
+ aseq,
+ bseq,
+ create_using=nx.DiGraph,
+ )
+ pytest.raises(
+ nx.NetworkXError,
+ alternating_havel_hakimi_graph,
+ aseq,
+ bseq,
+ create_using=nx.DiGraph,
+ )
+ pytest.raises(
+ nx.NetworkXError,
+ alternating_havel_hakimi_graph,
+ aseq,
+ bseq,
+ create_using=nx.MultiDiGraph,
+ )
+
+ def test_preferential_attachment(self):
+ aseq = [3, 2, 1, 1]
+ G = preferential_attachment_graph(aseq, 0.5)
+ assert G.is_multigraph()
+ assert not G.is_directed()
+
+ G = preferential_attachment_graph(aseq, 0.5, create_using=nx.Graph)
+ assert not G.is_multigraph()
+ assert not G.is_directed()
+
+ pytest.raises(
+ nx.NetworkXError,
+ preferential_attachment_graph,
+ aseq,
+ 0.5,
+ create_using=nx.DiGraph(),
+ )
+ pytest.raises(
+ nx.NetworkXError,
+ preferential_attachment_graph,
+ aseq,
+ 0.5,
+ create_using=nx.DiGraph(),
+ )
+ pytest.raises(
+ nx.NetworkXError,
+ preferential_attachment_graph,
+ aseq,
+ 0.5,
+ create_using=nx.DiGraph(),
+ )
+
+ def test_random_graph(self):
+ n = 10
+ m = 20
+ G = random_graph(n, m, 0.9)
+ assert len(G) == 30
+ assert nx.is_bipartite(G)
+ X, Y = nx.algorithms.bipartite.sets(G)
+ assert set(range(n)) == X
+ assert set(range(n, n + m)) == Y
+
+ def test_random_digraph(self):
+ n = 10
+ m = 20
+ G = random_graph(n, m, 0.9, directed=True)
+ assert len(G) == 30
+ assert nx.is_bipartite(G)
+ X, Y = nx.algorithms.bipartite.sets(G)
+ assert set(range(n)) == X
+ assert set(range(n, n + m)) == Y
+
+ def test_gnmk_random_graph(self):
+ n = 10
+ m = 20
+ edges = 100
+ # set seed because sometimes it is not connected
+ # which raises an error in bipartite.sets(G) below.
+ G = gnmk_random_graph(n, m, edges, seed=1234)
+ assert len(G) == n + m
+ assert nx.is_bipartite(G)
+ X, Y = nx.algorithms.bipartite.sets(G)
+ # print(X)
+ assert set(range(n)) == X
+ assert set(range(n, n + m)) == Y
+ assert edges == len(list(G.edges()))
+
+ def test_gnmk_random_graph_complete(self):
+ n = 10
+ m = 20
+ edges = 200
+ G = gnmk_random_graph(n, m, edges)
+ assert len(G) == n + m
+ assert nx.is_bipartite(G)
+ X, Y = nx.algorithms.bipartite.sets(G)
+ # print(X)
+ assert set(range(n)) == X
+ assert set(range(n, n + m)) == Y
+ assert edges == len(list(G.edges()))
+
+ @pytest.mark.parametrize("n", (4, range(4), {0, 1, 2, 3}))
+ @pytest.mark.parametrize("m", (range(4, 7), {4, 5, 6}))
+ def test_complete_bipartite_graph_str(self, n, m):
+ """Ensure G.name is consistent for all inputs accepted by nodes_or_number.
+ See gh-7396"""
+ G = nx.complete_bipartite_graph(n, m)
+ ans = "Graph named 'complete_bipartite_graph(4, 3)' with 7 nodes and 12 edges"
+ assert str(G) == ans
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matching.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matching.py
new file mode 100644
index 00000000..c24659ea
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matching.py
@@ -0,0 +1,327 @@
+"""Unit tests for the :mod:`networkx.algorithms.bipartite.matching` module."""
+
+import itertools
+
+import pytest
+
+import networkx as nx
+from networkx.algorithms.bipartite.matching import (
+ eppstein_matching,
+ hopcroft_karp_matching,
+ maximum_matching,
+ minimum_weight_full_matching,
+ to_vertex_cover,
+)
+
+
+class TestMatching:
+ """Tests for bipartite matching algorithms."""
+
+ def setup_method(self):
+ """Creates a bipartite graph for use in testing matching algorithms.
+
+ The bipartite graph has a maximum cardinality matching that leaves
+ vertex 1 and vertex 10 unmatched. The first six numbers are the left
+ vertices and the next six numbers are the right vertices.
+
+ """
+ self.simple_graph = nx.complete_bipartite_graph(2, 3)
+ self.simple_solution = {0: 2, 1: 3, 2: 0, 3: 1}
+
+ edges = [(0, 7), (0, 8), (2, 6), (2, 9), (3, 8), (4, 8), (4, 9), (5, 11)]
+ self.top_nodes = set(range(6))
+ self.graph = nx.Graph()
+ self.graph.add_nodes_from(range(12))
+ self.graph.add_edges_from(edges)
+
+ # Example bipartite graph from issue 2127
+ G = nx.Graph()
+ G.add_nodes_from(
+ [
+ (1, "C"),
+ (1, "B"),
+ (0, "G"),
+ (1, "F"),
+ (1, "E"),
+ (0, "C"),
+ (1, "D"),
+ (1, "I"),
+ (0, "A"),
+ (0, "D"),
+ (0, "F"),
+ (0, "E"),
+ (0, "H"),
+ (1, "G"),
+ (1, "A"),
+ (0, "I"),
+ (0, "B"),
+ (1, "H"),
+ ]
+ )
+ G.add_edge((1, "C"), (0, "A"))
+ G.add_edge((1, "B"), (0, "A"))
+ G.add_edge((0, "G"), (1, "I"))
+ G.add_edge((0, "G"), (1, "H"))
+ G.add_edge((1, "F"), (0, "A"))
+ G.add_edge((1, "F"), (0, "C"))
+ G.add_edge((1, "F"), (0, "E"))
+ G.add_edge((1, "E"), (0, "A"))
+ G.add_edge((1, "E"), (0, "C"))
+ G.add_edge((0, "C"), (1, "D"))
+ G.add_edge((0, "C"), (1, "I"))
+ G.add_edge((0, "C"), (1, "G"))
+ G.add_edge((0, "C"), (1, "H"))
+ G.add_edge((1, "D"), (0, "A"))
+ G.add_edge((1, "I"), (0, "A"))
+ G.add_edge((1, "I"), (0, "E"))
+ G.add_edge((0, "A"), (1, "G"))
+ G.add_edge((0, "A"), (1, "H"))
+ G.add_edge((0, "E"), (1, "G"))
+ G.add_edge((0, "E"), (1, "H"))
+ self.disconnected_graph = G
+
+ def check_match(self, matching):
+ """Asserts that the matching is what we expect from the bipartite graph
+ constructed in the :meth:`setup` fixture.
+
+ """
+ # For the sake of brevity, rename `matching` to `M`.
+ M = matching
+ matched_vertices = frozenset(itertools.chain(*M.items()))
+ # Assert that the maximum number of vertices (10) is matched.
+ assert matched_vertices == frozenset(range(12)) - {1, 10}
+ # Assert that no vertex appears in two edges, or in other words, that
+ # the matching (u, v) and (v, u) both appear in the matching
+ # dictionary.
+ assert all(u == M[M[u]] for u in range(12) if u in M)
+
+ def check_vertex_cover(self, vertices):
+ """Asserts that the given set of vertices is the vertex cover we
+ expected from the bipartite graph constructed in the :meth:`setup`
+ fixture.
+
+ """
+ # By Konig's theorem, the number of edges in a maximum matching equals
+ # the number of vertices in a minimum vertex cover.
+ assert len(vertices) == 5
+ # Assert that the set is truly a vertex cover.
+ for u, v in self.graph.edges():
+ assert u in vertices or v in vertices
+ # TODO Assert that the vertices are the correct ones.
+
+ def test_eppstein_matching(self):
+ """Tests that David Eppstein's implementation of the Hopcroft--Karp
+ algorithm produces a maximum cardinality matching.
+
+ """
+ self.check_match(eppstein_matching(self.graph, self.top_nodes))
+
+ def test_hopcroft_karp_matching(self):
+ """Tests that the Hopcroft--Karp algorithm produces a maximum
+ cardinality matching in a bipartite graph.
+
+ """
+ self.check_match(hopcroft_karp_matching(self.graph, self.top_nodes))
+
+ def test_to_vertex_cover(self):
+ """Test for converting a maximum matching to a minimum vertex cover."""
+ matching = maximum_matching(self.graph, self.top_nodes)
+ vertex_cover = to_vertex_cover(self.graph, matching, self.top_nodes)
+ self.check_vertex_cover(vertex_cover)
+
+ def test_eppstein_matching_simple(self):
+ match = eppstein_matching(self.simple_graph)
+ assert match == self.simple_solution
+
+ def test_hopcroft_karp_matching_simple(self):
+ match = hopcroft_karp_matching(self.simple_graph)
+ assert match == self.simple_solution
+
+ def test_eppstein_matching_disconnected(self):
+ with pytest.raises(nx.AmbiguousSolution):
+ match = eppstein_matching(self.disconnected_graph)
+
+ def test_hopcroft_karp_matching_disconnected(self):
+ with pytest.raises(nx.AmbiguousSolution):
+ match = hopcroft_karp_matching(self.disconnected_graph)
+
+ def test_issue_2127(self):
+ """Test from issue 2127"""
+ # Build the example DAG
+ G = nx.DiGraph()
+ G.add_edge("A", "C")
+ G.add_edge("A", "B")
+ G.add_edge("C", "E")
+ G.add_edge("C", "D")
+ G.add_edge("E", "G")
+ G.add_edge("E", "F")
+ G.add_edge("G", "I")
+ G.add_edge("G", "H")
+
+ tc = nx.transitive_closure(G)
+ btc = nx.Graph()
+
+ # Create a bipartite graph based on the transitive closure of G
+ for v in tc.nodes():
+ btc.add_node((0, v))
+ btc.add_node((1, v))
+
+ for u, v in tc.edges():
+ btc.add_edge((0, u), (1, v))
+
+ top_nodes = {n for n in btc if n[0] == 0}
+ matching = hopcroft_karp_matching(btc, top_nodes)
+ vertex_cover = to_vertex_cover(btc, matching, top_nodes)
+ independent_set = set(G) - {v for _, v in vertex_cover}
+ assert {"B", "D", "F", "I", "H"} == independent_set
+
+ def test_vertex_cover_issue_2384(self):
+ G = nx.Graph([(0, 3), (1, 3), (1, 4), (2, 3)])
+ matching = maximum_matching(G)
+ vertex_cover = to_vertex_cover(G, matching)
+ for u, v in G.edges():
+ assert u in vertex_cover or v in vertex_cover
+
+ def test_vertex_cover_issue_3306(self):
+ G = nx.Graph()
+ edges = [(0, 2), (1, 0), (1, 1), (1, 2), (2, 2)]
+ G.add_edges_from([((i, "L"), (j, "R")) for i, j in edges])
+
+ matching = maximum_matching(G)
+ vertex_cover = to_vertex_cover(G, matching)
+ for u, v in G.edges():
+ assert u in vertex_cover or v in vertex_cover
+
+ def test_unorderable_nodes(self):
+ a = object()
+ b = object()
+ c = object()
+ d = object()
+ e = object()
+ G = nx.Graph([(a, d), (b, d), (b, e), (c, d)])
+ matching = maximum_matching(G)
+ vertex_cover = to_vertex_cover(G, matching)
+ for u, v in G.edges():
+ assert u in vertex_cover or v in vertex_cover
+
+
+def test_eppstein_matching():
+ """Test in accordance to issue #1927"""
+ G = nx.Graph()
+ G.add_nodes_from(["a", 2, 3, 4], bipartite=0)
+ G.add_nodes_from([1, "b", "c"], bipartite=1)
+ G.add_edges_from([("a", 1), ("a", "b"), (2, "b"), (2, "c"), (3, "c"), (4, 1)])
+ matching = eppstein_matching(G)
+ assert len(matching) == len(maximum_matching(G))
+ assert all(x in set(matching.keys()) for x in set(matching.values()))
+
+
+class TestMinimumWeightFullMatching:
+ @classmethod
+ def setup_class(cls):
+ pytest.importorskip("scipy")
+
+ def test_minimum_weight_full_matching_incomplete_graph(self):
+ B = nx.Graph()
+ B.add_nodes_from([1, 2], bipartite=0)
+ B.add_nodes_from([3, 4], bipartite=1)
+ B.add_edge(1, 4, weight=100)
+ B.add_edge(2, 3, weight=100)
+ B.add_edge(2, 4, weight=50)
+ matching = minimum_weight_full_matching(B)
+ assert matching == {1: 4, 2: 3, 4: 1, 3: 2}
+
+ def test_minimum_weight_full_matching_with_no_full_matching(self):
+ B = nx.Graph()
+ B.add_nodes_from([1, 2, 3], bipartite=0)
+ B.add_nodes_from([4, 5, 6], bipartite=1)
+ B.add_edge(1, 4, weight=100)
+ B.add_edge(2, 4, weight=100)
+ B.add_edge(3, 4, weight=50)
+ B.add_edge(3, 5, weight=50)
+ B.add_edge(3, 6, weight=50)
+ with pytest.raises(ValueError):
+ minimum_weight_full_matching(B)
+
+ def test_minimum_weight_full_matching_square(self):
+ G = nx.complete_bipartite_graph(3, 3)
+ G.add_edge(0, 3, weight=400)
+ G.add_edge(0, 4, weight=150)
+ G.add_edge(0, 5, weight=400)
+ G.add_edge(1, 3, weight=400)
+ G.add_edge(1, 4, weight=450)
+ G.add_edge(1, 5, weight=600)
+ G.add_edge(2, 3, weight=300)
+ G.add_edge(2, 4, weight=225)
+ G.add_edge(2, 5, weight=300)
+ matching = minimum_weight_full_matching(G)
+ assert matching == {0: 4, 1: 3, 2: 5, 4: 0, 3: 1, 5: 2}
+
+ def test_minimum_weight_full_matching_smaller_left(self):
+ G = nx.complete_bipartite_graph(3, 4)
+ G.add_edge(0, 3, weight=400)
+ G.add_edge(0, 4, weight=150)
+ G.add_edge(0, 5, weight=400)
+ G.add_edge(0, 6, weight=1)
+ G.add_edge(1, 3, weight=400)
+ G.add_edge(1, 4, weight=450)
+ G.add_edge(1, 5, weight=600)
+ G.add_edge(1, 6, weight=2)
+ G.add_edge(2, 3, weight=300)
+ G.add_edge(2, 4, weight=225)
+ G.add_edge(2, 5, weight=290)
+ G.add_edge(2, 6, weight=3)
+ matching = minimum_weight_full_matching(G)
+ assert matching == {0: 4, 1: 6, 2: 5, 4: 0, 5: 2, 6: 1}
+
+ def test_minimum_weight_full_matching_smaller_top_nodes_right(self):
+ G = nx.complete_bipartite_graph(3, 4)
+ G.add_edge(0, 3, weight=400)
+ G.add_edge(0, 4, weight=150)
+ G.add_edge(0, 5, weight=400)
+ G.add_edge(0, 6, weight=1)
+ G.add_edge(1, 3, weight=400)
+ G.add_edge(1, 4, weight=450)
+ G.add_edge(1, 5, weight=600)
+ G.add_edge(1, 6, weight=2)
+ G.add_edge(2, 3, weight=300)
+ G.add_edge(2, 4, weight=225)
+ G.add_edge(2, 5, weight=290)
+ G.add_edge(2, 6, weight=3)
+ matching = minimum_weight_full_matching(G, top_nodes=[3, 4, 5, 6])
+ assert matching == {0: 4, 1: 6, 2: 5, 4: 0, 5: 2, 6: 1}
+
+ def test_minimum_weight_full_matching_smaller_right(self):
+ G = nx.complete_bipartite_graph(4, 3)
+ G.add_edge(0, 4, weight=400)
+ G.add_edge(0, 5, weight=400)
+ G.add_edge(0, 6, weight=300)
+ G.add_edge(1, 4, weight=150)
+ G.add_edge(1, 5, weight=450)
+ G.add_edge(1, 6, weight=225)
+ G.add_edge(2, 4, weight=400)
+ G.add_edge(2, 5, weight=600)
+ G.add_edge(2, 6, weight=290)
+ G.add_edge(3, 4, weight=1)
+ G.add_edge(3, 5, weight=2)
+ G.add_edge(3, 6, weight=3)
+ matching = minimum_weight_full_matching(G)
+ assert matching == {1: 4, 2: 6, 3: 5, 4: 1, 5: 3, 6: 2}
+
+ def test_minimum_weight_full_matching_negative_weights(self):
+ G = nx.complete_bipartite_graph(2, 2)
+ G.add_edge(0, 2, weight=-2)
+ G.add_edge(0, 3, weight=0.2)
+ G.add_edge(1, 2, weight=-2)
+ G.add_edge(1, 3, weight=0.3)
+ matching = minimum_weight_full_matching(G)
+ assert matching == {0: 3, 1: 2, 2: 1, 3: 0}
+
+ def test_minimum_weight_full_matching_different_weight_key(self):
+ G = nx.complete_bipartite_graph(2, 2)
+ G.add_edge(0, 2, mass=2)
+ G.add_edge(0, 3, mass=0.2)
+ G.add_edge(1, 2, mass=1)
+ G.add_edge(1, 3, mass=2)
+ matching = minimum_weight_full_matching(G, weight="mass")
+ assert matching == {0: 3, 1: 2, 2: 1, 3: 0}
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matrix.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matrix.py
new file mode 100644
index 00000000..53d83115
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matrix.py
@@ -0,0 +1,84 @@
+import pytest
+
+np = pytest.importorskip("numpy")
+sp = pytest.importorskip("scipy")
+sparse = pytest.importorskip("scipy.sparse")
+
+
+import networkx as nx
+from networkx.algorithms import bipartite
+from networkx.utils import edges_equal
+
+
+class TestBiadjacencyMatrix:
+ def test_biadjacency_matrix_weight(self):
+ G = nx.path_graph(5)
+ G.add_edge(0, 1, weight=2, other=4)
+ X = [1, 3]
+ Y = [0, 2, 4]
+ M = bipartite.biadjacency_matrix(G, X, weight="weight")
+ assert M[0, 0] == 2
+ M = bipartite.biadjacency_matrix(G, X, weight="other")
+ assert M[0, 0] == 4
+
+ def test_biadjacency_matrix(self):
+ tops = [2, 5, 10]
+ bots = [5, 10, 15]
+ for i in range(len(tops)):
+ G = bipartite.random_graph(tops[i], bots[i], 0.2)
+ top = [n for n, d in G.nodes(data=True) if d["bipartite"] == 0]
+ M = bipartite.biadjacency_matrix(G, top)
+ assert M.shape[0] == tops[i]
+ assert M.shape[1] == bots[i]
+
+ def test_biadjacency_matrix_order(self):
+ G = nx.path_graph(5)
+ G.add_edge(0, 1, weight=2)
+ X = [3, 1]
+ Y = [4, 2, 0]
+ M = bipartite.biadjacency_matrix(G, X, Y, weight="weight")
+ assert M[1, 2] == 2
+
+ def test_biadjacency_matrix_empty_graph(self):
+ G = nx.empty_graph(2)
+ M = nx.bipartite.biadjacency_matrix(G, [0])
+ assert np.array_equal(M.toarray(), np.array([[0]]))
+
+ def test_null_graph(self):
+ with pytest.raises(nx.NetworkXError):
+ bipartite.biadjacency_matrix(nx.Graph(), [])
+
+ def test_empty_graph(self):
+ with pytest.raises(nx.NetworkXError):
+ bipartite.biadjacency_matrix(nx.Graph([(1, 0)]), [])
+
+ def test_duplicate_row(self):
+ with pytest.raises(nx.NetworkXError):
+ bipartite.biadjacency_matrix(nx.Graph([(1, 0)]), [1, 1])
+
+ def test_duplicate_col(self):
+ with pytest.raises(nx.NetworkXError):
+ bipartite.biadjacency_matrix(nx.Graph([(1, 0)]), [0], [1, 1])
+
+ def test_format_keyword(self):
+ with pytest.raises(nx.NetworkXError):
+ bipartite.biadjacency_matrix(nx.Graph([(1, 0)]), [0], format="foo")
+
+ def test_from_biadjacency_roundtrip(self):
+ B1 = nx.path_graph(5)
+ M = bipartite.biadjacency_matrix(B1, [0, 2, 4])
+ B2 = bipartite.from_biadjacency_matrix(M)
+ assert nx.is_isomorphic(B1, B2)
+
+ def test_from_biadjacency_weight(self):
+ M = sparse.csc_matrix([[1, 2], [0, 3]])
+ B = bipartite.from_biadjacency_matrix(M)
+ assert edges_equal(B.edges(), [(0, 2), (0, 3), (1, 3)])
+ B = bipartite.from_biadjacency_matrix(M, edge_attribute="weight")
+ e = [(0, 2, {"weight": 1}), (0, 3, {"weight": 2}), (1, 3, {"weight": 3})]
+ assert edges_equal(B.edges(data=True), e)
+
+ def test_from_biadjacency_multigraph(self):
+ M = sparse.csc_matrix([[1, 2], [0, 3]])
+ B = bipartite.from_biadjacency_matrix(M, create_using=nx.MultiGraph())
+ assert edges_equal(B.edges(), [(0, 2), (0, 3), (0, 3), (1, 3), (1, 3), (1, 3)])
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_project.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_project.py
new file mode 100644
index 00000000..076bb42b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_project.py
@@ -0,0 +1,407 @@
+import pytest
+
+import networkx as nx
+from networkx.algorithms import bipartite
+from networkx.utils import edges_equal, nodes_equal
+
+
+class TestBipartiteProject:
+ def test_path_projected_graph(self):
+ G = nx.path_graph(4)
+ P = bipartite.projected_graph(G, [1, 3])
+ assert nodes_equal(list(P), [1, 3])
+ assert edges_equal(list(P.edges()), [(1, 3)])
+ P = bipartite.projected_graph(G, [0, 2])
+ assert nodes_equal(list(P), [0, 2])
+ assert edges_equal(list(P.edges()), [(0, 2)])
+ G = nx.MultiGraph([(0, 1)])
+ with pytest.raises(nx.NetworkXError, match="not defined for multigraphs"):
+ bipartite.projected_graph(G, [0])
+
+ def test_path_projected_properties_graph(self):
+ G = nx.path_graph(4)
+ G.add_node(1, name="one")
+ G.add_node(2, name="two")
+ P = bipartite.projected_graph(G, [1, 3])
+ assert nodes_equal(list(P), [1, 3])
+ assert edges_equal(list(P.edges()), [(1, 3)])
+ assert P.nodes[1]["name"] == G.nodes[1]["name"]
+ P = bipartite.projected_graph(G, [0, 2])
+ assert nodes_equal(list(P), [0, 2])
+ assert edges_equal(list(P.edges()), [(0, 2)])
+ assert P.nodes[2]["name"] == G.nodes[2]["name"]
+
+ def test_path_collaboration_projected_graph(self):
+ G = nx.path_graph(4)
+ P = bipartite.collaboration_weighted_projected_graph(G, [1, 3])
+ assert nodes_equal(list(P), [1, 3])
+ assert edges_equal(list(P.edges()), [(1, 3)])
+ P[1][3]["weight"] = 1
+ P = bipartite.collaboration_weighted_projected_graph(G, [0, 2])
+ assert nodes_equal(list(P), [0, 2])
+ assert edges_equal(list(P.edges()), [(0, 2)])
+ P[0][2]["weight"] = 1
+
+ def test_directed_path_collaboration_projected_graph(self):
+ G = nx.DiGraph()
+ nx.add_path(G, range(4))
+ P = bipartite.collaboration_weighted_projected_graph(G, [1, 3])
+ assert nodes_equal(list(P), [1, 3])
+ assert edges_equal(list(P.edges()), [(1, 3)])
+ P[1][3]["weight"] = 1
+ P = bipartite.collaboration_weighted_projected_graph(G, [0, 2])
+ assert nodes_equal(list(P), [0, 2])
+ assert edges_equal(list(P.edges()), [(0, 2)])
+ P[0][2]["weight"] = 1
+
+ def test_path_weighted_projected_graph(self):
+ G = nx.path_graph(4)
+
+ with pytest.raises(nx.NetworkXAlgorithmError):
+ bipartite.weighted_projected_graph(G, [1, 2, 3, 3])
+
+ P = bipartite.weighted_projected_graph(G, [1, 3])
+ assert nodes_equal(list(P), [1, 3])
+ assert edges_equal(list(P.edges()), [(1, 3)])
+ P[1][3]["weight"] = 1
+ P = bipartite.weighted_projected_graph(G, [0, 2])
+ assert nodes_equal(list(P), [0, 2])
+ assert edges_equal(list(P.edges()), [(0, 2)])
+ P[0][2]["weight"] = 1
+
+ def test_digraph_weighted_projection(self):
+ G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 4)])
+ P = bipartite.overlap_weighted_projected_graph(G, [1, 3])
+ assert nx.get_edge_attributes(P, "weight") == {(1, 3): 1.0}
+ assert len(P) == 2
+
+ def test_path_weighted_projected_directed_graph(self):
+ G = nx.DiGraph()
+ nx.add_path(G, range(4))
+ P = bipartite.weighted_projected_graph(G, [1, 3])
+ assert nodes_equal(list(P), [1, 3])
+ assert edges_equal(list(P.edges()), [(1, 3)])
+ P[1][3]["weight"] = 1
+ P = bipartite.weighted_projected_graph(G, [0, 2])
+ assert nodes_equal(list(P), [0, 2])
+ assert edges_equal(list(P.edges()), [(0, 2)])
+ P[0][2]["weight"] = 1
+
+ def test_star_projected_graph(self):
+ G = nx.star_graph(3)
+ P = bipartite.projected_graph(G, [1, 2, 3])
+ assert nodes_equal(list(P), [1, 2, 3])
+ assert edges_equal(list(P.edges()), [(1, 2), (1, 3), (2, 3)])
+ P = bipartite.weighted_projected_graph(G, [1, 2, 3])
+ assert nodes_equal(list(P), [1, 2, 3])
+ assert edges_equal(list(P.edges()), [(1, 2), (1, 3), (2, 3)])
+
+ P = bipartite.projected_graph(G, [0])
+ assert nodes_equal(list(P), [0])
+ assert edges_equal(list(P.edges()), [])
+
+ def test_project_multigraph(self):
+ G = nx.Graph()
+ G.add_edge("a", 1)
+ G.add_edge("b", 1)
+ G.add_edge("a", 2)
+ G.add_edge("b", 2)
+ P = bipartite.projected_graph(G, "ab")
+ assert edges_equal(list(P.edges()), [("a", "b")])
+ P = bipartite.weighted_projected_graph(G, "ab")
+ assert edges_equal(list(P.edges()), [("a", "b")])
+ P = bipartite.projected_graph(G, "ab", multigraph=True)
+ assert edges_equal(list(P.edges()), [("a", "b"), ("a", "b")])
+
+ def test_project_collaboration(self):
+ G = nx.Graph()
+ G.add_edge("a", 1)
+ G.add_edge("b", 1)
+ G.add_edge("b", 2)
+ G.add_edge("c", 2)
+ G.add_edge("c", 3)
+ G.add_edge("c", 4)
+ G.add_edge("b", 4)
+ P = bipartite.collaboration_weighted_projected_graph(G, "abc")
+ assert P["a"]["b"]["weight"] == 1
+ assert P["b"]["c"]["weight"] == 2
+
+ def test_directed_projection(self):
+ G = nx.DiGraph()
+ G.add_edge("A", 1)
+ G.add_edge(1, "B")
+ G.add_edge("A", 2)
+ G.add_edge("B", 2)
+ P = bipartite.projected_graph(G, "AB")
+ assert edges_equal(list(P.edges()), [("A", "B")])
+ P = bipartite.weighted_projected_graph(G, "AB")
+ assert edges_equal(list(P.edges()), [("A", "B")])
+ assert P["A"]["B"]["weight"] == 1
+
+ P = bipartite.projected_graph(G, "AB", multigraph=True)
+ assert edges_equal(list(P.edges()), [("A", "B")])
+
+ G = nx.DiGraph()
+ G.add_edge("A", 1)
+ G.add_edge(1, "B")
+ G.add_edge("A", 2)
+ G.add_edge(2, "B")
+ P = bipartite.projected_graph(G, "AB")
+ assert edges_equal(list(P.edges()), [("A", "B")])
+ P = bipartite.weighted_projected_graph(G, "AB")
+ assert edges_equal(list(P.edges()), [("A", "B")])
+ assert P["A"]["B"]["weight"] == 2
+
+ P = bipartite.projected_graph(G, "AB", multigraph=True)
+ assert edges_equal(list(P.edges()), [("A", "B"), ("A", "B")])
+
+
+class TestBipartiteWeightedProjection:
+ @classmethod
+ def setup_class(cls):
+ # Tore Opsahl's example
+ # http://toreopsahl.com/2009/05/01/projecting-two-mode-networks-onto-weighted-one-mode-networks/
+ cls.G = nx.Graph()
+ cls.G.add_edge("A", 1)
+ cls.G.add_edge("A", 2)
+ cls.G.add_edge("B", 1)
+ cls.G.add_edge("B", 2)
+ cls.G.add_edge("B", 3)
+ cls.G.add_edge("B", 4)
+ cls.G.add_edge("B", 5)
+ cls.G.add_edge("C", 1)
+ cls.G.add_edge("D", 3)
+ cls.G.add_edge("E", 4)
+ cls.G.add_edge("E", 5)
+ cls.G.add_edge("E", 6)
+ cls.G.add_edge("F", 6)
+ # Graph based on figure 6 from Newman (2001)
+ cls.N = nx.Graph()
+ cls.N.add_edge("A", 1)
+ cls.N.add_edge("A", 2)
+ cls.N.add_edge("A", 3)
+ cls.N.add_edge("B", 1)
+ cls.N.add_edge("B", 2)
+ cls.N.add_edge("B", 3)
+ cls.N.add_edge("C", 1)
+ cls.N.add_edge("D", 1)
+ cls.N.add_edge("E", 3)
+
+ def test_project_weighted_shared(self):
+ edges = [
+ ("A", "B", 2),
+ ("A", "C", 1),
+ ("B", "C", 1),
+ ("B", "D", 1),
+ ("B", "E", 2),
+ ("E", "F", 1),
+ ]
+ Panswer = nx.Graph()
+ Panswer.add_weighted_edges_from(edges)
+ P = bipartite.weighted_projected_graph(self.G, "ABCDEF")
+ assert edges_equal(list(P.edges()), Panswer.edges())
+ for u, v in list(P.edges()):
+ assert P[u][v]["weight"] == Panswer[u][v]["weight"]
+
+ edges = [
+ ("A", "B", 3),
+ ("A", "E", 1),
+ ("A", "C", 1),
+ ("A", "D", 1),
+ ("B", "E", 1),
+ ("B", "C", 1),
+ ("B", "D", 1),
+ ("C", "D", 1),
+ ]
+ Panswer = nx.Graph()
+ Panswer.add_weighted_edges_from(edges)
+ P = bipartite.weighted_projected_graph(self.N, "ABCDE")
+ assert edges_equal(list(P.edges()), Panswer.edges())
+ for u, v in list(P.edges()):
+ assert P[u][v]["weight"] == Panswer[u][v]["weight"]
+
+ def test_project_weighted_newman(self):
+ edges = [
+ ("A", "B", 1.5),
+ ("A", "C", 0.5),
+ ("B", "C", 0.5),
+ ("B", "D", 1),
+ ("B", "E", 2),
+ ("E", "F", 1),
+ ]
+ Panswer = nx.Graph()
+ Panswer.add_weighted_edges_from(edges)
+ P = bipartite.collaboration_weighted_projected_graph(self.G, "ABCDEF")
+ assert edges_equal(list(P.edges()), Panswer.edges())
+ for u, v in list(P.edges()):
+ assert P[u][v]["weight"] == Panswer[u][v]["weight"]
+
+ edges = [
+ ("A", "B", 11 / 6.0),
+ ("A", "E", 1 / 2.0),
+ ("A", "C", 1 / 3.0),
+ ("A", "D", 1 / 3.0),
+ ("B", "E", 1 / 2.0),
+ ("B", "C", 1 / 3.0),
+ ("B", "D", 1 / 3.0),
+ ("C", "D", 1 / 3.0),
+ ]
+ Panswer = nx.Graph()
+ Panswer.add_weighted_edges_from(edges)
+ P = bipartite.collaboration_weighted_projected_graph(self.N, "ABCDE")
+ assert edges_equal(list(P.edges()), Panswer.edges())
+ for u, v in list(P.edges()):
+ assert P[u][v]["weight"] == Panswer[u][v]["weight"]
+
+ def test_project_weighted_ratio(self):
+ edges = [
+ ("A", "B", 2 / 6.0),
+ ("A", "C", 1 / 6.0),
+ ("B", "C", 1 / 6.0),
+ ("B", "D", 1 / 6.0),
+ ("B", "E", 2 / 6.0),
+ ("E", "F", 1 / 6.0),
+ ]
+ Panswer = nx.Graph()
+ Panswer.add_weighted_edges_from(edges)
+ P = bipartite.weighted_projected_graph(self.G, "ABCDEF", ratio=True)
+ assert edges_equal(list(P.edges()), Panswer.edges())
+ for u, v in list(P.edges()):
+ assert P[u][v]["weight"] == Panswer[u][v]["weight"]
+
+ edges = [
+ ("A", "B", 3 / 3.0),
+ ("A", "E", 1 / 3.0),
+ ("A", "C", 1 / 3.0),
+ ("A", "D", 1 / 3.0),
+ ("B", "E", 1 / 3.0),
+ ("B", "C", 1 / 3.0),
+ ("B", "D", 1 / 3.0),
+ ("C", "D", 1 / 3.0),
+ ]
+ Panswer = nx.Graph()
+ Panswer.add_weighted_edges_from(edges)
+ P = bipartite.weighted_projected_graph(self.N, "ABCDE", ratio=True)
+ assert edges_equal(list(P.edges()), Panswer.edges())
+ for u, v in list(P.edges()):
+ assert P[u][v]["weight"] == Panswer[u][v]["weight"]
+
+ def test_project_weighted_overlap(self):
+ edges = [
+ ("A", "B", 2 / 2.0),
+ ("A", "C", 1 / 1.0),
+ ("B", "C", 1 / 1.0),
+ ("B", "D", 1 / 1.0),
+ ("B", "E", 2 / 3.0),
+ ("E", "F", 1 / 1.0),
+ ]
+ Panswer = nx.Graph()
+ Panswer.add_weighted_edges_from(edges)
+ P = bipartite.overlap_weighted_projected_graph(self.G, "ABCDEF", jaccard=False)
+ assert edges_equal(list(P.edges()), Panswer.edges())
+ for u, v in list(P.edges()):
+ assert P[u][v]["weight"] == Panswer[u][v]["weight"]
+
+ edges = [
+ ("A", "B", 3 / 3.0),
+ ("A", "E", 1 / 1.0),
+ ("A", "C", 1 / 1.0),
+ ("A", "D", 1 / 1.0),
+ ("B", "E", 1 / 1.0),
+ ("B", "C", 1 / 1.0),
+ ("B", "D", 1 / 1.0),
+ ("C", "D", 1 / 1.0),
+ ]
+ Panswer = nx.Graph()
+ Panswer.add_weighted_edges_from(edges)
+ P = bipartite.overlap_weighted_projected_graph(self.N, "ABCDE", jaccard=False)
+ assert edges_equal(list(P.edges()), Panswer.edges())
+ for u, v in list(P.edges()):
+ assert P[u][v]["weight"] == Panswer[u][v]["weight"]
+
+ def test_project_weighted_jaccard(self):
+ edges = [
+ ("A", "B", 2 / 5.0),
+ ("A", "C", 1 / 2.0),
+ ("B", "C", 1 / 5.0),
+ ("B", "D", 1 / 5.0),
+ ("B", "E", 2 / 6.0),
+ ("E", "F", 1 / 3.0),
+ ]
+ Panswer = nx.Graph()
+ Panswer.add_weighted_edges_from(edges)
+ P = bipartite.overlap_weighted_projected_graph(self.G, "ABCDEF")
+ assert edges_equal(list(P.edges()), Panswer.edges())
+ for u, v in list(P.edges()):
+ assert P[u][v]["weight"] == Panswer[u][v]["weight"]
+
+ edges = [
+ ("A", "B", 3 / 3.0),
+ ("A", "E", 1 / 3.0),
+ ("A", "C", 1 / 3.0),
+ ("A", "D", 1 / 3.0),
+ ("B", "E", 1 / 3.0),
+ ("B", "C", 1 / 3.0),
+ ("B", "D", 1 / 3.0),
+ ("C", "D", 1 / 1.0),
+ ]
+ Panswer = nx.Graph()
+ Panswer.add_weighted_edges_from(edges)
+ P = bipartite.overlap_weighted_projected_graph(self.N, "ABCDE")
+ assert edges_equal(list(P.edges()), Panswer.edges())
+ for u, v in P.edges():
+ assert P[u][v]["weight"] == Panswer[u][v]["weight"]
+
+ def test_generic_weighted_projected_graph_simple(self):
+ def shared(G, u, v):
+ return len(set(G[u]) & set(G[v]))
+
+ B = nx.path_graph(5)
+ G = bipartite.generic_weighted_projected_graph(
+ B, [0, 2, 4], weight_function=shared
+ )
+ assert nodes_equal(list(G), [0, 2, 4])
+ assert edges_equal(
+ list(G.edges(data=True)),
+ [(0, 2, {"weight": 1}), (2, 4, {"weight": 1})],
+ )
+
+ G = bipartite.generic_weighted_projected_graph(B, [0, 2, 4])
+ assert nodes_equal(list(G), [0, 2, 4])
+ assert edges_equal(
+ list(G.edges(data=True)),
+ [(0, 2, {"weight": 1}), (2, 4, {"weight": 1})],
+ )
+ B = nx.DiGraph()
+ nx.add_path(B, range(5))
+ G = bipartite.generic_weighted_projected_graph(B, [0, 2, 4])
+ assert nodes_equal(list(G), [0, 2, 4])
+ assert edges_equal(
+ list(G.edges(data=True)), [(0, 2, {"weight": 1}), (2, 4, {"weight": 1})]
+ )
+
+ def test_generic_weighted_projected_graph_custom(self):
+ def jaccard(G, u, v):
+ unbrs = set(G[u])
+ vnbrs = set(G[v])
+ return len(unbrs & vnbrs) / len(unbrs | vnbrs)
+
+ def my_weight(G, u, v, weight="weight"):
+ w = 0
+ for nbr in set(G[u]) & set(G[v]):
+ w += G.edges[u, nbr].get(weight, 1) + G.edges[v, nbr].get(weight, 1)
+ return w
+
+ B = nx.bipartite.complete_bipartite_graph(2, 2)
+ for i, (u, v) in enumerate(B.edges()):
+ B.edges[u, v]["weight"] = i + 1
+ G = bipartite.generic_weighted_projected_graph(
+ B, [0, 1], weight_function=jaccard
+ )
+ assert edges_equal(list(G.edges(data=True)), [(0, 1, {"weight": 1.0})])
+ G = bipartite.generic_weighted_projected_graph(
+ B, [0, 1], weight_function=my_weight
+ )
+ assert edges_equal(list(G.edges(data=True)), [(0, 1, {"weight": 10})])
+ G = bipartite.generic_weighted_projected_graph(B, [0, 1])
+ assert edges_equal(list(G.edges(data=True)), [(0, 1, {"weight": 2})])
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_redundancy.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_redundancy.py
new file mode 100644
index 00000000..8d979db8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_redundancy.py
@@ -0,0 +1,35 @@
+"""Unit tests for the :mod:`networkx.algorithms.bipartite.redundancy` module."""
+
+import pytest
+
+from networkx import NetworkXError, cycle_graph
+from networkx.algorithms.bipartite import complete_bipartite_graph, node_redundancy
+
+
+def test_no_redundant_nodes():
+ G = complete_bipartite_graph(2, 2)
+
+ # when nodes is None
+ rc = node_redundancy(G)
+ assert all(redundancy == 1 for redundancy in rc.values())
+
+ # when set of nodes is specified
+ rc = node_redundancy(G, (2, 3))
+ assert rc == {2: 1.0, 3: 1.0}
+
+
+def test_redundant_nodes():
+ G = cycle_graph(6)
+ edge = {0, 3}
+ G.add_edge(*edge)
+ redundancy = node_redundancy(G)
+ for v in edge:
+ assert redundancy[v] == 2 / 3
+ for v in set(G) - edge:
+ assert redundancy[v] == 1
+
+
+def test_not_enough_neighbors():
+ with pytest.raises(NetworkXError):
+ G = complete_bipartite_graph(1, 2)
+ node_redundancy(G)
diff --git a/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py
new file mode 100644
index 00000000..b9406497
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py
@@ -0,0 +1,80 @@
+import pytest
+
+pytest.importorskip("scipy")
+
+import networkx as nx
+from networkx.algorithms.bipartite import spectral_bipartivity as sb
+
+# Examples from Figure 1
+# E. Estrada and J. A. Rodríguez-Velázquez, "Spectral measures of
+# bipartivity in complex networks", PhysRev E 72, 046105 (2005)
+
+
+class TestSpectralBipartivity:
+ def test_star_like(self):
+ # star-like
+
+ G = nx.star_graph(2)
+ G.add_edge(1, 2)
+ assert sb(G) == pytest.approx(0.843, abs=1e-3)
+
+ G = nx.star_graph(3)
+ G.add_edge(1, 2)
+ assert sb(G) == pytest.approx(0.871, abs=1e-3)
+
+ G = nx.star_graph(4)
+ G.add_edge(1, 2)
+ assert sb(G) == pytest.approx(0.890, abs=1e-3)
+
+ def test_k23_like(self):
+ # K2,3-like
+ G = nx.complete_bipartite_graph(2, 3)
+ G.add_edge(0, 1)
+ assert sb(G) == pytest.approx(0.769, abs=1e-3)
+
+ G = nx.complete_bipartite_graph(2, 3)
+ G.add_edge(2, 4)
+ assert sb(G) == pytest.approx(0.829, abs=1e-3)
+
+ G = nx.complete_bipartite_graph(2, 3)
+ G.add_edge(2, 4)
+ G.add_edge(3, 4)
+ assert sb(G) == pytest.approx(0.731, abs=1e-3)
+
+ G = nx.complete_bipartite_graph(2, 3)
+ G.add_edge(0, 1)
+ G.add_edge(2, 4)
+ assert sb(G) == pytest.approx(0.692, abs=1e-3)
+
+ G = nx.complete_bipartite_graph(2, 3)
+ G.add_edge(2, 4)
+ G.add_edge(3, 4)
+ G.add_edge(0, 1)
+ assert sb(G) == pytest.approx(0.645, abs=1e-3)
+
+ G = nx.complete_bipartite_graph(2, 3)
+ G.add_edge(2, 4)
+ G.add_edge(3, 4)
+ G.add_edge(2, 3)
+ assert sb(G) == pytest.approx(0.645, abs=1e-3)
+
+ G = nx.complete_bipartite_graph(2, 3)
+ G.add_edge(2, 4)
+ G.add_edge(3, 4)
+ G.add_edge(2, 3)
+ G.add_edge(0, 1)
+ assert sb(G) == pytest.approx(0.597, abs=1e-3)
+
+ def test_single_nodes(self):
+ # single nodes
+ G = nx.complete_bipartite_graph(2, 3)
+ G.add_edge(2, 4)
+ sbn = sb(G, nodes=[1, 2])
+ assert sbn[1] == pytest.approx(0.85, abs=1e-2)
+ assert sbn[2] == pytest.approx(0.77, abs=1e-2)
+
+ G = nx.complete_bipartite_graph(2, 3)
+ G.add_edge(0, 1)
+ sbn = sb(G, nodes=[1, 2])
+ assert sbn[1] == pytest.approx(0.73, abs=1e-2)
+ assert sbn[2] == pytest.approx(0.82, abs=1e-2)