Repository: Qiskit/qiskit

Issue:
function to calculate 2-qubit gate depth
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected behavior?
Currently `circuit.depth()` considers all gates in the circuit. In the context of present experiments 2-qubit gates are often much longer than 1-qubit gates, so a function `circuit.depth_2q_gates()` would be useful. This would be a better estimate of true depth cost. Of course the most accurate would be to schedule the circuit with the gate durations and look at `circuit.duration`, but that requires further compilation and is not a pure analysis of the circuit.




Discussion:
It would be nice to just have this do non-local gates in general rather than just 2Q. But I agree this is much needed.  Especially after the IBM Quantum basis gate change. One might have to be careful about things like conditional 1Q gates and what those might do to the depth even if not explicitly in the computation. 
Actually, the circuit depth is the number of non-parallel gates that are executed in the circuit secuentially. Thus, a conditional gate such as a CNOT is not a matter. If you consider it a unit in each qubit, it will be counted as a 1 unit, as they are executed in parallel. And if you consider it as a 1 gate, then there is no problem. 
"The depth of a circuit is the number of distinct timesteps at which gates are applied; the point of this problem is that it is  possible to parallelize the Cn(X) construction by applying many gates in parallel during the same timestep.)" Quantum computation and quantum information" Nielsen & Chuang
As I see, the function you demmand would have the same result as the circuit.depth(). However, it is true that having the duration function we would have two different result.

If implementing this function what would be the difference between 1-qubit gate and 2-qubit gate with reference to the depth?
I think this can be accommodated with https://github.com/Qiskit/retworkx/pull/383.

I like the approach there which allows the user to provide the weight function so we don't have to make assumptions about use cases.

> Actually, the circuit depth is the number of non-parallel gates that are executed in the circuit secuentially. Thus, a conditional gate such as a CNOT is not a matter.

conditional gates take longer to execute as it takes the time to evaluate the condition plus execute the gate. Here "conditional" means conditioned on the value of a measured bit.
Your going to get an overhead from the dag conversion. That is why the current depth uses the list data. Ideally this would be computed as gates are added / modified. 
The easy way of implementing this function could be a similar function to circuit.depth() in which, you are able to set a different weight to the conditional gates. Then, you will obtain a different result only of the weight for this conditional gates is different from weight=1. Is this a good idea?

If it is, I would like to work in this issue to implement it. Thanks.
@VicentePerezSoloviev Are you working on this issue? If not then I would like to work on this issue.
Apologies for the slow response on this, it is already being worked on by @y4izus, we will update the assignment to reflect this and prevent further confusion. 

thanks!
thanks @1ucian0 !

Resolving patch:
diff --git a/qiskit/circuit/library/blueprintcircuit.py b/qiskit/circuit/library/blueprintcircuit.py
--- a/qiskit/circuit/library/blueprintcircuit.py
+++ b/qiskit/circuit/library/blueprintcircuit.py
@@ -131,10 +131,10 @@ def __len__(self):
     def __getitem__(self, item):
         return self.data[item]
 
-    def size(self):
+    def size(self, *args, **kwargs):
         if self._data is None:
             self._build()
-        return super().size()
+        return super().size(*args, **kwargs)
 
     def to_instruction(self, parameter_map=None, label=None):
         if self._data is None:
@@ -146,10 +146,10 @@ def to_gate(self, parameter_map=None, label=None):
             self._build()
         return super().to_gate(parameter_map, label=label)
 
-    def depth(self):
+    def depth(self, *args, **kwargs):
         if self._data is None:
             self._build()
-        return super().depth()
+        return super().depth(*args, **kwargs)
 
     def count_ops(self):
         if self._data is None:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -1756,22 +1756,26 @@ def draw(
             cregbundle=cregbundle,
         )
 
-    def size(self) -> int:
-        """Returns total number of gate operations in circuit.
+    def size(self, filter_function: Optional[callable] = lambda x: not x[0]._directive) -> int:
+        """Returns total number of instructions in circuit.
+
+        Args:
+            filter_function (callable): a function to filter out some instructions.
+                Should take as input a tuple of (Instruction, list(Qubit), list(Clbit)).
+                By default filters out "directives", such as barrier or snapshot.
 
         Returns:
             int: Total number of gate operations.
         """
-        gate_ops = 0
-        for instr, _, _ in self._data:
-            if not instr._directive:
-                gate_ops += 1
-        return gate_ops
+        return sum(map(filter_function, self._data))
 
-    def depth(self) -> int:
+    def depth(self, filter_function: Optional[callable] = lambda x: not x[0]._directive) -> int:
         """Return circuit depth (i.e., length of critical path).
-        This does not include compiler or simulator directives
-        such as 'barrier' or 'snapshot'.
+
+        Args:
+            filter_function (callable): a function to filter out some instructions.
+                Should take as input a tuple of (Instruction, list(Qubit), list(Clbit)).
+                By default filters out "directives", such as barrier or snapshot.
 
         Returns:
             int: Depth of circuit.
@@ -1799,8 +1803,6 @@ def depth(self) -> int:
         # line so that they all stacked at the same depth.
         # Conditional gates act on all cbits in the register
         # they are conditioned on.
-        # We treat barriers or snapshots different as
-        # They are transpiler and simulator directives.
         # The max stack height is the circuit depth.
         for instr, qargs, cargs in self._data:
             levels = []
@@ -1809,10 +1811,10 @@ def depth(self) -> int:
                 # Add to the stacks of the qubits and
                 # cbits used in the gate.
                 reg_ints.append(bit_indices[reg])
