Repository: Qiskit/qiskit-terra (python)

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.

Pull request:
Fix `UnitaryGate.qasm()` with unused qubits
The OpenQASM 2 exporter has a special case for providing `gate`
definitions, which was implemented purely for `UnitaryGate`.  This is
stateful per instruction, and does not generally behave well with the
rest of the exporter.  This commit fixes one misbehaviour: the way the
qubit operands for the definition was calculated was unreliable in both
order and number of qubits, if any of the qubits were unaffected by
the gate operation.

<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->


### Details and comments

There are general problems with the special-case approach to defining the definition like this (see also #8222), which this commit does not fix, and the whole OpenQASM 2 exporter is known to be pretty questionable with custom and parameterised gates still.

Fix #8224.

Patch:
diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py
index 6a36e630d..5d9364805 100644
--- a/qiskit/extensions/unitary.py
+++ b/qiskit/extensions/unitary.py
@@ -191,22 +191,13 @@ class UnitaryGate(Gate):
             _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 @@ class UnitaryGate(Gate):
             "gate "
             + self._qasm_name
             + " "
-            + ",".join(bit_to_qasm.values())
+            + ",".join(qubit_to_qasm[qubit] for qubit in self.definition.qubits)
             + " {\n"
             + gates_def
             + "}"
diff --git a/releasenotes/notes/fix-qasm2-identity-as-unitary-aa2feeb05707a597.yaml b/releasenotes/notes/fix-qasm2-identity-as-unitary-aa2feeb05707a597.yaml
new file mode 100644
index 000000000..7cbd2a5cd
--- /dev/null
+++ b/releasenotes/notes/fix-qasm2-identity-as-unitary-aa2feeb05707a597.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+  - |
+    The OpenQASM 2 exporter (:meth:`.QuantumCircuit.qasm`) will now correctly
+    define the qubit parameters for :class:`.UnitaryGate` operations that do
+    not affect all the qubits they are defined over.
+    Fixed `#8224 <https://github.com/Qiskit/qiskit-terra/issues/8224>`__.


Test patch:
diff --git a/test/python/circuit/test_unitary.py b/test/python/circuit/test_unitary.py
index d12418856..935b63897 100644
--- a/test/python/circuit/test_unitary.py
+++ b/test/python/circuit/test_unitary.py
@@ -281,6 +281,20 @@ class TestUnitaryCircuit(QiskitTestCase):
         )
         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)