Repository: networkx/networkx (Python)

Issue:
Unused `root` argument in `has_bridges`
This is the last remaining issue from #5486. I've split it out into it's own issue as all of the other problems identified in #5486 have been resolved.

As originally discovered by @dtekinoglu `has_bridges` defines a `root` parameter which is not used. It should be passed on to the underlying `bridges` call. There should also be some additional tests for `has_bridges` with the `root` argument - both that it works as expected when `root` is provided, and that it fails as expected when `root` is specified incorrectly (e.g. `NodeNotFound`)


Discussion:

Patch:
diff --git a/networkx/algorithms/bridges.py b/networkx/algorithms/bridges.py
index eda4a4f6dcc..42e6a8e7808 100644
--- a/networkx/algorithms/bridges.py
+++ b/networkx/algorithms/bridges.py
@@ -69,6 +69,9 @@ def bridges(G, root=None):
     H = nx.Graph(G) if multigraph else G
     chains = nx.chain_decomposition(H, root=root)
     chain_edges = set(chain.from_iterable(chains))
+    H_copy = H.copy()
+    if root is not None:
+        H = H.subgraph(nx.node_connected_component(H, root)).copy()
     for u, v in H.edges():
         if (u, v) not in chain_edges and (v, u) not in chain_edges:
             if multigraph and len(G[u][v]) > 1:
@@ -128,7 +131,7 @@ def has_bridges(G, root=None):
 
     """
     try:
-        next(bridges(G))
+        next(bridges(G, root=root))
     except StopIteration:
         return False
     else:
diff --git a/networkx/algorithms/chains.py b/networkx/algorithms/chains.py
index 2018c885f9b..53ec84c7f3f 100644
--- a/networkx/algorithms/chains.py
+++ b/networkx/algorithms/chains.py
@@ -141,6 +141,10 @@ def _build_chain(G, u, v, visited):
             u, v = v, G.nodes[v]["parent"]
         yield u, v
 
+    # Check if the root is in the graph G. If not, raise NodeNotFound
+    if root is not None and root not in G:
+        raise nx.NodeNotFound(f"Root node {root} is not in graph")
+
     # Create a directed version of H that has the DFS edges directed
     # toward the root and the nontree edges directed away from the root
     # (in each connected component).


Test patch:
diff --git a/networkx/algorithms/tests/test_bridges.py b/networkx/algorithms/tests/test_bridges.py
index 059cf3da893..9c3ceba607f 100644
--- a/networkx/algorithms/tests/test_bridges.py
+++ b/networkx/algorithms/tests/test_bridges.py
@@ -1,5 +1,7 @@
 """Unit tests for bridge-finding algorithms."""
 
+import pytest
+
 import networkx as nx
 
 
@@ -51,6 +53,61 @@ def test_multiedge_bridge(self):
         assert list(nx.bridges(G)) == [(2, 3)]
 
 
+class TestHasBridges:
+    """Unit tests for the has bridges function."""
+
+    def test_single_bridge(self):
+        edges = [
+            # DFS tree edges.
+            (1, 2),
+            (2, 3),
+            (3, 4),
+            (3, 5),
+            (5, 6),  # The only bridge edge
+            (6, 7),
+            (7, 8),
+            (5, 9),
+            (9, 10),
+            # Nontree edges.
+            (1, 3),
+            (1, 4),
+            (2, 5),
+            (5, 10),
+            (6, 8),
+        ]
+        G = nx.Graph(edges)
+        assert nx.has_bridges(G)  # Default root
+        assert nx.has_bridges(G, root=1)  # arbitrary root in G
+
+    def test_has_bridges_raises_root_not_in_G(self):
+        G = nx.Graph()
+        G.add_nodes_from([1, 2, 3])
+        with pytest.raises(nx.NodeNotFound):
+            nx.has_bridges(G, root=6)
+
+    def test_multiedge_bridge(self):
+        edges = [
+            (0, 1),
+            (0, 2),
+            (1, 2),
+            (1, 2),
+            (2, 3),
+            (3, 4),
+            (3, 4),
+        ]
+        G = nx.MultiGraph(edges)
+        assert nx.has_bridges(G)
+        # Make every edge a multiedge
+        G.add_edges_from([(0, 1), (0, 2), (2, 3)])
+        assert not nx.has_bridges(G)
+
+    def test_bridges_multiple_components(self):
+        G = nx.Graph()
+        nx.add_path(G, [0, 1, 2])  # One connected component
+        nx.add_path(G, [4, 5, 6])  # Another connected component
+        assert list(nx.bridges(G, root=4)) == [(4, 5), (5, 6)]
+
+
 class TestLocalBridges:
     """Unit tests for the local_bridge function."""
 
diff --git a/networkx/algorithms/tests/test_chains.py b/networkx/algorithms/tests/test_chains.py
index f8886c97d51..6a1b1142caa 100644
--- a/networkx/algorithms/tests/test_chains.py
+++ b/networkx/algorithms/tests/test_chains.py
@@ -1,6 +1,8 @@
 """Unit tests for the chain decomposition functions."""
 from itertools import cycle, islice
 
+import pytest
+
 import networkx as nx
 
 
@@ -129,3 +131,10 @@ def test_disconnected_graph_root_node(self):
         assert len(chains) == len(expected)
         for chain in chains:
             self.assertContainsChain(chain, expected)
+
+    def test_chain_decomposition_root_not_in_G(self):
+        """Test chain decomposition when root is not in graph"""
+        G = nx.Graph()
+        G.add_nodes_from([1, 2, 3])
+        with pytest.raises(nx.NodeNotFound):
+            nx.has_bridges(G, root=6)