Repository: Qiskit/qiskit

Issue:
load_qasm_file and load_qasm_string should be part of 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 behavior?
The load_qasm_* should be part of the circuit object and not in the wrapper

```circuit(qasm_file)```

or 

```circuit.load(qasm_file)```




Discussion:
I do agree with this approach. Somebody against it?
Something like `load_qasm_file` and  `load_qasm_string`? They should be deprecated from the wrappers.
So I'm starting to implement this now. Looking at how we'd do this we can parse the qasm and get an ast pretty easily. But when it comes to converting that ast to a circuit object it gets a bit more involved. We could either use the `Unroller()` with the backend set to `CircuitBackend` inside the new constructor methods. Or alternatively we could basically inline that code. (which would end up with a bunch of duplicated code) But I just want to make sure I'm not missing something obvious or there was another path to implement this because we'll need to loop over the ast from the Qasm parser and construct the circuit from that.

Resolving patch:
diff --git a/qiskit/_quantumcircuit.py b/qiskit/_quantumcircuit.py
--- a/qiskit/_quantumcircuit.py
+++ b/qiskit/_quantumcircuit.py
@@ -5,17 +5,37 @@
 # 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=cyclic-import
+
 """
 Quantum circuit object.
 """
 import itertools
 from collections import OrderedDict
+
+from qiskit.qasm import _qasm
+from qiskit.unrollers import _unroller
+from qiskit.unrollers import _circuitbackend
 from ._qiskiterror import QISKitError
 from ._register import Register
 from ._quantumregister import QuantumRegister
 from ._classicalregister import ClassicalRegister
 
 
+def _circuit_from_qasm(qasm, basis=None):
+    default_basis = ["id", "u0", "u1", "u2", "u3", "x", "y", "z", "h", "s",
+                     "sdg", "t", "tdg", "rx", "ry", "rz", "cx", "cy", "cz",
+                     "ch", "crz", "cu1", "cu3", "swap", "ccx", "cswap"]
+    if not basis:
+        basis = default_basis
+
+    ast = qasm.parse()
+    unroll = _unroller.Unroller(
+        ast, _circuitbackend.CircuitBackend(basis))
+    circuit = unroll.execute()
+    return circuit
+
+
 class QuantumCircuit(object):
     """Quantum circuit."""
     instances = 0
@@ -36,6 +56,30 @@ class QuantumCircuit(object):
     #   "body"   = GateBody AST node
     definitions = OrderedDict()
 
+    @staticmethod
+    def from_qasm_file(path):
+        """Take in a QASM file and generate a QuantumCircuit object.
+
+        Args:
+          path (str): Path to the file for a QASM program
+        Return:
+          QuantumCircuit: The QuantumCircuit object for the input QASM
+        """
+        qasm = _qasm.Qasm(filename=path)
+        return _circuit_from_qasm(qasm)
+
+    @staticmethod
+    def from_qasm_str(qasm_str):
+        """Take in a QASM string and generate a QuantumCircuit object.
+
+        Args:
+          qasm_str (str): A QASM program string
+        Return:
+          QuantumCircuit: The QuantumCircuit object for the input QASM
+        """
+        qasm = _qasm.Qasm(data=qasm_str)
+        return _circuit_from_qasm(qasm)
+
     def __init__(self, *regs, name=None):
         """Create a new circuit.
 
diff --git a/qiskit/unrollers/_circuitbackend.py b/qiskit/unrollers/_circuitbackend.py
--- a/qiskit/unrollers/_circuitbackend.py
+++ b/qiskit/unrollers/_circuitbackend.py
@@ -9,12 +9,12 @@
 Backend for the unroller that produces a QuantumCircuit.
 """
 
