Repository: networkx/networkx

Issue:
Small bug in networkx.algorithms.dominance
There is a small bug in `networkx.algorithms.dominance` package. 

Function `immediate_dominators` should be given digraph and starting point. However, if a given graph has several entry points, say `entry_1` and `entry_2` (e.g. dead code block in control flow graph), and the starting point is specified as `entry_1`, `entry_2` will not be present in `idom` dict (return value) as key. This is correct behavior concerning the definition of dominators, but this can lead to `KeyError` exception in a call to `dominance_frontiers` function, because it does not check predecessors to be reachable from start.

Below is a snippet representing an issue. 

```
import networkx as nx
from networkx.algorithms.dominance import *

g = nx.DiGraph()

edges = [
            ('entry_1', 'b1'),
            ('b1', 'b2'),
            ('b2', 'b3'),
            ('b3', 'exit'),
            ('entry_2', 'b3')
        ]

# entry_1
#   |
#   b1
#   |
#   b2  entry_2
#    |  /
#    b3
#    |
#   exit

g.add_edges_from(edges)

# will raise KeyError on entry_2 when parsing b3
# because entry_2 does not have immediate doms (no path)
dominance_frontiers(g,'entry_1') 
```

Suggestion is to parse only those predecessors that have path from given starting point to them. This can be achieved by adding `v in idom` check which is equivalent to having a path from start point in this context.

P.S. tested on networkx version 1.11



Discussion:
Can you be more specific on the fix?  I'm not an expert in this algorithm.

Just add v in idom check before doing something in for loop in dominance_frontiers function, as I suggested before. After computing immediate dominators, idom keys will be only those nodes, that have immediate dominator, which is equivalent to having path from starting point.