-                if instr._directive:
-                    levels.append(op_stack[reg_ints[ind]])
-                else:
+                if (instr, qargs, cargs) in filter(filter_function, self._data):
                     levels.append(op_stack[reg_ints[ind]] + 1)
+                else:
+                    levels.append(op_stack[reg_ints[ind]])
             # Assuming here that there is no conditional
             # snapshots or barriers ever.
             if instr.condition:


Test patch:
diff --git a/test/python/circuit/test_circuit_properties.py b/test/python/circuit/test_circuit_properties.py
--- a/test/python/circuit/test_circuit_properties.py
+++ b/test/python/circuit/test_circuit_properties.py
@@ -393,6 +393,85 @@ def test_circuit_depth_snap3(self):
         circ.cx(2, 3)
         self.assertEqual(circ.depth(), 4)
 
+    def test_circuit_depth_2qubit(self):
+        """Test finding depth of two-qubit gates only.
+
+             ┌───┐
+        q_0: ┤ H ├──■───────────────────
+             └───┘┌─┴─┐┌─────────┐   ┌─┐
+        q_1: ─────┤ X ├┤ Rz(0.1) ├─■─┤M├
+             ┌───┐└───┘└─────────┘ │ └╥┘
+        q_2: ┤ H ├──■──────────────┼──╫─
+             └───┘┌─┴─┐            │  ║
+        q_3: ─────┤ X ├────────────■──╫─
+                  └───┘               ║
+        c: 1/═════════════════════════╩═
+                                      0
+        """
+        circ = QuantumCircuit(4, 1)
+        circ.h(0)
+        circ.cx(0, 1)
+        circ.h(2)
+        circ.cx(2, 3)
+        circ.rz(0.1, 1)
+        circ.cz(1, 3)
+        circ.measure(1, 0)
+        self.assertEqual(circ.depth(lambda x: x[0].num_qubits == 2), 2)
+
+    def test_circuit_depth_multiqubit_or_conditional(self):
+        """Test finding depth of multi-qubit or conditional gates.
+
+             ┌───┐                              ┌───┐
+        q_0: ┤ H ├──■───────────────────────────┤ X ├───
+             └───┘  │  ┌─────────┐        ┌─┐   └─╥─┘
+        q_1: ───────■──┤ Rz(0.1) ├──────■─┤M├─────╫─────
+                  ┌─┴─┐└──┬───┬──┘      │ └╥┘     ║
+        q_2: ─────┤ X ├───┤ H ├─────■───┼──╫──────╫─────
+                  └───┘   └───┘   ┌─┴─┐ │  ║      ║
+        q_3: ─────────────────────┤ X ├─■──╫──────╫─────
+                                  └───┘    ║ ┌────╨────┐
+        c: 1/══════════════════════════════╩═╡ c_0 = T ╞
+                                           0 └─────────┘
+        """
+        circ = QuantumCircuit(4, 1)
+        circ.h(0)
+        circ.ccx(0, 1, 2)
+        circ.h(2)
+        circ.cx(2, 3)
+        circ.rz(0.1, 1)
+        circ.cz(1, 3)
+        circ.measure(1, 0)
+        circ.x(0).c_if(0, 1)
+        self.assertEqual(
+            circ.depth(lambda x: x[0].num_qubits >= 2 or x[0].condition is not None), 4
+        )
+
+    def test_circuit_depth_first_qubit(self):
+        """Test finding depth of gates touching q0 only.
+
+             ┌───┐        ┌───┐
+        q_0: ┤ H ├──■─────┤ T ├─────────
+             └───┘┌─┴─┐┌──┴───┴──┐   ┌─┐
+        q_1: ─────┤ X ├┤ Rz(0.1) ├─■─┤M├
+             ┌───┐└───┘└─────────┘ │ └╥┘
+        q_2: ┤ H ├──■──────────────┼──╫─
+             └───┘┌─┴─┐            │  ║
+        q_3: ─────┤ X ├────────────■──╫─
+                  └───┘               ║
+        c: 1/═════════════════════════╩═
+                                      0
+        """
+        circ = QuantumCircuit(4, 1)
+        circ.h(0)
+        circ.cx(0, 1)
+        circ.t(0)
+        circ.h(2)
+        circ.cx(2, 3)
+        circ.rz(0.1, 1)
+        circ.cz(1, 3)
+        circ.measure(1, 0)
+        self.assertEqual(circ.depth(lambda x: circ.qubits[0] in x[1]), 3)
+
     def test_circuit_size_empty(self):
         """Circuit.size should return 0 for an empty circuit."""
         size = 4
@@ -414,17 +493,17 @@ def test_circuit_size_single_qubit_gates(self):
         qc.h(q[1])
         self.assertEqual(qc.size(), 2)
 
-    def test_circuit_size_two_qubit_gates(self):
-        """Circuit.size should increment for each added two qubit gate."""
-        size = 4
+    def test_circuit_size_2qubit(self):
+        """Circuit.size of only 2-qubit gates."""
+        size = 3
         q = QuantumRegister(size, "q")
         c = ClassicalRegister(size, "c")
         qc = QuantumCircuit(q, c)
 
         qc.cx(q[0], q[1])
-        self.assertEqual(qc.size(), 1)
-        qc.cx(q[2], q[3])
-        self.assertEqual(qc.size(), 2)
+        qc.rz(0.1, q[1])
+        qc.rzz(0.1, q[1], q[2])
+        self.assertEqual(qc.size(lambda x: x[0].num_qubits == 2), 2)
 
     def test_circuit_size_ignores_barriers_snapshots(self):
         """Circuit.size should not count barriers or snapshots."""