Repository: Qiskit/qiskit

Issue:
Qasms with composite gates don't load
Here is the qasm string

'OPENQASM 2.0;\ninclude "qelib1.inc";\ngate r q { h q; s q; h q; s q; }\ngate rinv q {sdg q; h q; sdg q; h q; }\ngate sinv q {s q; z q;}\nqreg q[3];\ncreg c[3];\nrinv q[0];\ncx q[1], q[2];\nr q[1];\nrinv q[2];\ncx q[0], q[2];\nrinv q[0];\nr q[2];\ncx q[0], q[1];\nrinv q[1];\ny q[0];\nbarrier q[0], q[1], q[2];\nr q[1];\ncx q[0], q[1];\nrinv q[2];\nr q[0];\ncx q[0], q[2];\nr q[2];\nrinv q[1];\ncx q[1], q[2];\nr q[0];\nx q[0];\nz q[1];\nmeasure q[0] -> c[0];\nmeasure q[1] -> c[1];\nmeasure q[2] -> c[2];'

If I run `from_qasm_str`

I get

`QiskitError: 'unknown operation for ast node name rinv'`

### Informations

- **Qiskit Terra version**:
- **Python version**:
- **Operating system**:

### What is the current behavior?



### Steps to reproduce the problem



### What is the expected behavior?



### Suggested solutions





Discussion:
yeah, this will not work. We give you an alternative way to do it soon. 
oops im sorry. This needs to be fixed. @1ucian0 can you look at this. 
This is part of a bigger problem that we have with composite gates, I think.
Is there an alternative way to do it?
Smaller program to reproduce the problem:
```
QuantumCircuit.from_qasm_str('OPENQASM 2.0;\ninclude "qelib1.inc";\ngate rinv q {sdg q; h q; sdg q; h q; }\nqreg q[3];\nrinv q[2];')
```

@ajavadia , is there any alternative way?
Now that composite gates are fixed... I will try to attack this issue... which me luck 🤞 
@1ucian0 Thank you for tackling this issue. Any progress for non-opaque gates? I've encountered the following error when loading qasm file with custom gates and found this issue. 
```
qiskit.exceptions.QiskitError: 'Custom non-opaque gates are not supported by as_to_dag module'
```
The problem is that we dont have an easy way to bind variables at DAG level. We are planing to refactoring the DAG, so I'm not fully sure if its worthy. What do you think @kdk ?
What's the current timeline to add this functionality back in? I just upgraded some production code from Qiskit 0.6.x (ancient, I know) and am trying to determine whether I'll need to create a workaround or whether I should just wait for a fix to land. Thanks!

Re. implementation: wouldn't it make more sense to just do AST -> circuit directly rather than AST -> DAG -> circuit? The only place `ast_to_dag` is used in Qiskit is in conjunction with `dag_to_circuit` to create a circuit from QASM, and going to a circuit directly would allow you to leverage their existing machinery for custom instructions, variable binding, etc. Maintaining backwards compatibility would also be easy (at least for users not importing `AstInterpreter` directly):

```python
def ast_to_dag(ast):
    return circuit_to_dag(ast_to_circuit(ast))
```

