Repository: Qiskit/qiskit

Issue:
Return important circuit properties from a QuantumCircuit
<!-- ⚠️ 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 enhancement?
It is often of interest to compute several properties of a quantum circuit after compiling to a given device.  For example, # of gates, # of cx-gates, depth, and # of swap gates added, are all properties of interest.  However, only some of these values (# gates, and cx gates) can actually be extracted from the qasm in a qobj, others require going qobj -> circuit -> DAG.  Some, like # of swaps added, cannot be gathered from the given information. These values should be reported in the qobj experiment headers.




Discussion:
Related to #1083: make the compiled circuit still look like a circuit, so it's easy to analyze.

@1ucian0 is proposing that we combine the QuantumCircuit class and the DAGCircuit class, because already whatever we want to do on QuantumCircuit we almost always use DAGCircuit. So we make the QuantumCircuit richer, by making the underlying datastructure a graph. and at the time of construction, the ops get appended to a graph (rather than a useless list). 
I already have an issue for that. #1070. 
Should these properties live in the transpiler property set? or in the circuit itself and be recalculated in every pass?
The properties are properties of the DAG, and properties obtained from running some of the transpiler passes.  However, we are going to go from circuits to circuits in the compile process, and therefore things like swaps added need to be attached to the circuit itself.
After #1173 is done, `transpile()` will consume a circuit and return a circuit. So in order for that to be useful, we need to take up this issue (i.e. return common metrics of a circuit object).

I suggest we just do a trivial thing now.

In `_quantumcircuit.py`:
```python
def property_summary(self):
    dag = DAGCircuit.fromQuantumCircuit(self)
    return dag.property_summary()
```

This would mean that you can easily visualize and get circuit resource usage before/after transpilation.

This back-and-forth between QuantumCircuit and DAGCircuit will be resolved later with #1163.

Resolving patch:
diff --git a/qiskit/_quantumcircuit.py b/qiskit/_quantumcircuit.py
--- a/qiskit/_quantumcircuit.py
+++ b/qiskit/_quantumcircuit.py
@@ -23,6 +23,7 @@
 from qiskit._quantumregister import QuantumRegister
 from qiskit._classicalregister import ClassicalRegister
 from qiskit.tools import visualization
+from qiskit.dagcircuit import DAGCircuit
 
 
 def _circuit_from_qasm(qasm, basis=None):
@@ -374,5 +375,34 @@ def draw(self, scale=0.7, filename=None, style=None, output='text',
                                             plot_barriers=plot_barriers,
                                             reverse_bits=reverse_bits)
 
+    def size(self):
+        """Return total number of operations in circuit."""
+        dag = DAGCircuit.fromQuantumCircuit(self)
+        return dag.size()
+
+    def depth(self):
+        """Return circuit depth (i.e. length of critical path)."""
+        dag = DAGCircuit.fromQuantumCircuit(self)
+        return dag.depth()
+
+    def width(self):
+        """Return number of qubits in circuit."""
+        dag = DAGCircuit.fromQuantumCircuit(self)
+        return dag.width()
+
+    def count_ops(self):
+        """Count each operation kind in the circuit.
+
+        Returns:
+            dict: a breakdown of how many operations of each kind.
+        """
+        dag = DAGCircuit.fromQuantumCircuit(self)
+        return dag.count_ops()
+
+    def num_tensor_factors(self):
+        """How many non-entangled subcircuits can the circuit be factored to."""
+        dag = DAGCircuit.fromQuantumCircuit(self)
+        return dag.num_tensor_factors()
+
     def __str__(self):
         return str(self.draw(output='text'))
diff --git a/qiskit/dagcircuit/_dagcircuit.py b/qiskit/dagcircuit/_dagcircuit.py
--- a/qiskit/dagcircuit/_dagcircuit.py
+++ b/qiskit/dagcircuit/_dagcircuit.py
@@ -1331,7 +1331,7 @@ def count_ops(self):
                     op_dict[name] += 1
         return op_dict
 
-    def property_summary(self):
+    def properties(self):
         """Return a dictionary of circuit properties."""
         summary = {"size": self.size(),
                    "depth": self.depth(),
diff --git a/qiskit/transpiler/_transpiler.py b/qiskit/transpiler/_transpiler.py
--- a/qiskit/transpiler/_transpiler.py
+++ b/qiskit/transpiler/_transpiler.py
@@ -263,7 +263,7 @@ def transpile_dag(dag, basis_gates='u1,u2,u3,cx,id', coupling_map=None,
         # if a coupling map is given compile to the map
         if coupling_map:
             logger.info("pre-mapping properties: %s",
-                        dag.property_summary())
+                        dag.properties())
             # Insert swap gates
             coupling = Coupling(coupling_list2dict(coupling_map))
             removed_meas = remove_last_measurements(dag)
@@ -285,7 +285,7 @@ def transpile_dag(dag, basis_gates='u1,u2,u3,cx,id', coupling_map=None,
             return_last_measurements(dag, removed_meas,
                                      last_layout)
             logger.info("post-mapping properties: %s",
-                        dag.property_summary())
+                        dag.properties())
 
     # choose output format
     # TODO: do we need all of these formats, or just the dag?


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
@@ -14,6 +14,7 @@
 from qiskit.dagcircuit import DAGCircuit
 from qiskit._quantumregister import QuantumRegister
 from qiskit._classicalregister import ClassicalRegister
+from qiskit._quantumcircuit import QuantumCircuit
 from .common import QiskitTestCase
 
 
@@ -118,5 +119,51 @@ def test_layers_basic(self):
             ], name_layers)
 
 
+class TestCircuitProperties(QiskitTestCase):
+    """DAGCircuit properties test."""
+    def setUp(self):
+        qr1 = QuantumRegister(4)
+        qr2 = QuantumRegister(2)
+        circ = QuantumCircuit(qr1, qr2)
+        circ.h(qr1[0])
+        circ.cx(qr1[2], qr1[3])
+        circ.h(qr1[2])
+        circ.t(qr1[2])
+        circ.ch(qr1[2], qr1[1])
+        circ.u2(0.1, 0.2, qr1[3])
+        circ.ccx(qr2[0], qr2[1], qr1[0])
+
+        self.dag = DAGCircuit.fromQuantumCircuit(circ)
+
+    def test_circuit_size(self):
+        """Test total number of operations in circuit."""
+        self.assertEqual(self.dag.size(), 7)
+
+    def test_circuit_depth(self):
+        """Test circuit depth."""
+        self.assertEqual(self.dag.depth(), 4)
+
+    def test_circuit_width(self):
+        """Test number of qubits in circuit."""
+        self.assertEqual(self.dag.width(), 6)
+
+    def test_circuit_operations(self):
+        """Test circuit operations breakdown by kind of op."""
+        operations = {
+            'h': 2,
+            't': 1,
+            'u2': 1,
+            'cx': 1,
+            'ch': 1,
+            'ccx': 1
+        }
+
+        self.assertDictEqual(self.dag.count_ops(), operations)
+
+    def test_circuit_factors(self):
+        """Test number of separable factors in circuit."""
+        self.assertEqual(self.dag.num_tensor_factors(), 2)
+
+
 if __name__ == '__main__':
     unittest.main()