diff --git a/qiskit/circuit/qpy_serialization.py b/qiskit/circuit/qpy_serialization.py index bd72322a50de..1c83c39c9eec 100644 --- a/qiskit/circuit/qpy_serialization.py +++ b/qiskit/circuit/qpy_serialization.py @@ -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 @@ -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 @@ -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") diff --git a/releasenotes/notes/single-qubit-conditions-qpy-39a5a9a7204a27dd.yaml b/releasenotes/notes/single-qubit-conditions-qpy-39a5a9a7204a27dd.yaml new file mode 100644 index 000000000000..76863b65ce41 --- /dev/null +++ b/releasenotes/notes/single-qubit-conditions-qpy-39a5a9a7204a27dd.yaml @@ -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. diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index c15c8b49f66e..e7c0c256c3bc 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -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 diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index bbe69712c60a..409c3ee66c38 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -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 @@ -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