Repository: Qiskit/qiskit

Issue:
Support PauliGate in qpy
<!-- ⚠️ 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**:
- **Python version**:
- **Operating system**:

### What is the current behavior?
Serializing a circuit with a PauliGate fails with 

```
 File "/Users/jessieyu/Documents/Q/Github/qiskit-terra/qiskit/circuit/qpy_serialization.py", line 824, in dump
    _write_circuit(file_obj, circuit)
  File "/Users/jessieyu/Documents/Q/Github/qiskit-terra/qiskit/circuit/qpy_serialization.py", line 867, in _write_circuit
    _write_instruction(instruction_buffer, instruction, custom_instructions, index_map)
  File "/Users/jessieyu/Documents/Q/Github/qiskit-terra/qiskit/circuit/qpy_serialization.py", line 724, in _write_instruction
    raise TypeError(
TypeError: Invalid parameter type <qiskit.circuit.library.generalized_gates.pauli.PauliGate object at 0x13bde8730> for gate <class 'str'>,
```


### Steps to reproduce the problem
```
circ = (X ^ Y ^ Z).to_circuit_op().to_circuit()
qpy_serialization.dump(buff, circ)
```


### What is the expected behavior?
The circuit can be serialized. 

Also the error message seems backwards - should have said `Invalid parameter type 'str' for gate 'PauliGate'` instead. 
### Suggested solutions
Allow string as a valid parameter for gates.




Discussion:

Resolving patch:
diff --git a/qiskit/circuit/library/__init__.py b/qiskit/circuit/library/__init__.py
--- a/qiskit/circuit/library/__init__.py
+++ b/qiskit/circuit/library/__init__.py
@@ -96,6 +96,7 @@
    GRY
    GRZ
    RVGate
+   PauliGate
 
 Boolean Logic Circuits
 ======================
@@ -349,6 +350,7 @@
     GRY,
     GRZ,
     RVGate,
+    PauliGate,
 )
 from .boolean_logic import (
     AND,
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
@@ -202,14 +202,15 @@ class if it's defined in Qiskit. Otherwise it falls back to the custom
     }
 
 After each INSTRUCTION_PARAM the next ``size`` bytes are the parameter's data.
-The ``type`` field can be ``'i'``, ``'f'``, ``'p'``, 'e', or ``'n'`` which dictate
-the format. For ``'i'`` it's an integer, ``'f'`` it's a double, ``'p'`` defines
-a :class:`~qiskit.circuit.Paramter` object  which is represented by a PARAM
+The ``type`` field can be ``'i'``, ``'f'``, ``'p'``, ``'e'``, ``'s'``,
+or ``'n'`` which dictate the format. For ``'i'`` it's an integer, ``'f'`` it's
+a double, ``'s'`` if it's a string (encoded as utf8), ``'p'`` defines a
+:class:`~qiskit.circuit.Parameter` object  which is represented by a PARAM
 struct (see below), ``e`` defines a :class:`~qiskit.circuit.ParameterExpression`
 object (that's not a :class:`~qiskit.circuit.Paramter`) which is represented by
-a PARAM_EXPR struct (see below), and ``'n'`` represents an object from numpy (
-either an ``ndarray`` or a numpy type) which means the data is .npy format [#f2]_
-data.
+a PARAM_EXPR struct (see below), and ``'n'`` represents an object from numpy
+(either an ``ndarray`` or a numpy type) which means the data is .npy
+format [#f2]_ data.
 
 
 PARAMETER
@@ -519,6 +520,8 @@ def _read_instruction(file_obj, circuit, registers, custom_instructions):
         elif type_str == "n":
             container = io.BytesIO(data)
             param = np.load(container)
+        elif type_str == "s":
+            param = data.decode("utf8")
         elif type_str == "p":
             container = io.BytesIO(data)
             param = _read_parameter(container)
@@ -702,6 +705,10 @@ def _write_instruction(file_obj, instruction_tuple, custom_instructions, index_m
             type_key = "f"
             data = struct.pack("<d", param)
             size = struct.calcsize("<d")
+        elif isinstance(param, str):
+            type_key = "s"
+            data = param.encode("utf8")
+            size = len(data)
         elif isinstance(param, Parameter):
             type_key = "p"
             _write_parameter(container, param)


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
@@ -22,6 +22,7 @@
 from qiskit.circuit.gate import Gate
 from qiskit.circuit.instruction import Instruction
 from qiskit.circuit.parameter import Parameter
+from qiskit.opflow import X, Y, Z
 from qiskit.test import QiskitTestCase
 from qiskit.circuit.qpy_serialization import dump, load
 
@@ -249,6 +250,15 @@ def test_parameter_expression(self):
         new_circuit = load(qpy_file)[0]
         self.assertEqual(qc, new_circuit)
 
+    def test_string_parameter(self):
+        """Test a PauliGate instruction that has string parameters."""
+        circ = (X ^ Y ^ Z).to_circuit_op().to_circuit()
+        qpy_file = io.BytesIO()
+        dump(circ, qpy_file)
+        qpy_file.seek(0)
+        new_circuit = load(qpy_file)[0]
+        self.assertEqual(circ, new_circuit)
+
     def test_multiple_circuits(self):
         """Test multiple circuits can be serialized together."""
         circuits = []