diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 6d910746a89a..cdf1a27657fe 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -37,7 +37,7 @@ from qiskit.extensions import quantum_initializer from qiskit.qpy import common, formats, type_keys from qiskit.qpy.binary_io import value, schedules -from qiskit.quantum_info.operators import SparsePauliOp +from qiskit.quantum_info.operators import SparsePauliOp, Clifford from qiskit.synthesis import evolution as evo_synth from qiskit.transpiler.layout import Layout, TranspileLayout @@ -290,6 +290,8 @@ def _read_instruction( gate_class = getattr(quantum_initializer, gate_name) elif hasattr(controlflow, gate_name): gate_class = getattr(controlflow, gate_name) + elif gate_name == "Clifford": + gate_class = Clifford else: raise AttributeError("Invalid instruction type: %s" % gate_name) @@ -578,7 +580,11 @@ def _dumps_instruction_parameter(param, index_map, use_symengine): # pylint: disable=too-many-boolean-expressions def _write_instruction(file_obj, instruction, custom_operations, index_map, use_symengine): - gate_class_name = instruction.operation.base_class.__name__ + if isinstance(instruction.operation, Instruction): + gate_class_name = instruction.operation.base_class.__name__ + else: + gate_class_name = instruction.operation.__class__.__name__ + custom_operations_list = [] if ( ( @@ -587,6 +593,7 @@ def _write_instruction(file_obj, instruction, custom_operations, index_map, use_ and not hasattr(extensions, gate_class_name) and not hasattr(quantum_initializer, gate_class_name) and not hasattr(controlflow, gate_class_name) + and gate_class_name != "Clifford" ) or gate_class_name == "Gate" or gate_class_name == "Instruction" @@ -628,7 +635,7 @@ def _write_instruction(file_obj, instruction, custom_operations, index_map, use_ condition_value = int(instruction.operation.condition[1]) gate_class_name = gate_class_name.encode(common.ENCODE) - label = getattr(instruction.operation, "label") + label = getattr(instruction.operation, "label", None) if label: label_raw = label.encode(common.ENCODE) else: @@ -641,6 +648,8 @@ def _write_instruction(file_obj, instruction, custom_operations, index_map, use_ instruction.operation.target, tuple(instruction.operation.cases_specifier()), ] + elif isinstance(instruction.operation, Clifford): + instruction_params = [instruction.operation.tableau] else: instruction_params = instruction.operation.params diff --git a/releasenotes/notes/fix-clifford-qpy-2ffc8308c888e7e0.yaml b/releasenotes/notes/fix-clifford-qpy-2ffc8308c888e7e0.yaml new file mode 100644 index 000000000000..a13ca4d55c3f --- /dev/null +++ b/releasenotes/notes/fix-clifford-qpy-2ffc8308c888e7e0.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + QPY (using :func:`.qpy.dump` and :func:`.qpy.load`) will now correctly serialize + and deserialize quantum circuits with Clifford operators + (:class:`~qiskit.quantum_info.Clifford`). diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index 5695a298f782..c3d9aed88336 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -51,7 +51,7 @@ from qiskit.synthesis import LieTrotter, SuzukiTrotter from qiskit.test import QiskitTestCase from qiskit.qpy import dump, load -from qiskit.quantum_info import Pauli, SparsePauliOp +from qiskit.quantum_info import Pauli, SparsePauliOp, Clifford from qiskit.quantum_info.random import random_unitary from qiskit.circuit.controlledgate import ControlledGate from qiskit.utils import optionals @@ -1696,6 +1696,33 @@ def test_valid_circuit_with_initialize_instruction(self, param): self.assertEqual(qc, new_circuit) self.assertDeprecatedBitProperties(qc, new_circuit) + def test_clifford(self): + """Test that circuits with Clifford operations can be saved and retrieved correctly.""" + cliff1 = Clifford.from_dict( + { + "stabilizer": ["-IZX", "+ZYZ", "+ZII"], + "destabilizer": ["+ZIZ", "+ZXZ", "-XIX"], + } + ) + cliff2 = Clifford.from_dict( + { + "stabilizer": ["+YX", "+ZZ"], + "destabilizer": ["+IZ", "+YI"], + } + ) + + circuit = QuantumCircuit(6, 1) + circuit.cx(0, 1) + circuit.append(cliff1, [2, 4, 5]) + circuit.h(4) + circuit.append(cliff2, [3, 0]) + + with io.BytesIO() as fptr: + dump(circuit, fptr) + fptr.seek(0) + new_circuit = load(fptr)[0] + self.assertEqual(circuit, new_circuit) + class TestSymengineLoadFromQPY(QiskitTestCase): """Test use of symengine in qpy set of methods.""" diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index 5fdf0eaffe7c..b6f20fd47e63 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -640,6 +640,21 @@ def generate_layout_circuits(): return [qc] +def generate_clifford_circuits(): + """Test qpy circuits with Clifford operations.""" + from qiskit.quantum_info import Clifford + + cliff = Clifford.from_dict( + { + "stabilizer": ["-IZX", "+ZYZ", "+ZII"], + "destabilizer": ["+ZIZ", "+ZXZ", "-XIX"], + } + ) + qc = QuantumCircuit(3) + qc.append(cliff, [0, 1, 2]) + return [qc] + + def generate_control_flow_expr(): """`IfElseOp`, `WhileLoopOp` and `SwitchCaseOp` with `Expr` nodes in their discriminators.""" from qiskit.circuit.classical import expr, types @@ -769,6 +784,8 @@ def generate_circuits(version_parts): "acquire_inst_with_kernel_and_disc.qpy" ] = generate_acquire_instruction_with_kernel_and_discriminator() output_circuits["control_flow_expr.qpy"] = generate_control_flow_expr() + if version_parts >= (0, 45, 2): + output_circuits["clifford.qpy"] = generate_clifford_circuits() return output_circuits