Repository: Qiskit/qiskit

Issue:
qpy not recognizing complex number as a parameter for Initialize 
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: 0.18.0
- **Python version**:
- **Operating system**:

### What is the current behavior?
When trying to serialize a QuantumCircuit using qpy, I got

```
~/Documents/Q/Github/qiskit-terra/qiskit/circuit/qpy_serialization.py in _write_instruction(file_obj, instruction_tuple, custom_instructions, index_map)
    722             size = len(data)
    723         else:
--> 724             raise TypeError(
    725                 f"Invalid parameter type {instruction_tuple[0]} for gate {type(param)},"
    726             )

TypeError: Invalid parameter type <qiskit.extensions.quantum_initializer.initializer.Initialize object at 0x103fd6dc0> for gate <class 'complex'>,
```


### Steps to reproduce the problem
The circuit I used:

```
import io
import numpy as np
from qiskit.circuit import QuantumCircuit, qpy_serialization
from qiskit.circuit.library import QFT
from qiskit.quantum_info import Statevector

k = 5
state = (1/np.sqrt(8))*np.array([np.exp(-1j*2*np.pi*k*(0)/8),np.exp(-1j*2*np.pi*k*(1)/8),
                                 np.exp(-1j*2*np.pi*k*(2)/8),
 np.exp(-1j*2*np.pi*k*3/8),np.exp(-1j*2*np.pi*k*4/8),np.exp(-1j*2*np.pi*k*5/8),
 np.exp(-1j*2*np.pi*k*6/8), np.exp(-1j*2*np.pi*k*7/8)])

qubits = 3 

circuit = QuantumCircuit(qubits, qubits)
circuit.initialize(state)
circuit.append(QFT(qubits),range(qubits))
circuit.measure(range(qubits),range(qubits))
buffer = io.BytesIO()
qpy_serialization.dump(circuit, buffer)
```

### What is the expected behavior?



### Suggested solutions





Discussion:
There are actually 2 bugs here. The first is that initialize takes it's statevector as a single parameter on init but stores it as a list internally. This is why we get the conversion error on `complex` because every other instruction that takes complex does it as a numpy array as a single parameter. The other half though is with `qft` (and I think all blueprint circuits from the circuit library) passing the stored parameters to the constructor doesn't build an equivalent circuit object that can be appended to the reconstructed circuit in the deserializer. The only way I could come up with for handling this.  I'll push a fix for this in a few minutes (it might have to wait for 0.18.1 though).

One thing you can do while waiting for the fix to merge is I think if you decompose() both initialize and qft prior to saving the qpy circuit it should work fine. Granted it's not exactly the same but it will at least work around the hard failure (I think).

Resolving patch:
diff --git a/qiskit/circuit/qpy_serialization.py b/qiskit/circuit/qpy_serialization.py
--- a/qiskit/circuit/qpy_serialization.py
+++ b/qiskit/circuit/qpy_serialization.py
@@ -528,6 +528,8 @@ def _read_instruction(file_obj, circuit, registers, custom_instructions):
             param = struct.unpack("<q", data)[0]
         elif type_str == "f":
             param = struct.unpack("<d", data)[0]
+        elif type_str == "c":
+            param = complex(*struct.unpack(COMPLEX_PACK, data))
         elif type_str == "n":
             container = io.BytesIO(data)
             param = np.load(container)
@@ -551,6 +553,13 @@ def _read_instruction(file_obj, circuit, registers, custom_instructions):
             inst_obj.label = label
         circuit._append(inst_obj, qargs, cargs)
         return
+    elif gate_name in custom_instructions:
+        inst_obj = _parse_custom_instruction(custom_instructions, gate_name, params)
+        inst_obj.condition = condition_tuple
+        if label_size > 0:
+            inst_obj.label = label
+        circuit._append(inst_obj, qargs, cargs)
+        return
     elif hasattr(library, gate_name):
         gate_class = getattr(library, gate_name)
     elif hasattr(circuit_mod, gate_name):
@@ -559,22 +568,21 @@ def _read_instruction(file_obj, circuit, registers, custom_instructions):
         gate_class = getattr(extensions, gate_name)
     elif hasattr(quantum_initializer, gate_name):
         gate_class = getattr(quantum_initializer, gate_name)