Resolving patch:
diff --git a/qiskit/converters/ast_to_dag.py b/qiskit/converters/ast_to_dag.py
--- a/qiskit/converters/ast_to_dag.py
+++ b/qiskit/converters/ast_to_dag.py
@@ -18,10 +18,12 @@
 Acts as an OpenQASM interpreter.
 """
 from collections import OrderedDict
-from qiskit.circuit import QuantumRegister, ClassicalRegister, Gate
 from qiskit.dagcircuit import DAGCircuit
 from qiskit.exceptions import QiskitError
 
+from qiskit.circuit import QuantumRegister, ClassicalRegister, Gate
+from qiskit.qasm.node.real import Real
+from qiskit.circuit.instruction import Instruction
 from qiskit.circuit.measure import Measure
 from qiskit.circuit.reset import Reset
 from qiskit.extensions.standard.barrier import Barrier
@@ -361,6 +363,26 @@ def _process_node(self, node):
                               "file=%s" % node.file)
         return None
 
+    def _gate_definition_to_qiskit_definition(self, node, params):
+        """From a gate definition in qasm, to a gate.definition format."""
+        definition = []
+        qreg = QuantumRegister(node['n_bits'])
+        bit_args = {node['bits'][i]: q for i, q in enumerate(qreg)}
+        exp_args = {node['args'][i]: Real(q) for i, q in enumerate(params)}
+
+        for child_op in node['body'].children:
+            qparams = []
+            eparams = []
+            for param_list in child_op.children[1:]:
+                if param_list.type == 'id_list':
+                    qparams = [bit_args[param.name] for param in param_list.children]
+                elif param_list.type == 'expression_list':
+                    for param in param_list.children:
+                        eparams.append(param.sym(nested_scope=[exp_args]))
+            op = self._create_op(child_op.name, params=eparams)
+            definition.append((op, qparams, []))
+        return definition
+
     def _create_dag_op(self, name, params, qargs):
         """
         Create a DAG node out of a parsed AST op node.
