Repository: Qiskit/qiskit

Issue:
need a disassembler to replace qobj_to_circuits
<!-- ⚠️ 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?

We need a function `qiskit.compiler.disassemble()` that takes an assembled qobj and returns the components:
```
circuits, run_config, user_qobj_header = disassemble_qobj(qobj)
```

Currently we have a converter for this (`qobj_to_circuits`), but this is not really a conversion as the two entities are quite different. So this should be deprecated.


Discussion:
Just to clarify, since this is a first issue, am I right in thinking that the goal here is splitting out the run_config and user_qobj_header, not in covering differences in the qobj instruction and Terra circuit API (conditionals, memory/registers, bfuncs, ...) beyond what's already covered by `qobj_to_circuits`?

qobj has a superset of the functionality supported by the Terra circuits API, so a general disassembly won't always be trivial. We could limit scope to terra-generated qobj, without conditionals, (effectively what's covered by `qobj_to_circuits` now) and raise an exception for anything more complicated.
Yes I think you are right. This issue is more about returning all the other stuff (header, config), not only the circuit (as `qobj_to_circuit` does).

Some things like 'registers' will not have equivalents in the circuit, so I think they may get lost. But I think conditionals are fine no? At least the simple conditionals we have now.

Resolving patch:
diff --git a/qiskit/compiler/__init__.py b/qiskit/compiler/__init__.py
--- a/qiskit/compiler/__init__.py
+++ b/qiskit/compiler/__init__.py
@@ -12,4 +12,5 @@
 from .run_config import RunConfig
 from .transpile_config import TranspileConfig
 from .assembler import assemble_circuits, assemble_schedules
+from .disassembler import disassemble
 from .transpiler import transpile
diff --git a/qiskit/compiler/disassembler.py b/qiskit/compiler/disassembler.py
new file mode 100644
--- /dev/null
+++ b/qiskit/compiler/disassembler.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2019, IBM.
+#
+# This source code is licensed under the Apache License, Version 2.0 found in
+# the LICENSE.txt file in the root directory of this source tree.
+
+# pylint: disable=missing-return-type-doc
+
+"""Disassemble function for a qobj into a list of circuits and it's config"""
+
+from qiskit.circuit.classicalregister import ClassicalRegister
+from qiskit.circuit.quantumcircuit import QuantumCircuit
+from qiskit.circuit.quantumregister import QuantumRegister
+
+
+# TODO: This is broken for conditionals. Will fix after circuits_2_qobj pr
+def _experiments_to_circuits(qobj):
+    """Return a list of QuantumCircuit object(s) from a qobj
+
+    Args:
+        qobj (Qobj): The Qobj object to convert to QuantumCircuits
+    Returns:
+        list: A list of QuantumCircuit objects from the qobj
+
+    """
+    if qobj.experiments:
+        circuits = []
+        for x in qobj.experiments:
+            quantum_registers = [QuantumRegister(i[1], name=i[0])
+                                 for i in x.header.qreg_sizes]
+            classical_registers = [ClassicalRegister(i[1], name=i[0])
+                                   for i in x.header.creg_sizes]
+            circuit = QuantumCircuit(*quantum_registers,
+                                     *classical_registers,
+                                     name=x.header.name)
+            qreg_dict = {}
+            creg_dict = {}
+            for reg in quantum_registers:
+                qreg_dict[reg.name] = reg
+            for reg in classical_registers:
+                creg_dict[reg.name] = reg
+            for i in x.instructions:
+                instr_method = getattr(circuit, i.name)
+                qubits = []
+                try:
+                    for qubit in i.qubits:
+                        qubit_label = x.header.qubit_labels[qubit]
+                        qubits.append(
+                            qreg_dict[qubit_label[0]][qubit_label[1]])
+                except Exception:  # pylint: disable=broad-except
+                    pass
+                clbits = []
+                try:
+                    for clbit in i.memory:
+                        clbit_label = x.header.clbit_labels[clbit]
+                        clbits.append(
+                            creg_dict[clbit_label[0]][clbit_label[1]])
+                except Exception:  # pylint: disable=broad-except
+                    pass
+                params = []
+                try:
+                    params = i.params
+                except Exception:  # pylint: disable=broad-except
+                    pass
+                if i.name in ['snapshot']:
+                    instr_method(*params)
+                elif i.name == 'initialize':
+                    instr_method(params, qubits)
+                else:
+                    instr_method(*params, *qubits, *clbits)
+            circuits.append(circuit)
+        return circuits
+    return None
+
+
+def disassemble(qobj):
+    """Dissasemble a qobj and return the circuits, run_config, and user header
+
+    Args:
+        qobj (Qobj): The input qobj object to dissasemble
+    Returns:
+        circuits (list): A list of quantum circuits
+        run_config (dict): The dist of the run config
+        user_qobj_header (dict): The dict of any user headers in the qobj
+
+    """
+    run_config = qobj.config.to_dict()
+    user_qobj_header = qobj.header.to_dict()
+    circuits = _experiments_to_circuits(qobj)
+
+    return circuits, run_config, user_qobj_header
diff --git a/qiskit/converters/qobj_to_circuits.py b/qiskit/converters/qobj_to_circuits.py
--- a/qiskit/converters/qobj_to_circuits.py
+++ b/qiskit/converters/qobj_to_circuits.py
@@ -7,10 +7,11 @@
 
 """Helper function for converting qobj to a list of circuits"""
 