-    elif gate_name in custom_instructions:
-        inst_obj = _parse_custom_instruction(custom_instructions, gate_name, params)
-        inst_obj.condition = condition_tuple
-        if label_size > 0:
-            inst_obj.label = label
-        circuit._append(inst_obj, qargs, cargs)
-        return
     else:
         raise AttributeError("Invalid instruction type: %s" % gate_name)
-    if gate_name == "Barrier":
-        params = [len(qargs)]
-    gate = gate_class(*params)
+    if gate_name == "Initialize":
+        gate = gate_class(params)
+    else:
+        if gate_name == "Barrier":
+            params = [len(qargs)]
+        gate = gate_class(*params)
     gate.condition = condition_tuple
     if label_size > 0:
         gate.label = label
-    circuit._append(gate, qargs, cargs)
+    if not isinstance(gate, Instruction):
+        circuit.append(gate, qargs, cargs)
+    else:
+        circuit._append(gate, qargs, cargs)
 
 
 def _parse_custom_instruction(custom_instructions, gate_name, params):
@@ -677,6 +685,7 @@ def _write_instruction(file_obj, instruction_tuple, custom_instructions, index_m
         )
         or gate_class_name == "Gate"
         or gate_class_name == "Instruction"
+        or isinstance(instruction_tuple[0], library.BlueprintCircuit)
     ):
         if instruction_tuple[0].name not in custom_instructions:
             custom_instructions[instruction_tuple[0].name] = instruction_tuple[0]
@@ -745,7 +754,11 @@ def _write_instruction(file_obj, instruction_tuple, custom_instructions, index_m
             container.seek(0)
             data = container.read()
             size = len(data)
-        elif isinstance(param, (np.integer, np.floating, np.ndarray)):
+        elif isinstance(param, complex):
+            type_key = "c"
+            data = struct.pack(COMPLEX_PACK, param.real, param.imag)
+            size = struct.calcsize(COMPLEX_PACK)
+        elif isinstance(param, (np.integer, np.floating, np.ndarray, np.complexfloating)):
             type_key = "n"
             np.save(container, param)
             container.seek(0)


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
@@ -23,7 +23,7 @@
 from qiskit.circuit.quantumregister import Qubit
 from qiskit.circuit.random import random_circuit
 from qiskit.circuit.gate import Gate
-from qiskit.circuit.library import XGate
+from qiskit.circuit.library import XGate, QFT
 from qiskit.circuit.instruction import Instruction
 from qiskit.circuit.parameter import Parameter
 from qiskit.extensions import UnitaryGate
@@ -474,3 +474,31 @@ def test_circuit_with_conditional_with_label(self):
         new_circ = load(qpy_file)[0]
         self.assertEqual(qc, new_circ)
         self.assertEqual([x[0].label for x in qc.data], [x[0].label for x in new_circ.data])
+
+    def test_initialize_qft(self):
+        """Test that initialize with a complex statevector and qft work."""
+        k = 5
+        state = (1 / np.sqrt(8)) * np.array(
+            [
+                np.exp(-1j * 2 * np.pi * k * (0) / 8),
+                np.exp(-1j * 2 * np.pi * k * (1) / 8),
+                np.exp(-1j * 2 * np.pi * k * (2) / 8),
+                np.exp(-1j * 2 * np.pi * k * 3 / 8),
+                np.exp(-1j * 2 * np.pi * k * 4 / 8),
+                np.exp(-1j * 2 * np.pi * k * 5 / 8),
+                np.exp(-1j * 2 * np.pi * k * 6 / 8),
+                np.exp(-1j * 2 * np.pi * k * 7 / 8),
+            ]
+        )
+
+        qubits = 3
+        qc = QuantumCircuit(qubits, qubits)
+        qc.initialize(state)
+        qc.append(QFT(qubits), range(qubits))
+        qc.measure(range(qubits), range(qubits))
+        qpy_file = io.BytesIO()
+        dump(qc, qpy_file)
+        qpy_file.seek(0)
+        new_circ = load(qpy_file)[0]
+        self.assertEqual(qc, new_circ)
+        self.assertEqual([x[0].label for x in qc.data], [x[0].label for x in new_circ.data])