-from qiskit import QuantumCircuit
-from ._backenderror import BackendError
-from ._unrollerbackend import UnrollerBackend
+from qiskit import _quantumcircuit
+from qiskit.unrollers import _backenderror
+from qiskit.unrollers import _unrollerbackend
 
 
-class CircuitBackend(UnrollerBackend):
+class CircuitBackend(_unrollerbackend.UnrollerBackend):
     """Backend for the unroller that produces a QuantumCircuit.
 
     By default, basis gates are the QX gates.
@@ -35,7 +35,7 @@ def __init__(self, basis=None):
         self.gates = {}
         self.listen = True
         self.in_gate = ""
-        self.circuit = QuantumCircuit()
+        self.circuit = _quantumcircuit.QuantumCircuit()
 
     def set_basis(self, basis):
         """Declare the set of user-defined gates to emit.
@@ -79,21 +79,23 @@ def _map_qubit(self, qubit):
         """Map qubit tuple (regname, index) to (QuantumRegister, index)."""
         qregs = self.circuit.get_qregs()
         if qubit[0] not in qregs:
-            raise BackendError("qreg %s does not exist" % qubit[0])
+            raise _backenderror.BackendError(
+                "qreg %s does not exist" % qubit[0])
         return (qregs[qubit[0]], qubit[1])
 
     def _map_bit(self, bit):
         """Map bit tuple (regname, index) to (ClassicalRegister, index)."""
         cregs = self.circuit.get_cregs()
         if bit[0] not in cregs:
-            raise BackendError("creg %s does not exist" % bit[0])
+            raise _backenderror.BackendError(
+                "creg %s does not exist" % bit[0])
         return (cregs[bit[0]], bit[1])
 
     def _map_creg(self, creg):
         """Map creg name to ClassicalRegister."""
         cregs = self.circuit.get_cregs()
         if creg not in cregs:
-            raise BackendError("creg %s does not exist" % creg)
+            raise _backenderror.BackendError("creg %s does not exist" % creg)
         return cregs[creg]
 
     def u(self, arg, qubit, nested_scope=None):
@@ -190,7 +192,8 @@ def start_gate(self, name, args, qubits, nested_scope=None, extra_fields=None):
         """
         if self.listen and name not in self.basis \
            and self.gates[name]["opaque"]:
-            raise BackendError("opaque gate %s not in basis" % name)
+            raise _backenderror.BackendError(
+                "opaque gate %s not in basis" % name)
         if self.listen and name in self.basis:
             self.in_gate = name
             self.listen = False
@@ -238,14 +241,15 @@ def start_gate(self, name, args, qubits, nested_scope=None, extra_fields=None):
                    "y": [(0, 1), lambda x: self.circuit.y(x[1][0])],
                    "z": [(0, 1), lambda x: self.circuit.z(x[1][0])]}
             if name not in lut:
-                raise BackendError("gate %s not in standard extensions" %
-                                   name)
+                raise _backenderror.BackendError(
+                    "gate %s not in standard extensions" % name)
             gate_data = lut[name]
             if gate_data[0] != (len(args), len(qubits)):
-                raise BackendError("gate %s signature (%d, %d) is " %
-                                   (name, len(args), len(qubits)) +
-                                   "incompatible with the standard " +
-                                   "extensions")
+                raise _backenderror.BackendError(
+                    "gate %s signature (%d, %d) is " %
+                    (name, len(args), len(qubits)) +
+                    "incompatible with the standard " +
+                    "extensions")
             this_gate = gate_data[1]([list(map(lambda x:
                                                x.sym(nested_scope), args)),
                                       list(map(self._map_qubit, qubits))])
diff --git a/qiskit/wrapper/_wrapper.py b/qiskit/wrapper/_wrapper.py
--- a/qiskit/wrapper/_wrapper.py
+++ b/qiskit/wrapper/_wrapper.py
@@ -7,6 +7,7 @@
 
 """Helper module for simplified QISKit usage."""
 import logging
+import warnings
 
 from ._circuittoolkit import circuit_from_qasm_file, circuit_from_qasm_string
 
@@ -30,6 +31,9 @@ def load_qasm_string(qasm_string, name=None,
     Raises:
         QISKitError: if the string is not valid QASM
     """
+    warnings.warn('The load_qasm_string() function is deprecated and will be '
+                  'removed in a future release. Instead use '
+                  'QuantumCircuit.from_qasm_str().', DeprecationWarning)
     return circuit_from_qasm_string(qasm_string, name, basis_gates)
 
 
