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 #11495

Merged
merged 8 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
17 changes: 14 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,13 +290,17 @@ 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":
pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be?

Suggested change
pass
gate_class = Clifford

Mostly just for consistency. But if we did this then we would be able to drop the special handling in L302 as it would just call gate_class(*params). Or is len(Clifford.params) > 1 and doing this wouldn't work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in bc1f5e3.

else:
raise AttributeError("Invalid instruction type: %s" % gate_name)

if instruction.label_size <= 0:
label = None
if gate_name in {"IfElseOp", "WhileLoopOp"}:
gate = gate_class(condition, *params, label=label)
elif gate_name == "Clifford":
gate = Clifford(params[0])
elif version >= 5 and issubclass(gate_class, ControlledGate):
if gate_name in {
"MCPhaseGate",
Expand Down Expand Up @@ -578,7 +582,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 +595,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 +637,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 +650,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 @@ -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."""
Expand Down