Repository: Qiskit/qiskit

Issue:
Serialization fails for controlled unitary gate
**Describe the bug**
I get the error: `AttributeError: 'NoneType' object has no attribute 'clbits` and it seems that the `Qpy to QASM conversion failed: unable to parse quantum circuit` for a circuit containing a controlled unitary gate.

**Steps to reproduce**
```
from qiskit.extensions import UnitaryGate
from qiskit.quantum_info import random_unitary, SparsePauliOp
from qiskit import QuantumCircuit, QuantumRegister
from qiskit_ibm_runtime import QiskitRuntimeService, Options, Estimator, Session

UU = random_unitary(4, seed=12345)
qr = QuantumRegister(3)
qc = QuantumCircuit(qr)
qc.h(0)
qc.append(UnitaryGate(UU).control(1), qr)

service = QiskitRuntimeService()
backend = service.get_backend('ibmq_qasm_simulator')

options = Options(optimization_level=2, resilience_level=0)
estimator = Estimator(backend=backend, options=options)

XXexp = estimator.run(qc, observables=SparsePauliOp('IIX')).result().values[0]
```


**Expected behavior**
It should work

**Suggested solutions**


**Additional Information**

Transpiling before works
`transpile(qc, basis_gates = backend.configuration().basis_gates)`

- **qiskit-ibm-runtime version**: 0.12.0
- **Python version**: 3.8.16
- **Operating system**: MacOs



Discussion:

Resolving patch:
diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py
--- a/qiskit/qpy/binary_io/circuits.py
+++ b/qiskit/qpy/binary_io/circuits.py
@@ -157,12 +157,13 @@ def _loads_instruction_parameter(
     elif type_key == type_keys.Value.REGISTER:
         param = _loads_register_param(data_bytes.decode(common.ENCODE), circuit, registers)
     else:
+        clbits = circuit.clbits if circuit is not None else ()
         param = value.loads_value(
             type_key,
             data_bytes,
             version,
             vectors,
-            clbits=circuit.clbits,
+            clbits=clbits,
             cregs=registers["c"],
             use_symengine=use_symengine,
         )
@@ -577,12 +578,21 @@ def _write_instruction(file_obj, instruction, custom_operations, index_map, use_
         or gate_class_name == "Instruction"
         or isinstance(instruction.operation, library.BlueprintCircuit)
     ):
-        if instruction.operation.name not in custom_operations:
-            custom_operations[instruction.operation.name] = instruction.operation
-            custom_operations_list.append(instruction.operation.name)
         gate_class_name = instruction.operation.name
+        # ucr*_dg gates can have different numbers of parameters,
+        # the uuid is appended to avoid storing a single definition
+        # in circuits with multiple ucr*_dg gates.
+        if instruction.operation.name in ["ucrx_dg", "ucry_dg", "ucrz_dg"]:
+            gate_class_name += "_" + str(uuid.uuid4())
+
+        if gate_class_name not in custom_operations:
+            custom_operations[gate_class_name] = instruction.operation
+            custom_operations_list.append(gate_class_name)
 
     elif gate_class_name == "ControlledGate":
+        # controlled gates can have the same name but different parameter
+        # values, the uuid is appended to avoid storing a single definition
+        # in circuits with multiple controlled gates.
         gate_class_name = instruction.operation.name + "_" + str(uuid.uuid4())
         custom_operations[gate_class_name] = instruction.operation
         custom_operations_list.append(gate_class_name)


Test patch:
diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py
--- a/test/python/circuit/test_circuit_load_from_qpy.py
+++ b/test/python/circuit/test_circuit_load_from_qpy.py
@@ -175,6 +175,22 @@ def test_unitary_gate(self):
         self.assertEqual(qc, new_circ)
         self.assertDeprecatedBitProperties(qc, new_circ)
 
+    def test_controlled_unitary_gate(self):
+        """Test that numpy array parameters are correctly serialized
+        in controlled unitary gate."""
+        qc = QuantumCircuit(2)
+        unitary = np.array([[0, 1], [1, 0]])
+        gate = UnitaryGate(unitary)
+        qc.append(gate.control(1), [0, 1])
+
+        with io.BytesIO() as qpy_file:
+            dump(qc, qpy_file)
+            qpy_file.seek(0)
+            new_circ = load(qpy_file)[0]
+
+        self.assertEqual(qc.decompose(reps=5), new_circ.decompose(reps=5))
+        self.assertDeprecatedBitProperties(qc, new_circ)
+
     def test_opaque_gate(self):
         """Test that custom opaque gate is correctly serialized"""
         custom_gate = Gate("black_box", 1, [])