Repository: networkx/networkx

Issue:
metric_closure will throw KeyError with unconnected graph
Suggest checking connectedness with `nx.is_connected()` on entry to `metric_closure()` and throwing a more informative error if not.


Discussion:
Is there a reference for the definition of metric_closure or Steiner tree on a disconnected graph?
My naive view is that Steiner Tree's definition works applied to disconnected graphs.
Most definitions of the metric closure I have found add the extra edges with infinite (or very large) distances to make it a complete graph.  But couldn't we also just exclude all edges between components? It would work just as well for Steiner Tree. Are there other uses for metric closure?
I'm not a graph theorist but certainly the definition of a Steiner Tree seems to require connectedness to me, if the terminals occur across multiple components, since like Minimal Spanning Trees they make use of edges in the original graph - the metric closure is required only in the approximation algorithm. I suppose an alternative would be to have the metric closure defined, but infinite for edges between components. At the moment this case needs to be handled more gracefully.
I agree...   As far as my fairly cursory search of the literature has found, people don't use these terms for a disconnected graph. It may be possible to create a useful definition in such cases, but let's just refactor so that the current error message becomes a more useful one. I think the cost of running nx.is_connected(G) is pretty steep for this feature. Most calls will use connected graphs. I suggest a check of the first dijkstra result to test connectivity. I'll put a PR together soon if this sounds good.
Sounds good to me - do you plan to simply check the resultant cost matrix is fully populated? If `is_connected()` is less efficient than this proposed approach, maybe it should also be re-implemented ;)

Resolving patch:
diff --git a/networkx/algorithms/approximation/steinertree.py b/networkx/algorithms/approximation/steinertree.py
--- a/networkx/algorithms/approximation/steinertree.py
+++ b/networkx/algorithms/approximation/steinertree.py
@@ -25,11 +25,22 @@ def metric_closure(G, weight='weight'):
     """
     M = nx.Graph()
 
-    seen = set()
     Gnodes = set(G)
-    for u, (distance, path) in nx.all_pairs_dijkstra(G, weight=weight):
-        seen.add(u)
-        for v in Gnodes - seen:
+
+    # check for connected graph while processing first node
+    all_paths_iter = nx.all_pairs_dijkstra(G, weight=weight)
+    u, (distance, path) = next(all_paths_iter)
+    if Gnodes - set(distance):
+        msg = "G is not a connected graph. metric_closure is not defined."
+        raise nx.NetworkXError(msg)
+    Gnodes.remove(u)
+    for v in Gnodes:
+        M.add_edge(u, v, distance=distance[v], path=path[v])
+
+    # first node done -- now process the rest
+    for u, (distance, path) in all_paths_iter:
+        Gnodes.remove(u)
+        for v in Gnodes:
             M.add_edge(u, v, distance=distance[v], path=path[v])
 
     return M


Test patch:
diff --git a/networkx/algorithms/approximation/tests/test_steinertree.py b/networkx/algorithms/approximation/tests/test_steinertree.py
--- a/networkx/algorithms/approximation/tests/test_steinertree.py
+++ b/networkx/algorithms/approximation/tests/test_steinertree.py
@@ -1,3 +1,4 @@
+from nose.tools import assert_raises
 import networkx as nx
 from networkx.algorithms.approximation.steinertree import metric_closure
 from networkx.algorithms.approximation.steinertree import steiner_tree
@@ -17,6 +18,11 @@ def setUp(self):
         self.G = G
         self.term_nodes = [1, 2, 3, 4, 5]
 
+    def test_connected_metric_closure(self):
+        G = self.G.copy()
+        G.add_node(100)
+        assert_raises(nx.NetworkXError, metric_closure, G)
+
     def test_metric_closure(self):
         M = metric_closure(self.G)
         mc = [(1, 2, {'distance': 10, 'path': [1, 2]}),