-from qiskit.circuit import QuantumCircuit, ClassicalRegister, QuantumRegister
+import warnings
+
+from qiskit.compiler import disassembler
 
 
-# TODO: This is broken for conditionals. Will fix after circuits_2_qobj pr
 def qobj_to_circuits(qobj):
     """Return a list of QuantumCircuit object(s) from a qobj
 
@@ -20,51 +21,8 @@ def qobj_to_circuits(qobj):
         list: A list of QuantumCircuit objects from the qobj
 
     """
-    if qobj.experiments:
-        circuits = []
-        for x in qobj.experiments:
-            quantum_registers = [QuantumRegister(i[1], name=i[0])
-                                 for i in x.header.qreg_sizes]
-            classical_registers = [ClassicalRegister(i[1], name=i[0])
-                                   for i in x.header.creg_sizes]
-            circuit = QuantumCircuit(*quantum_registers,
-                                     *classical_registers,
-                                     name=x.header.name)
-            qreg_dict = {}
-            creg_dict = {}
-            for reg in quantum_registers:
-                qreg_dict[reg.name] = reg
-            for reg in classical_registers:
-                creg_dict[reg.name] = reg
-            for i in x.instructions:
-                instr_method = getattr(circuit, i.name)
-                qubits = []
-                try:
-                    for qubit in i.qubits:
-                        qubit_label = x.header.qubit_labels[qubit]
-                        qubits.append(
-                            qreg_dict[qubit_label[0]][qubit_label[1]])
-                except Exception:  # pylint: disable=broad-except
-                    pass
-                clbits = []
-                try:
-                    for clbit in i.memory:
-                        clbit_label = x.header.clbit_labels[clbit]
-                        clbits.append(
-                            creg_dict[clbit_label[0]][clbit_label[1]])
-                except Exception:  # pylint: disable=broad-except
-                    pass
-                params = []
-                try:
-                    params = i.params
-                except Exception:  # pylint: disable=broad-except
-                    pass
-                if i.name in ['snapshot']:
-                    instr_method(*params)
-                elif i.name == 'initialize':
-                    instr_method(params, qubits)
-                else:
-                    instr_method(*params, *qubits, *clbits)
-            circuits.append(circuit)
-        return circuits
-    return None
+    warnings.warn('qiskit.converters.qobj_to_circuit() is deprecated and will '
+                  'be removed in Qiskit Terra 0.9. Please use '
+                  'qiskit.compiler.disassemble_circuits() to convert a qobj '
+                  'to list of circuits.', DeprecationWarning)
+    return disassembler._experiments_to_circuits(qobj)


