about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
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)