@@ -49,6 +53,9 @@ def load_qasm_file(qasm_file, name=None,
     Raises:
         QISKitError: if the file cannot be read.
     """
+    warnings.warn('The load_qasm_file() function is deprecated and will be '
+                  'removed in a future release. Instead use '
+                  'QuantumCircuit.from_qasm_file().', DeprecationWarning)
     return circuit_from_qasm_file(qasm_file, name, basis_gates)
 
 


Test patch:
diff --git a/test/python/test_circuit.py b/test/python/test_circuit.py
--- a/test/python/test_circuit.py
+++ b/test/python/test_circuit.py
@@ -9,6 +9,10 @@
 
 """Test Qiskit's QuantumCircuit class."""
 
+import os
+import tempfile
+import unittest
+
 import qiskit.extensions.simulator
 from qiskit import Aer
 from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
@@ -90,6 +94,67 @@ def test_circuit_qasm(self):
 measure qr2[1] -> cr[2];\n"""
         self.assertEqual(qc.qasm(), expected_qasm)
 
+    def test_circuit_from_qasm_str(self):
+        """Test qasm constructor from string."""
+        qasm_str = """OPENQASM 2.0;
+include "qelib1.inc";
+qreg qr1[1];
+qreg qr2[2];
+creg cr[3];
+u1(0.300000000000000) qr1[0];
+u2(0.200000000000000,0.100000000000000) qr2[0];
+u3(0.300000000000000,0.200000000000000,0.100000000000000) qr2[1];
+s qr2[1];
+sdg qr2[1];
+cx qr1[0],qr2[1];
+barrier qr2[0],qr2[1];
+cx qr2[1],qr1[0];
+h qr2[1];
+if(cr==0) x qr2[1];
+if(cr==1) y qr1[0];
+if(cr==2) z qr1[0];
+barrier qr1[0],qr2[0],qr2[1];
+measure qr1[0] -> cr[0];
+measure qr2[0] -> cr[1];
+measure qr2[1] -> cr[2];\n"""
+        qc = QuantumCircuit.from_qasm_str(qasm_str)
+        self.assertEqual(len(qc.get_cregs()), 1)
+        self.assertEqual(len(qc.get_qregs()), 2)
+        self.assertEqual(qasm_str, qc.qasm())
+
+    @unittest.skipIf(os.getenv('APPVEYOR', None),
+                     'Cannot make temp file in Appveyor.')
+    def test_circuit_from_qasm_file(self):
+        """Test qasm constructor from file."""
+        qasm_str = """OPENQASM 2.0;
+include "qelib1.inc";
+qreg qr1[1];
+qreg qr2[2];
+creg cr[3];
+u1(0.300000000000000) qr1[0];
+u2(0.200000000000000,0.100000000000000) qr2[0];
+u3(0.300000000000000,0.200000000000000,0.100000000000000) qr2[1];
+s qr2[1];
+sdg qr2[1];
+cx qr1[0],qr2[1];
+barrier qr2[0],qr2[1];
+cx qr2[1],qr1[0];
+h qr2[1];
+if(cr==0) x qr2[1];
+if(cr==1) y qr1[0];
+if(cr==2) z qr1[0];
+barrier qr1[0],qr2[0],qr2[1];
+measure qr1[0] -> cr[0];
+measure qr2[0] -> cr[1];
+measure qr2[1] -> cr[2];\n"""
+        with tempfile.NamedTemporaryFile(suffix='.qasm') as qasm_file:
+            qasm_file.write(qasm_str.encode('utf8'))
+            qasm_file.flush()
+            qc = QuantumCircuit.from_qasm_file(qasm_file.name)
+        self.assertEqual(len(qc.get_cregs()), 1)
+        self.assertEqual(len(qc.get_qregs()), 2)
+        self.assertEqual(qasm_str, qc.qasm())
+
 
 class TestCircuitCombineExtend(QiskitTestCase):
     """Test combining and extending of QuantumCircuits."""