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

Fix qpy support for Cliffords (backport #11495) #11582

Merged
merged 1 commit into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions qiskit/qpy/binary_io/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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 (
(
Expand All @@ -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"
Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand Down
6 changes: 6 additions & 0 deletions releasenotes/notes/fix-clifford-qpy-2ffc8308c888e7e0.yaml
Original file line number Diff line number Diff line change
@@ -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`).
29 changes: 28 additions & 1 deletion test/python/circuit/test_circuit_load_from_qpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1697,6 +1697,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."""
Expand Down
17 changes: 17 additions & 0 deletions test/qpy_compat/test_qpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,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
Expand Down Expand Up @@ -752,6 +767,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


Expand Down