From 35d0954578d5312a63fb702f29bbd3d47ff25cc5 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Thu, 15 Aug 2024 13:20:04 -0400 Subject: [PATCH 01/13] Make PrimtiveJob serializable --- qiskit/primitives/containers/data_bin.py | 3 -- qiskit/primitives/primitive_job.py | 35 ++++++++++++++++-------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/qiskit/primitives/containers/data_bin.py b/qiskit/primitives/containers/data_bin.py index 5ea31f7510e6..6d0356fd1ec3 100644 --- a/qiskit/primitives/containers/data_bin.py +++ b/qiskit/primitives/containers/data_bin.py @@ -93,9 +93,6 @@ def __init__(self, *, shape: ShapeInput = (), **data): def __len__(self): return len(self._data) - def __setattr__(self, *_): - raise NotImplementedError - def __repr__(self): vals = [f"{name}={_value_repr(val)}" for name, val in self.items()] if self.ndim: diff --git a/qiskit/primitives/primitive_job.py b/qiskit/primitives/primitive_job.py index 64ab8d016095..6563aedc9f99 100644 --- a/qiskit/primitives/primitive_job.py +++ b/qiskit/primitives/primitive_job.py @@ -27,7 +27,7 @@ class PrimitiveJob(BasePrimitiveJob[ResultT, JobStatus]): Primitive job class for the reference implementations of Primitives. """ - def __init__(self, function, *args, **kwargs): + def __init__(self, function=None, *args, **kwargs): """ Args: function: A callable function to execute the job. @@ -35,6 +35,8 @@ def __init__(self, function, *args, **kwargs): super().__init__(str(uuid.uuid4())) self._future = None self._function = function + self._result = None + self._status = None self._args = args self._kwargs = kwargs @@ -46,19 +48,30 @@ def _submit(self): self._future = executor.submit(self._function, *self._args, **self._kwargs) executor.shutdown(wait=False) + def _prepare_dump(self): + _ = self.result() + _ = self.status() + self._future = None + def result(self) -> ResultT: - self._check_submitted() - return self._future.result() + if self._result is None: + self._check_submitted() + self._result = self._future.result() + return self._result def status(self) -> JobStatus: - self._check_submitted() - if self._future.running(): - return JobStatus.RUNNING - elif self._future.cancelled(): - return JobStatus.CANCELLED - elif self._future.done() and self._future.exception() is None: - return JobStatus.DONE - return JobStatus.ERROR + if self._status is None: + self._check_submitted() + if self._future.running(): + # we should not store status running because it is not completed + return JobStatus.RUNNING + elif self._future.cancelled(): + self._status = JobStatus.CANCELLED + elif self._future.done() and self._future.exception() is None: + self._status = JobStatus.DONE + else: + self._status = JobStatus.ERROR + return self._status def _check_submitted(self): if self._future is None: From cb378660efa65dcd4f79b46fe855ed0710f8adfe Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Thu, 15 Aug 2024 14:52:56 -0400 Subject: [PATCH 02/13] fix style --- qiskit/primitives/primitive_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/primitives/primitive_job.py b/qiskit/primitives/primitive_job.py index 6563aedc9f99..09ff6b1dc813 100644 --- a/qiskit/primitives/primitive_job.py +++ b/qiskit/primitives/primitive_job.py @@ -27,7 +27,7 @@ class PrimitiveJob(BasePrimitiveJob[ResultT, JobStatus]): Primitive job class for the reference implementations of Primitives. """ - def __init__(self, function=None, *args, **kwargs): + def __init__(self, *args, function=None, **kwargs): """ Args: function: A callable function to execute the job. From e58cc672368d3362fa6a521296285003e8928965 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Fri, 16 Aug 2024 12:55:12 -0400 Subject: [PATCH 03/13] fix init param order & ignore pylint --- qiskit/primitives/primitive_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/primitives/primitive_job.py b/qiskit/primitives/primitive_job.py index 09ff6b1dc813..868beee6d0ab 100644 --- a/qiskit/primitives/primitive_job.py +++ b/qiskit/primitives/primitive_job.py @@ -27,7 +27,7 @@ class PrimitiveJob(BasePrimitiveJob[ResultT, JobStatus]): Primitive job class for the reference implementations of Primitives. """ - def __init__(self, *args, function=None, **kwargs): + def __init__(self, function=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg """ Args: function: A callable function to execute the job. From f9970509fae5a090ff1bbe534f10f5caf71346eb Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Tue, 10 Dec 2024 14:34:52 -0500 Subject: [PATCH 04/13] add reno --- .../notes/serialize-primitive-job-aa97d0bf8221ea99.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml diff --git a/releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml b/releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml new file mode 100644 index 000000000000..08a65dd278c5 --- /dev/null +++ b/releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml @@ -0,0 +1,5 @@ +--- +features_primitives: + - | + To make :class:`.PrimitiveJob` serializable, `qiskit.primitives.containers.DataBin` has been + updated to be pickleable. As a result, :class:`.PrimitiveResult` is now also pickleable. From a6d1314247cc4004e9a91777577822e998b40962 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Wed, 11 Dec 2024 20:47:44 -0500 Subject: [PATCH 05/13] Remove default none --- qiskit/primitives/primitive_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/primitives/primitive_job.py b/qiskit/primitives/primitive_job.py index 868beee6d0ab..95e3768898f1 100644 --- a/qiskit/primitives/primitive_job.py +++ b/qiskit/primitives/primitive_job.py @@ -27,7 +27,7 @@ class PrimitiveJob(BasePrimitiveJob[ResultT, JobStatus]): Primitive job class for the reference implementations of Primitives. """ - def __init__(self, function=None, *args, **kwargs): # pylint: disable=keyword-arg-before-vararg + def __init__(self, function, *args, **kwargs): """ Args: function: A callable function to execute the job. From fac959a4f7deaf6375d23f8293c92fe2119ecdd6 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Mon, 16 Dec 2024 15:49:01 -0500 Subject: [PATCH 06/13] Add comment in _prepare_dump() --- qiskit/primitives/primitive_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/primitives/primitive_job.py b/qiskit/primitives/primitive_job.py index 95e3768898f1..79779d5262c4 100644 --- a/qiskit/primitives/primitive_job.py +++ b/qiskit/primitives/primitive_job.py @@ -48,7 +48,7 @@ def _submit(self): self._future = executor.submit(self._function, *self._args, **self._kwargs) executor.shutdown(wait=False) - def _prepare_dump(self): + def _prepare_dump(self): # This method allows PrimitiveJob to be serialized _ = self.result() _ = self.status() self._future = None From 7c4cc23efe15ee962ca4f23aea62f9eea07b28b8 Mon Sep 17 00:00:00 2001 From: Kevin Tian Date: Tue, 17 Dec 2024 12:36:12 -0500 Subject: [PATCH 07/13] Update qiskit/primitives/primitive_job.py Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> --- qiskit/primitives/primitive_job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/primitives/primitive_job.py b/qiskit/primitives/primitive_job.py index 79779d5262c4..fa49cfb56a8b 100644 --- a/qiskit/primitives/primitive_job.py +++ b/qiskit/primitives/primitive_job.py @@ -48,7 +48,8 @@ def _submit(self): self._future = executor.submit(self._function, *self._args, **self._kwargs) executor.shutdown(wait=False) - def _prepare_dump(self): # This method allows PrimitiveJob to be serialized + def _prepare_dump(self): + """This method allows PrimitiveJob to be serialized""" _ = self.result() _ = self.status() self._future = None From 37b594e128997640c9b17a9d1282ce8a41c1b5b7 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Tue, 17 Dec 2024 12:41:16 -0500 Subject: [PATCH 08/13] formatting --- qiskit/primitives/primitive_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/primitives/primitive_job.py b/qiskit/primitives/primitive_job.py index fa49cfb56a8b..580ab5930df5 100644 --- a/qiskit/primitives/primitive_job.py +++ b/qiskit/primitives/primitive_job.py @@ -49,7 +49,7 @@ def _submit(self): executor.shutdown(wait=False) def _prepare_dump(self): - """This method allows PrimitiveJob to be serialized""" + """This method allows PrimitiveJob to be serialized""" _ = self.result() _ = self.status() self._future = None From 4468ff6bc2f7eaa7eb4b3496d2e1bf931f5525a8 Mon Sep 17 00:00:00 2001 From: Kevin Tian Date: Wed, 18 Dec 2024 17:11:49 -0500 Subject: [PATCH 09/13] Update releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> --- .../notes/serialize-primitive-job-aa97d0bf8221ea99.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml b/releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml index 08a65dd278c5..071de1e35a9d 100644 --- a/releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml +++ b/releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml @@ -1,5 +1,5 @@ --- features_primitives: - | - To make :class:`.PrimitiveJob` serializable, `qiskit.primitives.containers.DataBin` has been + To make :class:`.PrimitiveJob` serializable, ``qiskit.primitives.containers.DataBin`` has been updated to be pickleable. As a result, :class:`.PrimitiveResult` is now also pickleable. From 8f8aeae2b9fac5b3b73e24fc632b0b3732a08b6a Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 27 Mar 2025 11:45:26 +0900 Subject: [PATCH 10/13] implement __getstate__ and __setstate__ --- qiskit/primitives/primitive_job.py | 9 +++- test/python/primitives/test_primitive_job.py | 51 ++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 test/python/primitives/test_primitive_job.py diff --git a/qiskit/primitives/primitive_job.py b/qiskit/primitives/primitive_job.py index 580ab5930df5..bc316cfde61d 100644 --- a/qiskit/primitives/primitive_job.py +++ b/qiskit/primitives/primitive_job.py @@ -48,10 +48,15 @@ def _submit(self): self._future = executor.submit(self._function, *self._args, **self._kwargs) executor.shutdown(wait=False) - def _prepare_dump(self): - """This method allows PrimitiveJob to be serialized""" + def __getstate__(self): _ = self.result() _ = self.status() + state = self.__dict__.copy() + state["_future"] = None + return state + + def __setstate__(self, state): + self.__dict__.update(state) self._future = None def result(self) -> ResultT: diff --git a/test/python/primitives/test_primitive_job.py b/test/python/primitives/test_primitive_job.py new file mode 100644 index 000000000000..c76dcc33d87d --- /dev/null +++ b/test/python/primitives/test_primitive_job.py @@ -0,0 +1,51 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2025. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Tests for PrimitiveJob.""" + +import pickle +from test import QiskitTestCase + +import numpy as np +from ddt import data, ddt + +from qiskit import QuantumCircuit +from qiskit.primitives import PrimitiveJob, StatevectorSampler + + +@ddt +class TestPrimitiveJob(QiskitTestCase): + """Tests PrimitiveJob.""" + + @data(1, 2, 3) + def test_serialize(self, size): + """Test serialize.""" + n = 2 + qc = QuantumCircuit(n) + qc.h(range(n)) + qc.measure_all() + sampler = StatevectorSampler() + job = sampler.run([qc] * size) + obj = pickle.dumps(job) + job2 = pickle.loads(obj) + self.assertIsInstance(job2, PrimitiveJob) + self.assertEqual(job.job_id(), job2.job_id()) + self.assertEqual(job.status(), job2.status()) + self.assertEqual(job.metadata, job2.metadata) + result = job.result() + result2 = job2.result() + self.assertEqual(result.metadata, result2.metadata) + self.assertEqual(len(result), len(result2)) + for i in range(len(result)): + self.assertEqual(result[i].metadata, result2[i].metadata) + self.assertEqual(result[i].data.keys(), result2[i].data.keys()) + np.testing.assert_allclose(result[i].join_data().array, result2[i].join_data().array) From 327bdc7d25bb771420342e8d4366efe6742c0bf7 Mon Sep 17 00:00:00 2001 From: kevin-tian Date: Thu, 27 Mar 2025 14:38:07 -0400 Subject: [PATCH 11/13] lint --- test/python/primitives/test_primitive_job.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/python/primitives/test_primitive_job.py b/test/python/primitives/test_primitive_job.py index c76dcc33d87d..cc6aca767494 100644 --- a/test/python/primitives/test_primitive_job.py +++ b/test/python/primitives/test_primitive_job.py @@ -45,7 +45,7 @@ def test_serialize(self, size): result2 = job2.result() self.assertEqual(result.metadata, result2.metadata) self.assertEqual(len(result), len(result2)) - for i in range(len(result)): - self.assertEqual(result[i].metadata, result2[i].metadata) - self.assertEqual(result[i].data.keys(), result2[i].data.keys()) - np.testing.assert_allclose(result[i].join_data().array, result2[i].join_data().array) + for _, sampler_pub in enumerate(result): + self.assertEqual(sampler_pub.metadata, sampler_pub.metadata) + self.assertEqual(sampler_pub.data.keys(), sampler_pub.data.keys()) + np.testing.assert_allclose(sampler_pub.join_data().array, sampler_pub.join_data().array) From 42e99c502b49c945bc060f3b491a04ab87354eb3 Mon Sep 17 00:00:00 2001 From: Kevin Tian Date: Fri, 28 Mar 2025 15:24:44 -0400 Subject: [PATCH 12/13] Update releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> --- .../notes/serialize-primitive-job-aa97d0bf8221ea99.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml b/releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml index 071de1e35a9d..4d9a08b9802e 100644 --- a/releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml +++ b/releasenotes/notes/serialize-primitive-job-aa97d0bf8221ea99.yaml @@ -1,5 +1,5 @@ --- features_primitives: - | - To make :class:`.PrimitiveJob` serializable, ``qiskit.primitives.containers.DataBin`` has been + To make :class:`.PrimitiveJob` serializable, :class:`.DataBin` has been updated to be pickleable. As a result, :class:`.PrimitiveResult` is now also pickleable. From d9243b95b314902f0a3ca5ef459d130329ce1bb0 Mon Sep 17 00:00:00 2001 From: Kevin Tian Date: Fri, 28 Mar 2025 15:24:54 -0400 Subject: [PATCH 13/13] Update test/python/primitives/test_primitive_job.py Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> --- test/python/primitives/test_primitive_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/primitives/test_primitive_job.py b/test/python/primitives/test_primitive_job.py index cc6aca767494..f6cb7479f057 100644 --- a/test/python/primitives/test_primitive_job.py +++ b/test/python/primitives/test_primitive_job.py @@ -45,7 +45,7 @@ def test_serialize(self, size): result2 = job2.result() self.assertEqual(result.metadata, result2.metadata) self.assertEqual(len(result), len(result2)) - for _, sampler_pub in enumerate(result): + for sampler_pub in result: self.assertEqual(sampler_pub.metadata, sampler_pub.metadata) self.assertEqual(sampler_pub.data.keys(), sampler_pub.data.keys()) np.testing.assert_allclose(sampler_pub.join_data().array, sampler_pub.join_data().array)