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 pickle handling for SingletonGate class #10871

Merged
merged 5 commits into from
Sep 28, 2023
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
5 changes: 5 additions & 0 deletions qiskit/circuit/singleton_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ def __init__(self, *args, _condition=None, **kwargs):
super().__init__(*args, **kwargs)
self._condition = _condition

def __getnewargs_ex__(self):
if not self.mutable:
return ((), {})
return ((self.label, self._condition, self.duration, self.unit), {})

def c_if(self, classical, val):
if not isinstance(classical, (ClassicalRegister, Clbit)):
raise CircuitError("c_if must be used with a classical register or classical bit")
Expand Down
26 changes: 26 additions & 0 deletions test/python/circuit/test_singleton_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"""

import copy
import io
import pickle

from qiskit.circuit.library import HGate, SXGate
from qiskit.circuit import Clbit, QuantumCircuit, QuantumRegister, ClassicalRegister
Expand Down Expand Up @@ -251,3 +253,27 @@ def test_positional_label(self):
label_gate = SXGate("I am a little label")
self.assertIsNot(gate, label_gate)
self.assertEqual(label_gate.label, "I am a little label")

def test_immutable_pickle(self):
gate = SXGate()
self.assertFalse(gate.mutable)
with io.BytesIO() as fd:
pickle.dump(gate, fd)
fd.seek(0)
copied = pickle.load(fd)
Comment on lines +260 to +263
Copy link
Member

Choose a reason for hiding this comment

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

Totally unimportant, but pickle allows serialisation to bytes directly, so you can do this as a one-liner if you prefer: copied = pickle.loads(pickle.dumps(gate)).

Copy link
Member Author

Choose a reason for hiding this comment

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

I just don't like using pickle.dumps because s implies string to me and it's not a string :D

Copy link
Contributor

Choose a reason for hiding this comment

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

Rest in peace, Python 2 🙂

Copy link
Member

Choose a reason for hiding this comment

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

maybe it's an initial from a language that reads right-to-left: "DUMP byteS" instead of "DUMP String" lol

Copy link
Contributor

Choose a reason for hiding this comment

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

Here's the Python 2 docstring for dumps 🙂 : "Return the pickled representation of the object as a string, instead of writing it to a file."

self.assertFalse(copied.mutable)
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
self.assertIs(copied, gate)

def test_mutable_pickle(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

One thing that surprised me with this issue is that it was not caught in this repo before the singleton gate PR was merged. I expect there is a good set of tests for transpiling conditional gates, but likely the tests all transpile a single circuit and don't hit the pickling issues that come up with parallel transpilation. I wonder if the tests added here are enough or if there should be more tests of pickling and unpickling circuits.

Copy link
Member

Choose a reason for hiding this comment

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

There's actually not a vast amount of testing of conditional gates (with .condition set), because they typically weren't executable on anything but simulators until IBM hardware grew support for midcircuit measurements and more generalised control flow, at which point we began pivoting to IfElseOp.

The bug getting through points to a deficiency in integration testing around conditional gates for sure, and we can add more tests along those lines until Instruction.condition is completely removed in 1.0. Conditionals after transpilation are also harder to write good tests about, because we can't just test for unitary equivalence or anything like that when they're present, so it's easier for stuff to slip through.

gate = SXGate()
clbit = Clbit()
condition_gate = gate.c_if(clbit, 0)
self.assertIsNot(gate, condition_gate)
self.assertEqual(condition_gate.condition, (clbit, 0))
self.assertTrue(condition_gate.mutable)
with io.BytesIO() as fd:
pickle.dump(condition_gate, fd)
fd.seek(0)
copied = pickle.load(fd)
self.assertEqual(copied, condition_gate)
self.assertTrue(copied.mutable)
14 changes: 14 additions & 0 deletions test/python/compiler/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2173,6 +2173,20 @@ def run(self, dag):
added_cal = qc_test.calibrations["sx"][((0,), ())]
self.assertEqual(added_cal, ref_cal)

@data(0, 1, 2, 3)
def test_parallel_singleton_conditional_gate(self, opt_level):
"""Test that singleton mutable instance doesn't lose state in parallel."""
backend = FakeNairobiV2()
circ = QuantumCircuit(2, 1)
circ.h(0)
circ.measure(0, circ.clbits[0])
circ.z(1).c_if(circ.clbits[0], 1)
res = transpile(
[circ, circ], backend, optimization_level=opt_level, seed_transpiler=123456769
)
self.assertTrue(res[0].data[-1].operation.mutable)
self.assertEqual(res[0].data[-1].operation.condition, (res[0].clbits[0], 1))

@data(0, 1, 2, 3)
def test_backendv2_and_basis_gates(self, opt_level):
"""Test transpile() with BackendV2 and basis_gates set."""
Expand Down