Fix support for standalone registers in qpy format (#6523)

* Fix support for standalone registers in qpy format

This commit tweaks the qpy register format slightly to enable
differentiating between standalone registers (ones that own their bits)
and registers that contain pre-existing bits. Previously this didn't
work in all cases with standalone registers (it only worked assuming
the qubits in a register were a contiguous set of qubit indices in the
circuit). Normally this type of change would require a format version
bump for backwards compatibility (see #6419 for a PoC showing this)
however because we have yet to include qpy in a release we have an
opportunity to fix this prior to release in the same initial version.

* Fix sphinx syntax

* Fix docstring whitespace

* Fix rebase error

* Update qiskit/circuit/qpy_serialization.py

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>

* Expand docstring

* Fix bugs and expand tests

* Add mixed standalone shared register circuit test

In the process of adding this test a bug was found in the DAGCircuit
__eq__ method (which is used for QuantumCircuit too). The equality of
circuits was failing based on the insertion order of registers which
doesn't matter. As long as the bit indices are in order in the register
(ie qr[2] -> 3 is consistent between both circuits) the order qr was
inserted into the circuit doesn't matter. This was causing a failure in
the new test because qpy deserialization always loads standalone
registers first even though the bit order is properly preserved. To fix
this the dagcircuit __eq__ method is update to only care about the bit
indices (both register and circuit) and ignore register insertion order.

* Add comments on the read register flow

* Preserve register insertion order on circuit deserialization

This commit preserves the register insertion order when deserializing a
circuit form qpy. Previously this wasn't being preserved and standalone
registers were always being added first. This also reverts the change
made to dagcircuit.__eq__ made in an earlier commit to workaround the
earlier limitation.

* Handle out of order register bit insertion

* Fix endianness bug in register index array

* Preserve the label for instructions

This commit modifies the instruction payload format to also include the
label of an instruction in the circuit. Previously we were just ignoring
the label which meant that it was omitted from the qpy data and any
labels would not be present in the deserialized circuit.

* Fix duplicate test_name

* Fix lint

* Assert label equality in label tests

* Fix label init condition

* Remove unecessary condition

* Deduplicate register creation

Co-authored-by: Kevin Krsulich <kevin@krsulich.net>
This commit is contained in:
Matthew Treinish 2021-07-07 13:14:23 -04:00 committed by GitHub
parent fcedd7e17a
commit 456d036ade
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 349 additions and 54 deletions

View File

@ -84,7 +84,7 @@ class Instruction:
# NOTE: The conditional statement checking if the `_label` attribute is
# already set is a temporary work around that can be removed after
# the next stable qiskit-aer release
if label is not None or not hasattr(self, "_label"):
if not hasattr(self, "_label"):
self._label = label
# tuple (ClassicalRegister, int), tuple (Clbit, bool) or tuple (Clbit, int)
# when the instruction has a conditional ("if")

View File

@ -19,12 +19,6 @@ QPY serialization (:mod:`qiskit.circuit.qpy_serialization`)
.. currentmodule:: qiskit.circuit.qpy_serialization
.. warning::
QPY serialization is still an experimental feature, the API and/or
forward compatibility are not yet guaranteed. Future versions of Qiskit
may not be fully compatible.
.. autosummary::
load
@ -103,6 +97,7 @@ as:
struct {
char type;
_Bool standalone;
uint32_t size;
unit16_t name_size;
}
@ -115,6 +110,23 @@ uint32_t values of size ``size`` that contains a map of the register's index to
the circuit's qubit index. For example, array element 0's value is the index
of the ``register[0]``'s position in the containing circuit's qubits list.
The standalone boolean determines whether the register is constructed as a
standalone register that was added to the circuit or was created from existing
bits. A register is considered standalone if it has bits constructed solely
as part of it, for example::
qr = QuantumRegister(2)
qc = QuantumCircuit(qr)
the register ``qr`` would be a standalone register. While something like::
bits = [Qubit(), Qubit()]
qr = QuantumRegister(bits=bits)
qc = QuantumCircuit(bits=bits)
``qr`` would have ``standalone`` set to ``False``.
CUSTOM_DEFINITIONS
------------------
@ -158,6 +170,7 @@ The contents of INSTRUCTIONS is a list of INSTRUCTION metadata objects
struct {
uint16_t name_size;
uint16_t label_size;
uint16_t num_parameters;
uint32_t num_qargs;
uint32_t num_cargs;
@ -169,9 +182,11 @@ The contents of INSTRUCTIONS is a list of INSTRUCTION metadata objects
This metadata object is immediately followed by ``name_size`` bytes of utf8 bytes
for the ``name``. ``name`` here is the Qiskit class name for the Instruction
class if it's defined in Qiskit. Otherwise it falls back to the custom
instruction name. Following the ``name`` bytes if ``has_conditional`` is ``True``
then there are ``conditonal_reg_name_size`` bytes of utf8 data for the name of
the condtional register name.
instruction name. Following the ``name`` bytes there are ``label_size`` bytes of
utf8 data for the label if one was set on the instruction. Following the label
bytes if ``has_conditional`` is ``True`` then there are
``conditonal_reg_name_size`` bytes of utf8 data for the name of the condtional
register name.
This is immediately followed by the INSTRUCTION_ARG structs for the list of
arguments of that instruction. These are in the order of all quantum arguments
@ -353,8 +368,8 @@ CUSTOM_DEFINITION_SIZE = struct.calcsize(CUSTOM_DEFINITION_PACK)
# REGISTER binary format
REGISTER = namedtuple("REGISTER", ["type", "size", "name_size"])
REGISTER_PACK = "!1cIH"
REGISTER = namedtuple("REGISTER", ["type", "standalone", "size", "name_size"])
REGISTER_PACK = "!1c?IH"
REGISTER_SIZE = struct.calcsize(REGISTER_PACK)
# INSTRUCTION binary format
@ -362,6 +377,7 @@ INSTRUCTION = namedtuple(
"INSTRUCTION",
[
"name_size",
"label_size",
"num_parameters",
"num_qargs",
"num_cargs",
@ -370,7 +386,7 @@ INSTRUCTION = namedtuple(
"value",
],
)
INSTRUCTION_PACK = "!HHII?Hq"
INSTRUCTION_PACK = "!HHHII?Hq"
INSTRUCTION_SIZE = struct.calcsize(INSTRUCTION_PACK)
# Instruction argument format
INSTRUCTION_ARG = namedtuple("INSTRUCTION_ARG", ["type", "size"])
@ -412,22 +428,15 @@ def _read_registers(file_obj, num_registers):
for _reg in range(num_registers):
register_raw = file_obj.read(REGISTER_SIZE)
register = struct.unpack(REGISTER_PACK, register_raw)
name = file_obj.read(register[2]).decode("utf8")
REGISTER_ARRAY_PACK = "%sI" % register[1]
name = file_obj.read(register[3]).decode("utf8")
standalone = register[1]
REGISTER_ARRAY_PACK = "!%sI" % register[2]
bit_indices_raw = file_obj.read(struct.calcsize(REGISTER_ARRAY_PACK))
bit_indices = struct.unpack(REGISTER_ARRAY_PACK, bit_indices_raw)
bit_indices = list(struct.unpack(REGISTER_ARRAY_PACK, bit_indices_raw))
if register[0].decode("utf8") == "q":
registers["q"][name] = {}
registers["q"][name]["register"] = QuantumRegister(register[1], name)
registers["q"][name]["index_map"] = dict(
zip(bit_indices, registers["q"][name]["register"])
)
registers["q"][name] = (standalone, bit_indices)
else:
registers["c"][name] = {}
registers["c"][name]["register"] = ClassicalRegister(register[1], name)
registers["c"][name]["index_map"] = dict(
zip(bit_indices, registers["c"][name]["register"])
)
registers["c"][name] = (standalone, bit_indices)
return registers
@ -477,20 +486,22 @@ def _read_instruction(file_obj, circuit, registers, custom_instructions):
instruction_raw = file_obj.read(INSTRUCTION_SIZE)
instruction = struct.unpack(INSTRUCTION_PACK, instruction_raw)
name_size = instruction[0]
label_size = instruction[1]
qargs = []
cargs = []
params = []
gate_name = file_obj.read(name_size).decode("utf8")
num_qargs = instruction[2]
num_cargs = instruction[3]
num_params = instruction[1]
has_condition = instruction[4]
register_name_size = instruction[5]
label = file_obj.read(label_size).decode("utf8")
num_qargs = instruction[3]
num_cargs = instruction[4]
num_params = instruction[2]
has_condition = instruction[5]
register_name_size = instruction[6]
condition_register = file_obj.read(register_name_size).decode("utf8")
condition_value = instruction[6]
condition_value = instruction[7]
condition_tuple = None
if has_condition:
condition_tuple = (registers["c"][condition_register]["register"], condition_value)
condition_tuple = (registers["c"][condition_register], condition_value)
qubit_indices = dict(enumerate(circuit.qubits))
clbit_indices = dict(enumerate(circuit.clbits))
# Load Arguments
@ -536,6 +547,8 @@ def _read_instruction(file_obj, circuit, registers, custom_instructions):
if gate_name in ("Gate", "Instruction"):
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):
@ -549,6 +562,8 @@ def _read_instruction(file_obj, circuit, registers, custom_instructions):
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:
@ -557,6 +572,8 @@ def _read_instruction(file_obj, circuit, registers, custom_instructions):
params = [len(qargs)]
gate = gate_class(*params)
gate.condition = condition_tuple
if label_size > 0:
gate.label = label
circuit._append(gate, qargs, cargs)
@ -674,9 +691,15 @@ def _write_instruction(file_obj, instruction_tuple, custom_instructions, index_m
condition_value = instruction_tuple[0].condition[1]
gate_class_name = gate_class_name.encode("utf8")
label = getattr(instruction_tuple[0], "label")
if label:
label_raw = label.encode("utf8")
else:
label_raw = b""
instruction_raw = struct.pack(
INSTRUCTION_PACK,
len(gate_class_name),
len(label_raw),
len(instruction_tuple[0].params),
instruction_tuple[0].num_qubits,
instruction_tuple[0].num_clbits,
@ -686,6 +709,7 @@ def _write_instruction(file_obj, instruction_tuple, custom_instructions, index_m
)
file_obj.write(instruction_raw)
file_obj.write(gate_class_name)
file_obj.write(label_raw)
file_obj.write(condition_register)
# Encode instruciton args
for qbit in instruction_tuple[1]:
@ -854,16 +878,18 @@ def _write_circuit(file_obj, circuit):
clbit_indices = {bit: index for index, bit in enumerate(circuit.clbits)}
if num_registers > 0:
for reg in circuit.qregs:
standalone = all(bit._register is reg for bit in reg)
reg_name = reg.name.encode("utf8")
file_obj.write(struct.pack(REGISTER_PACK, b"q", reg.size, len(reg_name)))
file_obj.write(struct.pack(REGISTER_PACK, b"q", standalone, reg.size, len(reg_name)))
file_obj.write(reg_name)
REGISTER_ARRAY_PACK = "%sI" % reg.size
REGISTER_ARRAY_PACK = "!%sI" % reg.size
file_obj.write(struct.pack(REGISTER_ARRAY_PACK, *(qubit_indices[bit] for bit in reg)))
for reg in circuit.cregs:
standalone = all(bit._register is reg for bit in reg)
reg_name = reg.name.encode("utf8")
file_obj.write(struct.pack(REGISTER_PACK, b"c", reg.size, len(reg_name)))
file_obj.write(struct.pack(REGISTER_PACK, b"c", standalone, reg.size, len(reg_name)))
file_obj.write(reg_name)
REGISTER_ARRAY_PACK = "%sI" % reg.size
REGISTER_ARRAY_PACK = "!%sI" % reg.size
file_obj.write(struct.pack(REGISTER_ARRAY_PACK, *(clbit_indices[bit] for bit in reg)))
instruction_buffer = io.BytesIO()
custom_instructions = {}
@ -961,25 +987,91 @@ def _read_circuit(file_obj):
num_registers,
num_instructions,
) = header
registers = {}
out_registers = {"q": {}, "c": {}}
if num_registers > 0:
circ = QuantumCircuit(name=name, global_phase=global_phase, metadata=metadata)
# TODO Update to handle registers composed of not continuous bit
# indices. Right now this only works for standalone registers or
# registers composed bit indices that are continuous
registers = _read_registers(file_obj, num_registers)
for qreg in registers["q"].values():
min_index = min(qreg["index_map"].keys())
qubits = [Qubit() for i in range(min_index - len(circ.qubits))]
if qubits:
circ.add_bits(qubits)
circ.add_register(qreg["register"])
for creg in registers["c"].values():
min_index = min(creg["index_map"].keys())
clbits = [Clbit() for i in range(min_index - len(circ.clbits))]
if clbits:
circ.add_bits(clbits)
circ.add_register(creg["register"])
for bit_type_label, bit_type, reg_type in [
("q", Qubit, QuantumRegister),
("c", Clbit, ClassicalRegister),
]:
register_bits = set()
# Add quantum registers and bits
for register_name in registers[bit_type_label]:
standalone, indices = registers[bit_type_label][register_name]
if standalone:
start = min(indices)
count = start
out_of_order = False
for index in indices:
if not out_of_order and index != count:
out_of_order = True
count += 1
if index in register_bits:
raise QiskitError("Duplicate register bits found")
register_bits.add(index)
num_reg_bits = len(indices)
# Create a standlone register of the appropriate length (from
# the number of indices in the qpy data) and add it to the circuit
reg = reg_type(num_reg_bits, register_name)
# If any bits from qreg are out of order in the circuit handle
# is case
if out_of_order:
sorted_indices = np.argsort(indices)
for index in sorted_indices:
pos = indices[index]
if bit_type_label == "q":
bit_len = len(circ.qubits)
else:
bit_len = len(circ.clbits)
# Fill any holes between the current register bit and the
# next one
if pos > bit_len:
bits = [bit_type() for _ in range(pos - bit_len)]
circ.add_bits(bits)
circ.add_bits([reg[index]])
circ.add_register(reg)
else:
if bit_type_label == "q":
bit_len = len(circ.qubits)
else:
bit_len = len(circ.clbits)
# If there is a hole between the start of the register and the
# current bits and standalone bits to fill the gap.
if start > len(circ.qubits):
bits = [bit_type() for _ in range(start - bit_len)]
circ.add_bits(bit_len)
circ.add_register(reg)
out_registers[bit_type_label][register_name] = reg
else:
for index in indices:
if bit_type_label == "q":
bit_len = len(circ.qubits)
else:
bit_len = len(circ.clbits)
# Add any missing bits
bits = [bit_type() for _ in range(index + 1 - bit_len)]
circ.add_bits(bits)
if index in register_bits:
raise QiskitError("Duplicate register bits found")
register_bits.add(index)
if bit_type_label == "q":
bits = [circ.qubits[i] for i in indices]
else:
bits = [circ.clbits[i] for i in indices]
reg = reg_type(name=register_name, bits=bits)
circ.add_register(reg)
out_registers[bit_type_label][register_name] = reg
# If we don't have sufficient bits in the circuit after adding
# all the registers add more bits to fill the circuit
if len(circ.qubits) < num_qubits:
qubits = [Qubit() for _ in range(num_qubits - len(circ.qubits))]
circ.add_bits(qubits)
if len(circ.clbits) < num_clbits:
clbits = [Clbit() for _ in range(num_qubits - len(circ.clbits))]
circ.add_bits(clbits)
else:
circ = QuantumCircuit(
num_qubits,
@ -990,6 +1082,6 @@ def _read_circuit(file_obj):
)
custom_instructions = _read_custom_instructions(file_obj)
for _instruction in range(num_instructions):
_read_instruction(file_obj, circ, registers, custom_instructions)
_read_instruction(file_obj, circ, out_registers, custom_instructions)
return circ

View File

@ -14,17 +14,23 @@
"""Test cases for the circuit qasm_file and qasm_string method."""
import io
import random
import numpy as np
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
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
from qiskit.circuit.instruction import Instruction
from qiskit.circuit.parameter import Parameter
from qiskit.extensions import UnitaryGate
from qiskit.opflow import X, Y, Z
from qiskit.test import QiskitTestCase
from qiskit.circuit.qpy_serialization import dump, load
from qiskit.quantum_info.random import random_unitary
class TestLoadFromQPY(QiskitTestCase):
@ -271,3 +277,200 @@ class TestLoadFromQPY(QiskitTestCase):
qpy_file.seek(0)
new_circs = load(qpy_file)
self.assertEqual(circuits, new_circs)
def test_shared_bit_register(self):
"""Test a circuit with shared bit registers."""
qubits = [Qubit() for _ in range(5)]
qc = QuantumCircuit()
qc.add_bits(qubits)
qr = QuantumRegister(bits=qubits)
qc.add_register(qr)
qc.h(qr)
qc.cx(0, 1)
qc.cx(0, 2)
qc.cx(0, 3)
qc.cx(0, 4)
qc.measure_all()
qpy_file = io.BytesIO()
dump(qc, qpy_file)
qpy_file.seek(0)
new_qc = load(qpy_file)[0]
self.assertEqual(qc, new_qc)
def test_hybrid_standalone_register(self):
"""Test qpy serialization with registers that mix bit types"""
qr = QuantumRegister(5, "foo")
qr = QuantumRegister(name="bar", bits=qr[:3] + [Qubit(), Qubit()])
cr = ClassicalRegister(5, "foo")
cr = ClassicalRegister(name="classical_bar", bits=cr[:3] + [Clbit(), Clbit()])
qc = QuantumCircuit(qr, cr)
qc.h(0)
qc.cx(0, 1)
qc.cx(0, 2)
qc.cx(0, 3)
qc.cx(0, 4)
qc.measure(qr, cr)
qpy_file = io.BytesIO()
dump(qc, qpy_file)
qpy_file.seek(0)
new_circ = load(qpy_file)[0]
self.assertEqual(qc, new_circ)
def test_mixed_registers(self):
"""Test circuit with mix of standalone and shared registers."""
qubits = [Qubit() for _ in range(5)]
clbits = [Clbit() for _ in range(5)]
qc = QuantumCircuit()
qc.add_bits(qubits)
qc.add_bits(clbits)
qr = QuantumRegister(bits=qubits)
cr = ClassicalRegister(bits=clbits)
qc.add_register(qr)
qc.add_register(cr)
qr_standalone = QuantumRegister(2, "standalone")
qc.add_register(qr_standalone)
cr_standalone = ClassicalRegister(2, "classical_standalone")
qc.add_register(cr_standalone)
qc.unitary(random_unitary(32, seed=42), qr)
qc.unitary(random_unitary(4, seed=100), qr_standalone)
qc.measure(qr, cr)
qc.measure(qr_standalone, cr_standalone)
qpy_file = io.BytesIO()
dump(qc, qpy_file)
qpy_file.seek(0)
new_circ = load(qpy_file)[0]
self.assertEqual(qc, new_circ)
def test_standalone_and_shared_out_of_order(self):
"""Test circuit with register bits inserted out of order."""
qr_standalone = QuantumRegister(2, "standalone")
qubits = [Qubit() for _ in range(5)]
clbits = [Clbit() for _ in range(5)]
qc = QuantumCircuit()
qc.add_bits(qubits)
qc.add_bits(clbits)
random.shuffle(qubits)
random.shuffle(clbits)
qr = QuantumRegister(bits=qubits)
cr = ClassicalRegister(bits=clbits)
qc.add_register(qr)
qc.add_register(cr)
qr_standalone = QuantumRegister(2, "standalone")
cr_standalone = ClassicalRegister(2, "classical_standalone")
qc.add_bits([qr_standalone[1], qr_standalone[0]])
qc.add_bits([cr_standalone[1], cr_standalone[0]])
qc.add_register(qr_standalone)
qc.add_register(cr_standalone)
qc.unitary(random_unitary(32, seed=42), qr)
qc.unitary(random_unitary(4, seed=100), qr_standalone)
qc.measure(qr, cr)
qc.measure(qr_standalone, cr_standalone)
qpy_file = io.BytesIO()
dump(qc, qpy_file)
qpy_file.seek(0)
new_circ = load(qpy_file)[0]
self.assertEqual(qc, new_circ)
def test_unitary_gate_with_label(self):
"""Test that numpy array parameters are correctly serialized with a label"""
qc = QuantumCircuit(1)
unitary = np.array([[0, 1], [1, 0]])
unitary_gate = UnitaryGate(unitary, "My Special unitary")
qc.append(unitary_gate, [0])
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])
def test_opaque_gate_with_label(self):
"""Test that custom opaque gate is correctly serialized with a label"""
custom_gate = Gate("black_box", 1, [])
custom_gate.label = "My Special Black Box"
qc = QuantumCircuit(1)
qc.append(custom_gate, [0])
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])
def test_opaque_instruction_with_label(self):
"""Test that custom opaque instruction is correctly serialized with a label"""
custom_gate = Instruction("black_box", 1, 0, [])
custom_gate.label = "My Special Black Box Instruction"
qc = QuantumCircuit(1)
qc.append(custom_gate, [0])
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])
def test_custom_gate_with_label(self):
"""Test that custom gate is correctly serialized with a label"""
custom_gate = Gate("black_box", 1, [])
custom_definition = QuantumCircuit(1)
custom_definition.h(0)
custom_definition.rz(1.5, 0)
custom_definition.sdg(0)
custom_gate.definition = custom_definition
custom_gate.label = "My special black box with a definition"
qc = QuantumCircuit(1)
qc.append(custom_gate, [0])
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(qc.decompose(), new_circ.decompose())
self.assertEqual([x[0].label for x in qc.data], [x[0].label for x in new_circ.data])
def test_custom_instruction_with_label(self):
"""Test that custom instruction is correctly serialized with a label"""
custom_gate = Instruction("black_box", 1, 0, [])
custom_definition = QuantumCircuit(1)
custom_definition.h(0)
custom_definition.rz(1.5, 0)
custom_definition.sdg(0)
custom_gate.definition = custom_definition
custom_gate.label = "My Special Black Box Instruction with a definition"
qc = QuantumCircuit(1)
qc.append(custom_gate, [0])
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(qc.decompose(), new_circ.decompose())
self.assertEqual([x[0].label for x in qc.data], [x[0].label for x in new_circ.data])
def test_standard_gate_with_label(self):
"""Test a standard gate with a label."""
qc = QuantumCircuit(1)
gate = XGate()
gate.label = "My special X gate"
qc.append(gate, [0])
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])
def test_circuit_with_conditional_with_label(self):
"""Test that instructions with conditions are correctly serialized."""
qc = QuantumCircuit(1, 1)
gate = XGate(label="My conditional x gate")
gate.c_if(qc.cregs[0], 1)
qc.append(gate, [0])
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])