Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle single bit conditions in QPY #6775

Merged
merged 9 commits into from
Jul 29, 2021
30 changes: 26 additions & 4 deletions qiskit/circuit/qpy_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,10 @@ class if it's defined in Qiskit. Otherwise it falls back to the custom
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.
register name. In case of single classical bit conditions the register name
utf8 data will be prefixed with a null character "\\x00" and then a utf8 string
integer representing the classical bit index in the circuit that the condition
is on.

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
Expand Down Expand Up @@ -531,7 +534,21 @@ def _read_instruction(file_obj, circuit, registers, custom_instructions):
condition_value = instruction[7]
condition_tuple = None
if has_condition:
condition_tuple = (registers["c"][condition_register], condition_value)
# If an invalid register name is used assume it's a single bit
# condition and treat the register name as a string of the clbit index
if ClassicalRegister.name_format.match(condition_register) is None:
# If invalid register prefixed with null character it's a clbit
# index for single bit condition
if condition_register[0] == "\x00":
conditional_bit = int(condition_register[1:])
condition_tuple = (circuit.clbits[conditional_bit], condition_value)
else:
raise ValueError(
f"Invalid register name: {condition_register} for condition register of "
f"instruction: {gate_name}"
)
else:
condition_tuple = (registers["c"][condition_register], condition_value)
qubit_indices = dict(enumerate(circuit.qubits))
clbit_indices = dict(enumerate(circuit.clbits))
# Load Arguments
Expand Down Expand Up @@ -726,8 +743,13 @@ def _write_instruction(file_obj, instruction_tuple, custom_instructions, index_m
condition_value = 0
if instruction_tuple[0].condition:
has_condition = True
condition_register = instruction_tuple[0].condition[0].name.encode("utf8")
condition_value = instruction_tuple[0].condition[1]
if isinstance(instruction_tuple[0].condition[0], Clbit):
bit_index = index_map["c"][instruction_tuple[0].condition[0]]
condition_register = b"\x00" + str(bit_index).encode("utf8")
condition_value = int(instruction_tuple[0].condition[1])
else:
condition_register = instruction_tuple[0].condition[0].name.encode("utf8")
condition_value = instruction_tuple[0].condition[1]

gate_class_name = gate_class_name.encode("utf8")
label = getattr(instruction_tuple[0], "label")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
fixes:
- |
Fixed the generation and loading of QPY files with
:func:`qiskit.circuit.qpy_serialiation.dump` and
:func:`qiskit.circuit.qpy_serialization.load` for
:class:`~qiskit.circuit.QuantumCircuit` objects that contain instructions
with classical conditions on a single :class:`~qiskit.circuit.Clbit` instead
of a :class:`~qiskit.circuit.ClassicalRegister`. While the use of single
:class:`~qiskit.circuit.Clbit` conditions is not yet fully supported if you
were using them in a circuit they are now correctly serialized by QPY.
16 changes: 16 additions & 0 deletions test/python/circuit/test_circuit_load_from_qpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,22 @@ def test_initialize_qft(self):
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_single_bit_teleportation(self):
"""Test a teleportation circuit with single bit conditions."""
qr = QuantumRegister(1)
cr = ClassicalRegister(2, name="name")
qc = QuantumCircuit(qr, cr, name="Reset Test")
qc.x(0)
qc.measure(0, cr[0])
qc.x(0).c_if(cr[0], 1)
qc.measure(0, cr[1])
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_qaoa(self):
"""Test loading a QAOA circuit works."""
cost_operator = Z ^ I ^ I ^ Z
Expand Down
12 changes: 12 additions & 0 deletions test/qpy_compat/test_qpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,17 @@ def generate_qft_circuit():
return qft_circ


def generate_single_clbit_condition_teleportation():
qr = QuantumRegister(1)
cr = ClassicalRegister(2, name="name")
teleport_qc = QuantumCircuit(qr, cr, name="Reset Test")
teleport_qc.x(0)
teleport_qc.measure(0, cr[0])
teleport_qc.x(0).c_if(cr[0], 1)
teleport_qc.measure(0, cr[1])
return teleport_qc


def generate_circuits(version_str=None):
"""Generate reference circuits."""
version_parts = None
Expand All @@ -210,6 +221,7 @@ def generate_circuits(version_str=None):

if version_parts >= (0, 18, 1):
output_circuits["qft_circuit.qpy"] = [generate_qft_circuit()]
output_circuits["teleport.qpy"] = [generate_single_clbit_condition_teleportation()]

return output_circuits

Expand Down