@@ -373,7 +395,10 @@ def _create_dag_op(self, name, params, qargs):
         Raises:
             QiskitError: if encountering a non-basis opaque gate
         """
+        op = self._create_op(name, params)
+        self.dag.apply_operation_back(op, qargs, [], condition=self.condition)
 
+    def _create_op(self, name, params):
         if name in self.standard_extension:
             op = self.standard_extension[name](*params)
         elif name in self.gates:
@@ -382,8 +407,12 @@ def _create_dag_op(self, name, params, qargs):
                 op = Gate(name=name, num_qubits=self.gates[name]['n_bits'], params=params)
             else:
                 # call a custom gate
-                raise QiskitError('Custom non-opaque gates are not supported by ast_to_dag module')
+                op = Instruction(name=name,
+                                 num_qubits=self.gates[name]['n_bits'],
+                                 num_clbits=0,
+                                 params=params)
+                op.definition = self._gate_definition_to_qiskit_definition(self.gates[name],
+                                                                           params=params)
         else:
             raise QiskitError("unknown operation for ast node name %s" % name)
-
-        self.dag.apply_operation_back(op, qargs, [], condition=self.condition)
+        return op


Test patch:
diff --git a/test/python/circuit/test_circuit_load_from_qasm.py b/test/python/circuit/test_circuit_load_from_qasm.py
--- a/test/python/circuit/test_circuit_load_from_qasm.py
+++ b/test/python/circuit/test_circuit_load_from_qasm.py
@@ -16,9 +16,11 @@
 """Test cases for the circuit qasm_file and qasm_string method."""
 
 from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
-from qiskit.circuit import Gate
+from qiskit.circuit import Gate, Parameter
 from qiskit.exceptions import QiskitError
 from qiskit.test import QiskitTestCase, Path
+from qiskit.transpiler.passes import Unroller
+from qiskit.converters.circuit_to_dag import circuit_to_dag
 
 
 class LoadFromQasmTest(QiskitTestCase):
@@ -238,3 +240,203 @@ def test_qasm_qas_string_order(self):
         q_circuit = QuantumCircuit.from_qasm_str(qasm_string)
 
         self.assertEqual(q_circuit.qasm(), expected_qasm)
+
+    def test_from_qasm_str_custom_gate1(self):
+        """ Test load custom gates (simple case)"""
+        qasm_string = """OPENQASM 2.0;
+                        include "qelib1.inc";
+                        gate rinv q {sdg q; h q; sdg q; h q; }
+                        qreg qr[1];
+                        rinv qr[0];"""
+        circuit = QuantumCircuit.from_qasm_str(qasm_string)
+
+        rinv_q = QuantumRegister(1, name='q')
+        rinv_gate = QuantumCircuit(rinv_q, name='rinv')
+        rinv_gate.sdg(rinv_q)
+        rinv_gate.h(rinv_q)
+        rinv_gate.sdg(rinv_q)
+        rinv_gate.h(rinv_q)
+        rinv = rinv_gate.to_instruction()
+        qr = QuantumRegister(1, name='qr')
+        expected = QuantumCircuit(qr, name='circuit')
+        expected.append(rinv, [qr[0]])
+
+        self.assertEqualUnroll(['sdg', 'h'], circuit, expected)
+
+    def test_from_qasm_str_custom_gate2(self):
+        """ Test load custom gates (no so simple case, different bit order)
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate swap2 a,b {
+                           cx a,b;
+                           cx b,a;  // different bit order
+                           cx a,b;
+                         }
+                         qreg qr[3];
+                         swap2 qr[0], qr[1];
+                         swap2 qr[1], qr[2];"""
+        circuit = QuantumCircuit.from_qasm_str(qasm_string)
+
+        ab_args = QuantumRegister(2, name='ab')
+        swap_gate = QuantumCircuit(ab_args, name='swap2')
+        swap_gate.cx(ab_args[0], ab_args[1])
+        swap_gate.cx(ab_args[1], ab_args[0])
+        swap_gate.cx(ab_args[0], ab_args[1])
+        swap = swap_gate.to_instruction()
+
+        qr = QuantumRegister(3, name='qr')
+        expected = QuantumCircuit(qr, name='circuit')
+        expected.append(swap, [qr[0], qr[1]])
+        expected.append(swap, [qr[1], qr[2]])
+
+        self.assertEqualUnroll(['cx'], expected, circuit)
+
+    def test_from_qasm_str_custom_gate3(self):
+        """ Test load custom gates (no so simple case, different bit count)
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate cswap2 a,b,c
+                         {
+                           cx c,b;  // different bit count
+                           ccx a,b,c; //previously defined gate
+                           cx c,b;
+                         }
+                         qreg qr[3];
+                         cswap2 qr[1], qr[0], qr[2];"""
+        circuit = QuantumCircuit.from_qasm_str(qasm_string)
+
+        abc_args = QuantumRegister(3, name='abc')
+        cswap_gate = QuantumCircuit(abc_args, name='cswap2')
+        cswap_gate.cx(abc_args[2], abc_args[1])
+        cswap_gate.ccx(abc_args[0], abc_args[1], abc_args[2])
+        cswap_gate.cx(abc_args[2], abc_args[1])
+        cswap = cswap_gate.to_instruction()
+
+        qr = QuantumRegister(3, name='qr')
+        expected = QuantumCircuit(qr, name='circuit')
+        expected.append(cswap, [qr[1], qr[0], qr[2]])
+
+        self.assertEqualUnroll(['cx', 'h', 'tdg', 't'], circuit, expected)
+
+    def test_from_qasm_str_custom_gate4(self):
+        """ Test load custom gates (parametrized)
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate my_gate(phi,lambda) q {u3(1.5707963267948966,phi,lambda) q;}
+                         qreg qr[1];
+                         my_gate(pi, pi) qr[0];"""
+        circuit = QuantumCircuit.from_qasm_str(qasm_string)
+
+        my_gate_circuit = QuantumCircuit(1, name='my_gate')
+        phi = Parameter('phi')
+        lam = Parameter('lambda')
+        my_gate_circuit.u3(1.5707963267948966, phi, lam, 0)
+        my_gate = my_gate_circuit.to_gate()
+
+        qr = QuantumRegister(1, name='qr')
+        expected = QuantumCircuit(qr, name='circuit')
+        expected.append(my_gate, [qr[0]])
+        expected = expected.bind_parameters({phi: 3.141592653589793, lam: 3.141592653589793})
+
+        self.assertEqualUnroll('u3', circuit, expected)
+
+    def test_from_qasm_str_custom_gate5(self):
+        """ Test load custom gates (parametrized, with biop and constant)
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-551307250
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate my_gate(phi,lambda) q {u3(pi/2,phi,lambda) q;} // biop with pi
+                         qreg qr[1];
+                         my_gate(pi, pi) qr[0];"""
+        circuit = QuantumCircuit.from_qasm_str(qasm_string)
+
+        my_gate_circuit = QuantumCircuit(1, name='my_gate')
+        phi = Parameter('phi')
+        lam = Parameter('lambda')
+        my_gate_circuit.u3(1.5707963267948966, phi, lam, 0)
+        my_gate = my_gate_circuit.to_gate()
+
+        qr = QuantumRegister(1, name='qr')
+        expected = QuantumCircuit(qr, name='circuit')
+        expected.append(my_gate, [qr[0]])
+        expected = expected.bind_parameters({phi: 3.141592653589793, lam: 3.141592653589793})
+
+        self.assertEqualUnroll('u3', circuit, expected)
+
+    def test_from_qasm_str_custom_gate6(self):
+        """ Test load custom gates (parameters used in expressions)
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-591668924
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate my_gate(phi,lambda) q
+                           {u2(phi+pi,lambda/2) q;}  // parameters used in expressions
+                         qreg qr[1];
+                         my_gate(pi, pi) qr[0];"""
+        circuit = QuantumCircuit.from_qasm_str(qasm_string)
+
+        my_gate_circuit = QuantumCircuit(1, name='my_gate')
+        phi = Parameter('phi')
+        lam = Parameter('lambda')
+        my_gate_circuit.u2(phi + 3.141592653589793, lam / 2, 0)
+        my_gate = my_gate_circuit.to_gate()
+
+        qr = QuantumRegister(1, name='qr')
+        expected = QuantumCircuit(qr, name='circuit')
+        expected.append(my_gate, [qr[0]])
+        expected = expected.bind_parameters({phi: 3.141592653589793, lam: 3.141592653589793})
+
+        self.assertEqualUnroll('u2', circuit, expected)
+
+    def test_from_qasm_str_custom_gate7(self):
+        """ Test load custom gates (build in functions)
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-592208951
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate my_gate(phi,lambda) q
+                           {u3(asin(cos(phi)/2), phi+pi, lambda/2) q;}  // build func
+                         qreg qr[1];
+                         my_gate(pi, pi) qr[0];"""
+        circuit = QuantumCircuit.from_qasm_str(qasm_string)
+
+        qr = QuantumRegister(1, name='qr')
+        expected = QuantumCircuit(qr, name='circuit')
+        expected.u3(-0.5235987755982988, 6.283185307179586, 1.5707963267948966, qr[0])
+        self.assertEqualUnroll('u3', circuit, expected)
+
+    def test_from_qasm_str_nested_custom_gate(self):
+        """ Test chain of custom gates
+        See: https://github.com/Qiskit/qiskit-terra/pull/3393#issuecomment-592261942
+        """
+        qasm_string = """OPENQASM 2.0;
+                         include "qelib1.inc";
+                         gate my_other_gate(phi,lambda) q
+                           {u3(asin(cos(phi)/2), phi+pi, lambda/2) q;}
+                         gate my_gate(phi) r
+                           {my_other_gate(phi, phi+pi) r;}
+                         qreg qr[1];
+                         my_gate(pi) qr[0];"""
+        circuit = QuantumCircuit.from_qasm_str(qasm_string)
+
+        qr = QuantumRegister(1, name='qr')
+        expected = QuantumCircuit(qr, name='circuit')
+        expected.u3(-0.5235987755982988, 6.283185307179586, 3.141592653589793, qr[0])
+        self.assertEqualUnroll('u3', circuit, expected)
+
+    def assertEqualUnroll(self, basis, circuit, expected):
+        """ Compares the dags after unrolling to basis """
+        circuit_dag = circuit_to_dag(circuit)
+        expected_dag = circuit_to_dag(expected)
+
+        circuit_result = Unroller(basis).run(circuit_dag)
+        expected_result = Unroller(basis).run(expected_dag)
+
+        self.assertEqual(circuit_result, expected_result)