Repository: Qiskit/qiskit-terra (python)

Issue:
QPY fails to serialize `MCXGrayCode` gate
### Environment

- **Qiskit Terra version**: 0.22.4
- **Python version**: 3.9
- **Operating system**: macOS Monterey 12.6


### What is happening?

When you try to serialize a circuit with an `MCXGrayCode` gate using qpy, you get `TypeError: __init__() missing 1 required positional argument: 'num_ctrl_qubits'`. As far as I have seen, other mcx gates do not run into this issue.

I discovered this while trying to run iterative amplitude estimation using the runtime `Sampler`, which relies on `qpy` for serialization.

### How can we reproduce the issue?

```
  from qiskit import QuantumCircuit, qpy
  circuit = QuantumCircuit(11)
  circuit.mcx(list(range(0,9)), 10)
  print('gate name:', circuit.data[0][0].name)

  with open('test.qpy', 'wb') as fd:
      qpy.dump(circuit, fd)

  with open('test.qpy', 'rb') as fd:
      new_qc = qpy.load(fd)[0]
```
This snippet will fail with a similar traceback to:

```
gate name: mcx_gray
  File "/Users/ept/Downloads/ExamplePEO.py", line 131, in <module>
    new_qc = qpy.load(fd)[0]
  File "/Users/ept/Desktop/qgt/qiskit/qpy/interface.py", line 269, in load
    loader(
  File "/Users/ept/Desktop/qgt/qiskit/qpy/binary_io/circuits.py", line 864, in read_circuit
    _read_instruction(file_obj, circ, out_registers, custom_operations, version, vectors)
  File "/Users/ept/Desktop/qgt/qiskit/qpy/binary_io/circuits.py", line 258, in _read_instruction
    gate = gate_class(*params)
TypeError: __init__() missing 1 required positional argument: 'num_ctrl_qubits'
```

### What should happen?

It should not have failed.

### Any suggestions?

I would modify [the following line](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/qpy/binary_io/circuits.py#L255) of `qpy.binary_io.circuits.py` to add "MCXGrayCode". There seem to be other gates that have been already accounted for this same issue, and adding "MCXGrayCode" to the list fixes it:

```
        if gate_name in {"MCPhaseGate", "MCU1Gate", "MCXGrayCode"}:
            gate = gate_class(*params, instruction.num_ctrl_qubits)
```

I'm happy to open a bugfix PR, but because I'm not that familiar with qpy, I wanted to document the problem in this issue first.

Pull request:
Fix qpy for `MCX` gates
<!--
⚠️ 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.
-->

### Summary
This PR fixes #9390.


### Details and comments




Patch:
diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py
index 4053fb0a5..527d7ec54 100644
--- a/qiskit/qpy/binary_io/circuits.py
+++ b/qiskit/qpy/binary_io/circuits.py
@@ -252,7 +252,14 @@ def _read_instruction(file_obj, circuit, registers, custom_operations, version,
     if gate_name in {"IfElseOp", "WhileLoopOp"}:
         gate = gate_class(condition_tuple, *params)
     elif version >= 5 and issubclass(gate_class, ControlledGate):
-        if gate_name in {"MCPhaseGate", "MCU1Gate"}:
+        if gate_name in {
+            "MCPhaseGate",
+            "MCU1Gate",
+            "MCXGrayCode",
+            "MCXGate",
+            "MCXRecursive",
+            "MCXVChain",
+        }:
             gate = gate_class(*params, instruction.num_ctrl_qubits)
         else:
             gate = gate_class(*params)
diff --git a/releasenotes/notes/fix-qpy-mcxgray-421cf8f673f24238.yaml b/releasenotes/notes/fix-qpy-mcxgray-421cf8f673f24238.yaml
new file mode 100644
index 000000000..5f594a561
--- /dev/null
+++ b/releasenotes/notes/fix-qpy-mcxgray-421cf8f673f24238.yaml
@@ -0,0 +1,7 @@
+---
+fixes:
+  - |
+    Fixed a bug in QPY (:mod:`qiskit.qpy`) where circuits containing gates of class
+    :class:`.MCXGate`, :class:`.MCXGrayCode`, and :class:`MCXRecursive`, and
+    :class:`.MCXVChain` would fail to serialize.
+    See `#9390 <https://github.com/Qiskit/qiskit-terra/issues/9390>`__.


Test patch:
diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py
index db4e62c59..349bca64f 100644
--- a/test/python/circuit/test_circuit_load_from_qpy.py
+++ b/test/python/circuit/test_circuit_load_from_qpy.py
@@ -24,7 +24,18 @@ from qiskit.circuit.classicalregister import Clbit
 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, QFT, QAOAAnsatz, PauliEvolutionGate, DCXGate, MCU1Gate
+from qiskit.circuit.library import (
+    XGate,
+    QFT,
+    QAOAAnsatz,
+    PauliEvolutionGate,
+    DCXGate,
+    MCU1Gate,
+    MCXGate,
+    MCXGrayCode,
+    MCXRecursive,
+    MCXVChain,
+)
 from qiskit.circuit.instruction import Instruction
 from qiskit.circuit.parameter import Parameter
 from qiskit.circuit.parametervector import ParameterVector
@@ -1043,9 +1054,17 @@ class TestLoadFromQPY(QiskitTestCase):
 
     def test_standard_control_gates(self):
         """Test standard library controlled gates."""
-        qc = QuantumCircuit(3)
+        qc = QuantumCircuit(6)
         mcu1_gate = MCU1Gate(np.pi, 2)
+        mcx_gate = MCXGate(5)
+        mcx_gray_gate = MCXGrayCode(5)
+        mcx_recursive_gate = MCXRecursive(4)
+        mcx_vchain_gate = MCXVChain(3)
         qc.append(mcu1_gate, [0, 2, 1])
+        qc.append(mcx_gate, list(range(0, 6)))
+        qc.append(mcx_gray_gate, list(range(0, 6)))
+        qc.append(mcx_recursive_gate, list(range(0, 5)))
+        qc.append(mcx_vchain_gate, list(range(0, 5)))
         qc.mcp(np.pi, [0, 2], 1)
         qc.mct([0, 2], 1)
         qc.mcx([0, 2], 1)