Repository: Qiskit/qiskit

Issue:
dag.depth seems broken
It seems that PR #1815 introduced a bug in `depth()`. 
```
from qiskit import QuantumRegister, QuantumCircuit
from qiskit.converters import circuit_to_dag

qr = QuantumRegister(2)
circuit = QuantumCircuit(qr)
circuit.h(qr[0])
circuit.h(qr[1])
circuit.cx(qr[0], qr[1])
circuit.cx(qr[0], qr[1])
circuit.cx(qr[0], qr[1])
circuit.cx(qr[0], qr[1])
circuit.cx(qr[1], qr[0])
circuit.cx(qr[1], qr[0])
dag = circuit_to_dag(circuit)

assert dag.depth() == 7
```


Discussion:
Yes, the depth is zero.  The change to `QuantumCircuit` in #1993, that computes the depth without the DAG, is correct, i.e. 7.
The problems is that DAGNodes considers every node with the same name equal. I will have a look... 
```
qr = QuantumRegister(2)
circuit = QuantumCircuit(qr)
circuit.cx(qr[0], qr[1])
circuit.cx(qr[0], qr[1])
dag = circuit_to_dag(circuit)

dag.named_nodes('cx')[0]  == dag.named_nodes('cx')[1] # This is wrongly True
```
Probably the same issue that @kdk detected here https://github.com/Qiskit/qiskit-terra/pull/1815#issuecomment-474382798. 
@1ucian0, @kdk and I discussed it during the review and he pointed out that equality in Python is meant to be for when two objects are semantically equivalent which those two `cx` gates are as they have the same name and same parameters. This can easily be reverted by reintroducing the version of the `__eq__` method [here](https://github.com/maddy-tod/qiskit-terra/blob/8c9aa0550488e3dd660898dc5b1c0b9455a98280/qiskit/dagcircuit/_dagnode.py#L87) which checks that the ids of the two operations are also the same. 

We should decide how strict we want the equality method to be, as to use our current `depth()` method we would need the stricter form but this goes against Python convention.
Agree that the current `DAGNode` implementation of `__hash__`/`__eq__` is inconsistent. From https://docs.python.org/3/reference/datamodel.html#object.__hash__ ,
> The only required property is that objects which compare equal have the same hash value; ...

If we want `DAGNode`s to be used as the labels of the networkx graph, we need semantically equivalent gates (as in the example) to be `neq` so that they can be safely hashed and compared ( line `where u != v` in https://networkx.github.io/documentation/stable/_modules/networkx/algorithms/dag.html#dag_longest_path is where the current example goes wrong.)

If that's the path we go, we can remove `DAGNode`s `__hash__`/`__eq__` and add a weaker "semantic equivalence" check to be used in comparing `DAGCircuit`s (which do `__eq__` by semantic equivalence.)

Resolving patch:
diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py
--- a/qiskit/dagcircuit/dagcircuit.py
+++ b/qiskit/dagcircuit/dagcircuit.py
@@ -754,21 +754,17 @@ def _full_pred_succ_maps(self, pred_map, succ_map, input_circuit,
         return full_pred_map, full_succ_map
 
     def __eq__(self, other):
+        # TODO this works but is a horrible way to do this
+        slf = copy.deepcopy(self.multi_graph)
+        oth = copy.deepcopy(other.multi_graph)
 
-        if nx.is_isomorphic(self.multi_graph, other.multi_graph):
-            # TODO this works but is a horrible way to do this
-            slf = copy.deepcopy(self.multi_graph)
-            oth = copy.deepcopy(other.multi_graph)
+        for node in slf.nodes:
+            slf.nodes[node]['node'] = node
+        for node in oth.nodes:
+            oth.nodes[node]['node'] = node
 
-            for node in slf.nodes:
-                slf.nodes[node]["node"] = node
-            for node in oth.nodes:
-                oth.nodes[node]["node"] = node
-
-            return nx.is_isomorphic(slf, oth, node_match=lambda x, y:
-                                    x['node'] == y['node'])
-
-        return False
+        return nx.is_isomorphic(slf, oth,
+                                node_match=lambda x, y: DAGNode.semantic_eq(x['node'], y['node']))
 
     def nodes_in_topological_order(self):
         """
diff --git a/qiskit/dagcircuit/dagnode.py b/qiskit/dagcircuit/dagnode.py
--- a/qiskit/dagcircuit/dagnode.py
+++ b/qiskit/dagcircuit/dagnode.py
@@ -19,6 +19,7 @@ class DAGNode:
     be supplied to functions that take a node.
 
     """
+
     def __init__(self, data_dict, nid=-1):
         """ Create a node """
         self._node_id = nid
@@ -83,31 +84,6 @@ def wire(self):
             raise QiskitError('The node %s is not an input/output node' % str(self))
         return self.data_dict.get('wire')
 
-    def __eq__(self, other):
-
-        # if other is None
-        if not other:
-            return False
-
-        if isinstance(other, int):
-            return other == self._node_id
-
-        # For barriers, qarg order is not significant so compare as sets
-        if 'barrier' == self.name == other.name:
-            node1_qargs = set(self.qargs)
-            node2_qargs = set(other.qargs)
-
-            if node1_qargs != node2_qargs:
-                return False
-
-            # qargs must be equal, so remove them from the dict then compare
-            copy_self = {k: v for (k, v) in self.data_dict.items() if k != 'qargs'}
-            copy_other = {k: v for (k, v) in other.data_dict.items() if k != 'qargs'}
-
-            return copy_self == copy_other
-
-        return self.data_dict == other.data_dict
-
     def __lt__(self, other):
         return self._node_id < other._node_id
 
@@ -128,3 +104,22 @@ def __str__(self):
     def pop(self, val):
         """Remove the provided value from the dictionary"""
         del self.data_dict[val]
+
+    @staticmethod
+    def semantic_eq(node1, node2):
+        """
+        Check if DAG nodes are considered equivalent, e.g. as a node_match for nx.is_isomorphic.
+
+        Args:
+            node1 (DAGNode): A node to compare.
+            node2 (DAGNode): The other node to compare.
+
+        Return:
+            Bool: If node1 == node2
+        """
+
+        # For barriers, qarg order is not significant so compare as sets
+        if 'barrier' == node1.name == node2.name:
+            return set(node1.qargs) == set(node2.qargs)
+
+        return node1.data_dict == node2.data_dict


Test patch:
diff --git a/test/python/test_dagcircuit.py b/test/python/test_dagcircuit.py
--- a/test/python/test_dagcircuit.py
+++ b/test/python/test_dagcircuit.py
@@ -247,18 +247,18 @@ def test_nodes_in_topological_order(self):
 
     def test_dag_ops_on_wire(self):
         """ Test that listing the gates on a qubit/classical bit gets the correct gates"""
-
         self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit1),
                                       [self.qubit0, self.qubit1])
         self.dag.apply_operation_back(HGate(self.qubit0), [self.qubit0])
 
         qbit = self.dag.qubits()[0]
-        self.assertEqual([1, 11, 12, 2], list(self.dag.nodes_on_wire(qbit)))
-        self.assertEqual([11, 12], list(self.dag.nodes_on_wire(qbit, only_ops=True)))
+        self.assertEqual([1, 11, 12, 2], [i._node_id for i in self.dag.nodes_on_wire(qbit)])
+        self.assertEqual([11, 12],
+                         [i._node_id for i in self.dag.nodes_on_wire(qbit, only_ops=True)])
 
         cbit = self.dag.clbits()[0]
-        self.assertEqual([7, 8], list(self.dag.nodes_on_wire(cbit)))
-        self.assertEqual([], list(self.dag.nodes_on_wire(cbit, only_ops=True)))
+        self.assertEqual([7, 8], [i._node_id for i in self.dag.nodes_on_wire(cbit)])
+        self.assertEqual([], [i._node_id for i in self.dag.nodes_on_wire(cbit, only_ops=True)])
 
         (reg, _) = qbit
         with self.assertRaises(DAGCircuitError):
@@ -351,6 +351,25 @@ def test_circuit_factors(self):
         self.assertEqual(self.dag.num_tensor_factors(), 2)
 
 
+class TestCircuitSpecialCases(QiskitTestCase):
+    """DAGCircuit test for special cases, usually for regression."""
+
+    def test_circuit_depth_with_repetition(self):
+        """ When cx repeat, they are not "the same".
+        See https://github.com/Qiskit/qiskit-terra/issues/1994
+        """
+        qr1 = QuantumRegister(2)
+        qr2 = QuantumRegister(2)
+        circ = QuantumCircuit(qr1, qr2)
+        circ.h(qr1[0])
+        circ.cx(qr1[1], qr2[1])
+        circ.cx(qr1[1], qr2[1])
+        circ.h(qr2[0])
+        dag = circuit_to_dag(circ)
+
+        self.assertEqual(dag.depth(), 2)
+
+
 class TestDagEquivalence(QiskitTestCase):
     """DAGCircuit equivalence check."""
 
diff --git a/test/python/transpiler/test_commutation_analysis.py b/test/python/transpiler/test_commutation_analysis.py
--- a/test/python/transpiler/test_commutation_analysis.py
+++ b/test/python/transpiler/test_commutation_analysis.py
@@ -21,10 +21,28 @@ class TestCommutationAnalysis(QiskitTestCase):
     """Test the Communttion pass."""
 
     def setUp(self):
-
         self.pass_ = CommutationAnalysis()
         self.pset = self.pass_.property_set = PropertySet()
 
+    def assertCommutationSet(self, result, expected):
+        """ Compares the result of propertyset["commutation_set"] with a dictionary of the form
+        {'q[0]': [ [node_id, ...], [node_id, ...] ]}
+        """
+        result_to_compare = {}
+        for qbit_str, sets in result.items():
+            if not isinstance(qbit_str, str):
+                continue
+            result_to_compare[qbit_str] = []
+            for commutation_set in sets:
+                result_to_compare[qbit_str].append(
+                    sorted([node._node_id for node in commutation_set]))
+
+        for qbit_str, sets in expected.items():
+            for commutation_set in sets:
+                commutation_set.sort()
+
+        self.assertDictEqual(result_to_compare, expected)
+
     def test_commutation_set_property_is_created(self):
         """Test property is created"""
         qr = QuantumRegister(3, 'qr')
@@ -60,9 +78,20 @@ def test_all_gates(self):
         dag = circuit_to_dag(circuit)
 
         self.pass_.run(dag)
-        self.assertEqual(self.pset["commutation_set"]["qr[0]"], [[1], [5], [6], [7], [8, 9, 10, 11],
-                                                                 [12], [13], [14], [15], [16], [2]])
-        self.assertEqual(self.pset["commutation_set"]["qr[1]"], [[3], [14], [15], [16], [4]])
+
+        expected = {'qr[0]': [[1],
+                              [5],
+                              [6],
+                              [7],
+                              [8, 9, 10, 11],
+                              [12],
+                              [13],
+                              [14],
+                              [15],
+                              [16],
+                              [2]],
+                    'qr[1]': [[3], [14], [15], [16], [4]]}
+        self.assertCommutationSet(self.pset["commutation_set"], expected)
 
     def test_non_commutative_circuit(self):
         """A simple circuit where no gates commute
@@ -79,9 +108,9 @@ def test_non_commutative_circuit(self):
         dag = circuit_to_dag(circuit)
 
         self.pass_.run(dag)
-        self.assertEqual(self.pset["commutation_set"]["qr[0]"], [[1], [7], [2]])
-        self.assertEqual(self.pset["commutation_set"]["qr[1]"], [[3], [8], [4]])
-        self.assertEqual(self.pset["commutation_set"]["qr[2]"], [[5], [9], [6]])
+
+        expected = {'qr[0]': [[1], [7], [2]], 'qr[1]': [[3], [8], [4]], 'qr[2]': [[5], [9], [6]]}
+        self.assertCommutationSet(self.pset["commutation_set"], expected)
 
     def test_non_commutative_circuit_2(self):
         """A simple circuit where no gates commute
@@ -100,9 +129,11 @@ def test_non_commutative_circuit_2(self):
         dag = circuit_to_dag(circuit)
 
         self.pass_.run(dag)
-        self.assertEqual(self.pset["commutation_set"]["qr[0]"], [[1], [7], [2]])
-        self.assertEqual(self.pset["commutation_set"]["qr[1]"], [[3], [7], [9], [4]])
-        self.assertEqual(self.pset["commutation_set"]["qr[2]"], [[5], [8], [9], [6]])
+
+        expected = {'qr[0]': [[1], [7], [2]],
+                    'qr[1]': [[3], [7], [9], [4]],
+                    'qr[2]': [[5], [8], [9], [6]]}
+        self.assertCommutationSet(self.pset["commutation_set"], expected)
 
     def test_commutative_circuit(self):
         """A simple circuit where two CNOTs commute
@@ -122,9 +153,11 @@ def test_commutative_circuit(self):
         dag = circuit_to_dag(circuit)
 
         self.pass_.run(dag)
-        self.assertEqual(self.pset["commutation_set"]["qr[0]"], [[1], [7], [2]])
-        self.assertEqual(self.pset["commutation_set"]["qr[1]"], [[3], [7, 9], [4]])
-        self.assertEqual(self.pset["commutation_set"]["qr[2]"], [[5], [8], [9], [6]])
+
+        expected = {'qr[0]': [[1], [7], [2]],
+                    'qr[1]': [[3], [7, 9], [4]],
+                    'qr[2]': [[5], [8], [9], [6]]}
+        self.assertCommutationSet(self.pset["commutation_set"], expected)
 
     def test_commutative_circuit_2(self):
         """A simple circuit where a CNOT and a Z gate commute,
@@ -146,9 +179,11 @@ def test_commutative_circuit_2(self):
         dag = circuit_to_dag(circuit)
 
         self.pass_.run(dag)
-        self.assertEqual(self.pset["commutation_set"]["qr[0]"], [[1], [7, 8], [2]])
-        self.assertEqual(self.pset["commutation_set"]["qr[1]"], [[3], [7, 10], [4]])
-        self.assertEqual(self.pset["commutation_set"]["qr[2]"], [[5], [9], [10], [6]])
+
+        expected = {'qr[0]': [[1], [7, 8], [2]],
+                    'qr[1]': [[3], [7, 10], [4]],
+                    'qr[2]': [[5], [9], [10], [6]]}
+        self.assertCommutationSet(self.pset["commutation_set"], expected)
 
     def test_commutative_circuit_3(self):
         """A simple circuit where multiple gates commute
@@ -169,13 +204,14 @@ def test_commutative_circuit_3(self):
         circuit.x(qr[2])
         circuit.z(qr[0])
         circuit.cx(qr[1], qr[2])
-
         dag = circuit_to_dag(circuit)
 
         self.pass_.run(dag)
-        self.assertEqual(self.pset["commutation_set"]["qr[0]"], [[1], [7, 9, 11, 13], [2]])
-        self.assertEqual(self.pset["commutation_set"]["qr[1]"], [[3], [7, 10, 11], [14], [4]])
-        self.assertEqual(self.pset["commutation_set"]["qr[2]"], [[5], [8], [10], [12, 14], [6]])
+
+        expected = {'qr[0]': [[1], [7, 9, 11, 13], [2]],
+                    'qr[1]': [[3], [7, 10, 11], [14], [4]],
+                    'qr[2]': [[5], [8], [10], [12, 14], [6]]}
+        self.assertCommutationSet(self.pset["commutation_set"], expected)
 
     def test_jordan_wigner_type_circuit(self):
         """A Jordan-Wigner type circuit where consecutive CNOTs commute
@@ -209,12 +245,14 @@ def test_jordan_wigner_type_circuit(self):
         dag = circuit_to_dag(circuit)
 
         self.pass_.run(dag)
-        self.assertEqual(self.pset["commutation_set"]["qr[0]"], [[1], [13, 23], [2]])
-        self.assertEqual(self.pset["commutation_set"]["qr[1]"], [[3], [13], [14, 22], [23], [4]])
-        self.assertEqual(self.pset["commutation_set"]["qr[2]"], [[5], [14], [15, 21], [22], [6]])
-        self.assertEqual(self.pset["commutation_set"]["qr[3]"], [[7], [15], [16, 20], [21], [8]])
-        self.assertEqual(self.pset["commutation_set"]["qr[4]"], [[9], [16], [17, 19], [20], [10]])
-        self.assertEqual(self.pset["commutation_set"]["qr[5]"], [[11], [17], [18], [19], [12]])
+
+        expected = {'qr[0]': [[1], [13, 23], [2]],
+                    'qr[1]': [[3], [13], [14, 22], [23], [4]],
+                    'qr[2]': [[5], [14], [15, 21], [22], [6]],
+                    'qr[3]': [[7], [15], [16, 20], [21], [8]],
+                    'qr[4]': [[9], [16], [17, 19], [20], [10]],
+                    'qr[5]': [[11], [17], [18], [19], [12]]}
+        self.assertCommutationSet(self.pset["commutation_set"], expected)
 
     def test_all_commute_circuit(self):
         """Test circuit with that all commute"""
@@ -233,11 +271,13 @@ def test_all_commute_circuit(self):
         dag = circuit_to_dag(circuit)
 
         self.pass_.run(dag)
-        self.assertEqual(self.pset["commutation_set"]["qr[0]"], [[1], [11, 15, 17], [2]])
-        self.assertEqual(self.pset["commutation_set"]["qr[1]"], [[3], [11, 12, 17, 18], [4]])
-        self.assertEqual(self.pset["commutation_set"]["qr[2]"], [[5], [12, 14, 18, 20], [6]])
-        self.assertEqual(self.pset["commutation_set"]["qr[3]"], [[7], [13, 14, 19, 20], [8]])
-        self.assertEqual(self.pset["commutation_set"]["qr[4]"], [[9], [13, 16, 19], [10]])
+
+        expected = {'qr[0]': [[1], [11, 15, 17], [2]],
+                    'qr[1]': [[3], [11, 12, 17, 18], [4]],
+                    'qr[2]': [[5], [12, 14, 18, 20], [6]],
+                    'qr[3]': [[7], [13, 14, 19, 20], [8]],
+                    'qr[4]': [[9], [13, 16, 19], [10]]}
+        self.assertCommutationSet(self.pset["commutation_set"], expected)
 
 
 if __name__ == '__main__':