Repository: Qiskit/qiskit

Issue:
Qasm exporter fails with `CXGate` and `CU1Gate` generates an empty unitary: `QasmError: "Expected an ID, received '{'"`
### Environment

- **Qiskit Terra version**: 0.19.1
- **Python version**: 3.8
- **Operating system**: Ubuntu 18.04.6 LTS


### What is happening?

The qasm exporter creates a unitary gate with empty definition. 

### How can we reproduce the issue?

Run the code: 
```python
import qiskit
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit.circuit.library.standard_gates import *
from qiskit import transpile

qr = QuantumRegister(3, name='qr') # with 2 bits it doesn't crash
cr = ClassicalRegister(3, name='cr')
qc = QuantumCircuit(qr, cr, name='qc')
subcircuit = QuantumCircuit(qr, name='subcircuit')

subcircuit.append(CXGate(), qargs=[qr[0], qr[1]], cargs=[])
subcircuit.append(CU1Gate(3), qargs=[qr[0], qr[1]], cargs=[])
qc.append(subcircuit, qargs=qr)
qc.append(subcircuit.inverse(), qargs=qr)
qc.measure(qr, cr)
qc = transpile(qc, optimization_level=3)

my_qasm = qc.qasm()
print(my_qasm)
```
Output:
```
OPENQASM 2.0;
include "qelib1.inc";
gate unitary139827973619136  {
}
qreg qr[3];
creg cr[3];
unitary139827973619136 qr[0],qr[1];
measure qr[0] -> cr[0];
measure qr[1] -> cr[1];
measure qr[2] -> cr[2];
```

Run this code to read back the exported qasm: 
```python
qc = QuantumCircuit.from_qasm_str(my_qasm)
```
But it gives an error because the qasm is incorrect, since it has gate unitary139827973619136 which is empty. Output:
```
Error near line 3 Column 3
....
QasmError: "Expected an ID, received '{'"
```


### What should happen?

The generated qasm should include the definition of the gate. This works if the optimization level is lower or if we set the registers to use 2 bits. This is odd, since the correctness of the exported qasm should not depend on the size of the circuit or on the optimization level.

### Any suggestions?

It must be connected to the optimizations running on optimization level 3 and it must depend also on the size of the registers. It could be curious to see, maybe in the test suite there was a test case testing a similar case but with a smaller size of register and it passed there, whereas with 3 or more bits as size of the registers it fails. This is just speculation.
Looking forward to your feedback, thanks in advance.


Discussion:
The empty definition is clearly correct here - transpilation at level 3 does full unitary re-synthesis, and discovers that the single-qubit unitary is a simple identity, and so it doesn't need to do anything.  Lower optimisation levels don't do the same level of resynthesis (because it's expensive), and since there's no `basis_gates` supplied, they don't need to do _any_ synthesis.  The bug component of it is that the output `gate` statement doesn't define any qubit operands, even though it takes two. 

Similar #8222, this will be because of `UnitaryGate`'s very questionable special case for how it constructs a definition for itself in OpenQASM 2.  On a quick look, I can see that it builds up the argument list by looking at the subinstructions in its definition, rather than using the qubits directly from the definition circuit.  Since this no-op gate has _no_ instructions, it doesn't add any qubits.  I can't see a reason why things are that way - the gate is _always_ going to be called with number of qubits in the definition circuit in that order, so I don't know why there's any logic that allows the definition to have a different number and order of the qubits.

Resolving patch:
diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py
--- a/qiskit/extensions/unitary.py
+++ b/qiskit/extensions/unitary.py
@@ -191,22 +191,13 @@ def qasm(self):
             _qasm_escape_gate_name(self.label) if self.label else "unitary" + str(id(self))
         )
 
-        # map from gates in the definition to params in the method
-        bit_to_qasm = OrderedDict()
-        current_bit = 0
-
+        qubit_to_qasm = {bit: f"p{i}" for i, bit in enumerate(self.definition.qubits)}
         gates_def = ""
         for instruction in self.definition.data:
 
-            # add bits from this gate to the overall set of params
-            for bit in instruction.qubits + instruction.clbits:
-                if bit not in bit_to_qasm:
-                    bit_to_qasm[bit] = "p" + str(current_bit)
-                    current_bit += 1
-
             curr_gate = "\t{} {};\n".format(
                 instruction.operation.qasm(),
-                ",".join([bit_to_qasm[j] for j in instruction.qubits + instruction.clbits]),
+                ",".join(qubit_to_qasm[qubit] for qubit in instruction.qubits),
             )
             gates_def += curr_gate
 
@@ -215,7 +206,7 @@ def qasm(self):
             "gate "
             + self._qasm_name
             + " "
-            + ",".join(bit_to_qasm.values())
+            + ",".join(qubit_to_qasm[qubit] for qubit in self.definition.qubits)
             + " {\n"
             + gates_def
             + "}"


Test patch:
diff --git a/test/python/circuit/test_unitary.py b/test/python/circuit/test_unitary.py
--- a/test/python/circuit/test_unitary.py
+++ b/test/python/circuit/test_unitary.py
@@ -281,6 +281,20 @@ def test_qasm_2q_unitary(self):
         )
         self.assertEqual(expected_qasm, qc.qasm())
 
+    def test_qasm_unitary_noop(self):
+        """Test that an identifier unitary can be converted to OpenQASM 2"""
+        qc = QuantumCircuit(QuantumRegister(3, "q0"))
+        qc.unitary(numpy.eye(8), qc.qubits, label="unitary_identity")
+        expected_qasm = (
+            "OPENQASM 2.0;\n"
+            'include "qelib1.inc";\n'
+            "gate unitary_identity p0,p1,p2 {\n"
+            "}\n"
+            "qreg q0[3];\n"
+            "unitary_identity q0[0],q0[1],q0[2];\n"
+        )
+        self.assertEqual(expected_qasm, qc.qasm())
+
     def test_unitary_decomposition(self):
         """Test decomposition for unitary gates over 2 qubits."""
         qc = QuantumCircuit(3)