Test patch:
diff --git a/test/python/compiler/test_disassembler.py b/test/python/compiler/test_disassembler.py
new file mode 100644
--- /dev/null
+++ b/test/python/compiler/test_disassembler.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2019, IBM.
+#
+# This source code is licensed under the Apache License, Version 2.0 found in
+# the LICENSE.txt file in the root directory of this source tree.
+
+"""Assembler Test."""
+
+import unittest
+
+import numpy as np
+
+from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit
+from qiskit.compiler import assemble_circuits
+from qiskit.compiler import disassemble
+from qiskit.compiler import RunConfig
+from qiskit.test import QiskitTestCase
+
+
+class TestAssembler(QiskitTestCase):
+    """Tests for assembling circuits to qobj."""
+
+    def test_disassemble_single_circuit(self):
+        """Test assembling a single circuit.
+        """
+        qr = QuantumRegister(2, name='q')
+        cr = ClassicalRegister(2, name='c')
+        circ = QuantumCircuit(qr, cr, name='circ')
+        circ.h(qr[0])
+        circ.cx(qr[0], qr[1])
+        circ.measure(qr, cr)
+
+        run_config = RunConfig(shots=2000, memory=True)
+        qobj = assemble_circuits(circ, run_config=run_config)
+        circuits, run_config_out, headers = disassemble(qobj)
+        self.assertEqual({'shots': 2000, 'memory': True, 'memory_slots': 2,
+                          'n_qubits': 2}, run_config_out)
+        self.assertEqual(len(circuits), 1)
+        self.assertEqual(circuits[0], circ)
+        self.assertEqual({}, headers)
+
+    def test_disssemble_multiple_circuits(self):
+        """Test assembling multiple circuits, all should have the same config.
+        """
+        qr0 = QuantumRegister(2, name='q0')
+        qc0 = ClassicalRegister(2, name='c0')
+        circ0 = QuantumCircuit(qr0, qc0, name='circ0')
+        circ0.h(qr0[0])
+        circ0.cx(qr0[0], qr0[1])
+        circ0.measure(qr0, qc0)
+
+        qr1 = QuantumRegister(3, name='q1')
+        qc1 = ClassicalRegister(3, name='c1')
+        circ1 = QuantumCircuit(qr1, qc1, name='circ0')
+        circ1.h(qr1[0])
+        circ1.cx(qr1[0], qr1[1])
+        circ1.cx(qr1[0], qr1[2])
+        circ1.measure(qr1, qc1)
+
+        run_config = RunConfig(shots=100, memory=False, seed=6)
+        qobj = assemble_circuits([circ0, circ1], run_config=run_config)
+        circuits, out_run_config, headers = disassemble(qobj)
+        self.assertEqual({'shots': 100, 'memory': False, 'memory_slots': 3,
+                          'n_qubits': 3, 'seed': 6}, out_run_config)
+        self.assertEqual(len(circuits), 2)
+        for circuit in circuits:
+            self.assertIn(circuit, [circ0, circ1])
+        self.assertEqual({}, headers)
+
+    def test_assemble_no_run_config(self):
+        """Test assembling with no run_config, relying on default.
+        """
+        qr = QuantumRegister(2, name='q')
+        qc = ClassicalRegister(2, name='c')
+        circ = QuantumCircuit(qr, qc, name='circ')
+        circ.h(qr[0])
+        circ.cx(qr[0], qr[1])
+        circ.measure(qr, qc)
+
+        qobj = assemble_circuits(circ)
+        circuits, run_config_out, headers = disassemble(qobj)
+        self.assertEqual({'memory_slots': 2, 'n_qubits': 2}, run_config_out)
+        self.assertEqual(len(circuits), 1)
+        self.assertEqual(circuits[0], circ)
+        self.assertEqual({}, headers)
+
+    def test_assemble_initialize(self):
+        """Test assembling a circuit with an initialize.
+        """
+        q = QuantumRegister(2, name='q')
+        circ = QuantumCircuit(q, name='circ')
+        circ.initialize([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)], q[:])
+
+        qobj = assemble_circuits(circ)
+        circuits, run_config_out, header = disassemble(qobj)
+        self.assertEqual({'memory_slots': 0, 'n_qubits': 2}, run_config_out)
+        self.assertEqual(len(circuits), 1)
+        self.assertEqual(circuits[0], circ)
+        self.assertEqual({}, header)
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)