Resolving patch:
diff --git a/networkx/algorithms/dominance.py b/networkx/algorithms/dominance.py
--- a/networkx/algorithms/dominance.py
+++ b/networkx/algorithms/dominance.py
@@ -126,17 +126,12 @@ def dominance_frontiers(G, start):
     """
     idom = nx.immediate_dominators(G, start)
 
-    df = {u: [] for u in idom}
-
+    df = {u: set() for u in idom}
     for u in idom:
-        if len(G.pred[u]) - int(u in G.pred[u]) >= 2:
-            p = set()
+        if len(G.pred[u]) >= 2:
             for v in G.pred[u]:
-                while v != idom[u] and v not in p:
-                    p.add(v)
-                    v = idom[v]
-            p.discard(u)
-            for v in p:
-                df[v].append(u)
-
+                if v in idom:
+                    while v != idom[u]:
+                        df[v].add(u)
+                        v = idom[v]
     return df


Test patch:
diff --git a/networkx/algorithms/tests/test_dominance.py b/networkx/algorithms/tests/test_dominance.py
--- a/networkx/algorithms/tests/test_dominance.py
+++ b/networkx/algorithms/tests/test_dominance.py
@@ -99,28 +99,28 @@ def test_exceptions(self):
     def test_singleton(self):
         G = nx.DiGraph()
         G.add_node(0)
-        assert_equal(nx.dominance_frontiers(G, 0), {0: []})
+        assert_equal(nx.dominance_frontiers(G, 0), {0: set()})
         G.add_edge(0, 0)
-        assert_equal(nx.dominance_frontiers(G, 0), {0: []})
+        assert_equal(nx.dominance_frontiers(G, 0), {0: set()})
 
     def test_path(self):
         n = 5
         G = nx.path_graph(n, create_using=nx.DiGraph())
         assert_equal(nx.dominance_frontiers(G, 0),
-                     {i: [] for i in range(n)})
+                     {i: set() for i in range(n)})
 
     def test_cycle(self):
         n = 5
         G = nx.cycle_graph(n, create_using=nx.DiGraph())
         assert_equal(nx.dominance_frontiers(G, 0),
-                     {i: [] for i in range(n)})
+                     {i: set() for i in range(n)})
 
     def test_unreachable(self):
         n = 5
         assert_greater(n, 1)
         G = nx.path_graph(n, create_using=nx.DiGraph())
         assert_equal(nx.dominance_frontiers(G, n // 2),
-                     {i: [] for i in range(n // 2, n)})
+                     {i: set() for i in range(n // 2, n)})
 
     def test_irreducible1(self):
         # Graph taken from Figure 2 of
@@ -129,9 +129,11 @@ def test_irreducible1(self):
         # Software Practice & Experience, 4:110, 2001.
         edges = [(1, 2), (2, 1), (3, 2), (4, 1), (5, 3), (5, 4)]
         G = nx.DiGraph(edges)
-        assert_equal({u: sorted(df)
+        assert_equal({u: df
                       for u, df in nx.dominance_frontiers(G, 5).items()},
-                     {1: [2], 2: [1], 3: [2], 4: [1], 5: []})
+                     {1: set([2]), 2: set([1]), 3: set([2]),
+                      4: set([1]), 5: set()})
+
 
     def test_irreducible2(self):
         # Graph taken from Figure 4 of
@@ -142,18 +144,21 @@ def test_irreducible2(self):
                  (6, 4), (6, 5)]
         G = nx.DiGraph(edges)
         assert_equal(nx.dominance_frontiers(G, 6),
-                     {1: [2], 2: [1, 3], 3: [2], 4: [2, 3], 5: [1], 6: []})
+                     {1: set([2]), 2: set([1, 3]), 3: set([2]), 4: set([2, 3])
+                      , 5: set([1]), 6: set([])})
 
     def test_domrel_png(self):
         # Graph taken from https://commons.wikipedia.org/wiki/File:Domrel.png
         edges = [(1, 2), (2, 3), (2, 4), (2, 6), (3, 5), (4, 5), (5, 2)]
         G = nx.DiGraph(edges)
         assert_equal(nx.dominance_frontiers(G, 1),
-                     {1: [], 2: [], 3: [5], 4: [5], 5: [2], 6: []})
+                     {1: set([]), 2: set([2]), 3: set([5]), 4: set([5]),
+                                         5: set([2]), 6: set()})
         # Test postdominance.
         with nx.utils.reversed(G):
             assert_equal(nx.dominance_frontiers(G, 6),
-                         {1: [], 2: [], 3: [2], 4: [2], 5: [2], 6: []})
+                         {1: set(), 2: set([2]), 3: set([2]), 4: set([2]),
+                          5: set([2]), 6: set()})
 
     def test_boost_example(self):
         # Graph taken from Figure 1 of
@@ -162,10 +167,97 @@ def test_boost_example(self):
                  (5, 7), (6, 4)]
         G = nx.DiGraph(edges)
         assert_equal(nx.dominance_frontiers(G, 0),
-                     {0: [], 1: [], 2: [7], 3: [7], 4: [7], 5: [7], 6: [4],
-                      7: []})
+                     {0: set(), 1: set(), 2: set([7]), 3: set([7]),
+                      4: set([4,7]), 5: set([7]), 6: set([4]), 7: set()})
         # Test postdominance.
         with nx.utils.reversed(G):
             assert_equal(nx.dominance_frontiers(G, 7),
-                         {0: [], 1: [], 2: [1], 3: [1], 4: [1], 5: [1], 6: [4],
-                          7: []})
+                         {0: set(), 1: set(), 2: set([1]), 3: set([1]),
+                          4: set([1,4]), 5: set([1]), 6: set([4]), 7: set()})
+
+
+    def test_discard_issue(self):
+        # https://github.com/networkx/networkx/issues/2071
+        g = nx.DiGraph()
+        g.add_edges_from([
+            ('b0','b1'),
+            ('b1', 'b2'),
+            ('b2', 'b3'),
+            ('b3','b1'),
+            ('b1','b5'),
+            ('b5', 'b6'),
+            ('b5', 'b8'),
+            ('b6', 'b7'),
+            ('b8', 'b7'),
+            ('b7', 'b3'),
+            ('b3', 'b4')
+        ]
+        )
+        df = nx.dominance_frontiers(g, 'b0')
+        assert_equal(df, {'b4': set(), 'b5': set(['b3']), 'b6': set(['b7']),
+                          'b7': set(['b3']),
+                          'b0': set(), 'b1': set(['b1']), 'b2': set(['b3']),
+                          'b3': set(['b1']), 'b8': set(['b7'])})
+
+    def test_loop(self):
+        g = nx.DiGraph()
+        g.add_edges_from([('a','b'),('b','c'),('b','a')])
+        df = nx.dominance_frontiers(g, 'a')
+        assert_equal(df, {'a': set(), 'b': set(), 'c': set()})
+
+    def test_missing_immediate_doms(self):
+        # see https://github.com/networkx/networkx/issues/2070
+        g = nx.DiGraph()
+        edges = [
+            ('entry_1', 'b1'),
+            ('b1', 'b2'),
+            ('b2', 'b3'),
+            ('b3', 'exit'),
+            ('entry_2', 'b3')
+        ]
+
+        # entry_1
+        #   |
+        #   b1
+        #   |
+        #   b2  entry_2
+        #    |  /
+        #    b3
+        #    |
+        #   exit
+
+        g.add_edges_from(edges)
+        # formerly raised KeyError on entry_2 when parsing b3
+        # because entry_2 does not have immediate doms (no path)
+        nx.dominance_frontiers(g,'entry_1')
+
+    def test_loops_larger(self):
+        # from
+        # http://ecee.colorado.edu/~waite/Darmstadt/motion.html
+        g = nx.DiGraph()
+        edges = [
+            ('entry', 'exit'),
+            ('entry', '1'),
+            ('1', '2'),
+            ('2', '3'),
+            ('3', '4'),
+            ('4', '5'),
+            ('5', '6'),
+            ('6', 'exit'),
+            ('6', '2'),
+            ('5', '3'),
+            ('4', '4')
+        ]
+
+        g.add_edges_from(edges)
+        df = nx.dominance_frontiers(g,'entry')
+        answer = {'entry': set(),
+                  '1': set(['exit']),
+                  '2': set(['exit', '2']),
+                  '3': set(['exit', '3', '2']),
+                  '4': set(['exit', '4','3', '2']),
+                  '5': set(['exit', '3', '2']),
+                  '6': set(['exit', '2']),
+                  'exit': set()}
+        for n in df:
+            assert_equal(set(df[n]),set(answer[n]))