Skip to content

Commit 5557271

Browse files
kt474t-imamichi
andauthored
Make PrimitiveJob serializable (#12963)
* Make PrimtiveJob serializable * fix style * fix init param order & ignore pylint * add reno * Remove default none * Add comment in _prepare_dump() * Update qiskit/primitives/primitive_job.py Co-authored-by: Takashi Imamichi <[email protected]> * formatting * Update releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml Co-authored-by: Takashi Imamichi <[email protected]> * implement __getstate__ and __setstate__ * lint * Update releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml Co-authored-by: Takashi Imamichi <[email protected]> * Update test/python/primitives/test_primitive_job.py Co-authored-by: Takashi Imamichi <[email protected]> --------- Co-authored-by: Takashi Imamichi <[email protected]> Co-authored-by: Takashi Imamichi <[email protected]>
1 parent 659aec8 commit 5557271

File tree

4 files changed

+85
-13
lines changed

4 files changed

+85
-13
lines changed

qiskit/primitives/containers/data_bin.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,6 @@ def __init__(self, *, shape: ShapeInput = (), **data):
103103
def __len__(self):
104104
return len(self._data)
105105

106-
def __setattr__(self, *_):
107-
raise NotImplementedError
108-
109106
def __repr__(self):
110107
vals = [f"{name}={_value_repr(val)}" for name, val in self.items()]
111108
if self.ndim:

qiskit/primitives/primitive_job.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ def __init__(self, function, *args, **kwargs):
3535
super().__init__(str(uuid.uuid4()))
3636
self._future = None
3737
self._function = function
38+
self._result = None
39+
self._status = None
3840
self._args = args
3941
self._kwargs = kwargs
4042

@@ -46,19 +48,36 @@ def _submit(self):
4648
self._future = executor.submit(self._function, *self._args, **self._kwargs)
4749
executor.shutdown(wait=False)
4850

51+
def __getstate__(self):
52+
_ = self.result()
53+
_ = self.status()
54+
state = self.__dict__.copy()
55+
state["_future"] = None
56+
return state
57+
58+
def __setstate__(self, state):
59+
self.__dict__.update(state)
60+
self._future = None
61+
4962
def result(self) -> ResultT:
50-
self._check_submitted()
51-
return self._future.result()
63+
if self._result is None:
64+
self._check_submitted()
65+
self._result = self._future.result()
66+
return self._result
5267

5368
def status(self) -> JobStatus:
54-
self._check_submitted()
55-
if self._future.running():
56-
return JobStatus.RUNNING
57-
elif self._future.cancelled():
58-
return JobStatus.CANCELLED
59-
elif self._future.done() and self._future.exception() is None:
60-
return JobStatus.DONE
61-
return JobStatus.ERROR
69+
if self._status is None:
70+
self._check_submitted()
71+
if self._future.running():
72+
# we should not store status running because it is not completed
73+
return JobStatus.RUNNING
74+
elif self._future.cancelled():
75+
self._status = JobStatus.CANCELLED
76+
elif self._future.done() and self._future.exception() is None:
77+
self._status = JobStatus.DONE
78+
else:
79+
self._status = JobStatus.ERROR
80+
return self._status
6281

6382
def _check_submitted(self):
6483
if self._future is None:
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
features_primitives:
3+
- |
4+
To make :class:`.PrimitiveJob` serializable, :class:`.DataBin` has been
5+
updated to be pickleable. As a result, :class:`.PrimitiveResult` is now also pickleable.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# This code is part of Qiskit.
2+
#
3+
# (C) Copyright IBM 2025.
4+
#
5+
# This code is licensed under the Apache License, Version 2.0. You may
6+
# obtain a copy of this license in the LICENSE.txt file in the root directory
7+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
#
9+
# Any modifications or derivative works of this code must retain this
10+
# copyright notice, and modified files need to carry a notice indicating
11+
# that they have been altered from the originals.
12+
13+
"""Tests for PrimitiveJob."""
14+
15+
import pickle
16+
from test import QiskitTestCase
17+
18+
import numpy as np
19+
from ddt import data, ddt
20+
21+
from qiskit import QuantumCircuit
22+
from qiskit.primitives import PrimitiveJob, StatevectorSampler
23+
24+
25+
@ddt
26+
class TestPrimitiveJob(QiskitTestCase):
27+
"""Tests PrimitiveJob."""
28+
29+
@data(1, 2, 3)
30+
def test_serialize(self, size):
31+
"""Test serialize."""
32+
n = 2
33+
qc = QuantumCircuit(n)
34+
qc.h(range(n))
35+
qc.measure_all()
36+
sampler = StatevectorSampler()
37+
job = sampler.run([qc] * size)
38+
obj = pickle.dumps(job)
39+
job2 = pickle.loads(obj)
40+
self.assertIsInstance(job2, PrimitiveJob)
41+
self.assertEqual(job.job_id(), job2.job_id())
42+
self.assertEqual(job.status(), job2.status())
43+
self.assertEqual(job.metadata, job2.metadata)
44+
result = job.result()
45+
result2 = job2.result()
46+
self.assertEqual(result.metadata, result2.metadata)
47+
self.assertEqual(len(result), len(result2))
48+
for sampler_pub in result:
49+
self.assertEqual(sampler_pub.metadata, sampler_pub.metadata)
50+
self.assertEqual(sampler_pub.data.keys(), sampler_pub.data.keys())
51+
np.testing.assert_allclose(sampler_pub.join_data().array, sampler_pub.join_data().array)

0 commit comments

Comments
 (0)