From 922baf02ca2bd183ef90e87f5171a1b5b8478f2f Mon Sep 17 00:00:00 2001 From: Itamar Goldman Date: Mon, 2 May 2022 18:38:48 +0300 Subject: [PATCH 1/7] Added warning for saving service class. --- .../database_service/db_experiment_data.py | 9 ++++++++- qiskit_experiments/library/__init__.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index c4649292dc..a05fec702e 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -1835,7 +1835,6 @@ def __json_encode__(self): for att in [ "_metadata", "_source", - "_service", "_backend", "_id", "_parent_id", @@ -1860,6 +1859,14 @@ def __json_encode__(self): # Handle non-serializable objects json_value["_jobs"] = self._safe_serialize_jobs() + service_value = getattr(self, "_service") + if service_value: + # the attribute self._service in charge of the connection and communication with the + # experiment db. It doesn't have meaning in the json format so there is no need to serialize + # it. + LOG.warning(f"Experiment was saved using {type(service_value)} service which cannot be " + f"automatically loaded. Please reset service attribute if required") + json_value["_service"] = None return json_value diff --git a/qiskit_experiments/library/__init__.py b/qiskit_experiments/library/__init__.py index 6edec0f2dd..0aa514355c 100644 --- a/qiskit_experiments/library/__init__.py +++ b/qiskit_experiments/library/__init__.py @@ -126,6 +126,7 @@ class instance to manage parameters and pulse schedules. from .characterization import ( T1, T2Ramsey, + T2Hahn, Tphi, QubitSpectroscopy, EFSpectroscopy, From 6c1736ee8082d7c27ae821b368adf61ac090549c Mon Sep 17 00:00:00 2001 From: Itamar Goldman Date: Mon, 2 May 2022 18:41:49 +0300 Subject: [PATCH 2/7] Update __init__.py --- qiskit_experiments/library/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit_experiments/library/__init__.py b/qiskit_experiments/library/__init__.py index 0aa514355c..6edec0f2dd 100644 --- a/qiskit_experiments/library/__init__.py +++ b/qiskit_experiments/library/__init__.py @@ -126,7 +126,6 @@ class instance to manage parameters and pulse schedules. from .characterization import ( T1, T2Ramsey, - T2Hahn, Tphi, QubitSpectroscopy, EFSpectroscopy, From 5d90b689581f07aab063b926599da4b84c9e80ed Mon Sep 17 00:00:00 2001 From: Itamar Goldman Date: Mon, 2 May 2022 18:59:35 +0300 Subject: [PATCH 3/7] lint + black --- qiskit_experiments/database_service/db_experiment_data.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index a05fec702e..c48cdaaed2 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -1864,8 +1864,11 @@ def __json_encode__(self): # the attribute self._service in charge of the connection and communication with the # experiment db. It doesn't have meaning in the json format so there is no need to serialize # it. - LOG.warning(f"Experiment was saved using {type(service_value)} service which cannot be " - f"automatically loaded. Please reset service attribute if required") + LOG.warning( + "Experiment was saved using %s service which cannot be automatically loaded. " + "Please reset service attribute if required", + str(type(service_value)), + ) json_value["_service"] = None return json_value From bae21a182e5ff920a087ea5753f12d522b194870 Mon Sep 17 00:00:00 2001 From: Itamar Goldman Date: Mon, 9 May 2022 12:29:40 +0300 Subject: [PATCH 4/7] Log message added for objects that aren't serialized --- .../database_service/db_experiment_data.py | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index c48cdaaed2..05345cea56 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -1835,7 +1835,6 @@ def __json_encode__(self): for att in [ "_metadata", "_source", - "_backend", "_id", "_parent_id", "_type", @@ -1859,17 +1858,15 @@ def __json_encode__(self): # Handle non-serializable objects json_value["_jobs"] = self._safe_serialize_jobs() - service_value = getattr(self, "_service") - if service_value: - # the attribute self._service in charge of the connection and communication with the - # experiment db. It doesn't have meaning in the json format so there is no need to serialize - # it. - LOG.warning( - "Experiment was saved using %s service which cannot be automatically loaded. " - "Please reset service attribute if required", - str(type(service_value)), - ) - json_value["_service"] = None + + # the attribute self._service in charge of the connection and communication with the + # experiment db. It doesn't have meaning in the json format so there is no need to serialize + # it. + for att in ["_service", "_backend"]: + json_value[att] = None + value = getattr(self, att) + if value is not None: + LOG.info("%s cannot be JSON serialized", str(type(value))) return json_value From 8365611c67990d024cc7f04d5d60d72038a60992 Mon Sep 17 00:00:00 2001 From: Itamar Goldman Date: Thu, 12 May 2022 14:17:33 +0300 Subject: [PATCH 5/7] Added tests for experiment data serialization and bug fix --- .../database_service/db_experiment_data.py | 16 +++++- test/test_qubit_spectroscopy.py | 53 +++++++++++++++++++ test/test_readout_angle.py | 19 +++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 05345cea56..64cc886b18 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -1820,6 +1820,20 @@ def _safe_serialize_figures(self): figures[name] = figure return figures + def _safe_serialize_data(self): + """Return serializable object for the experiment data""" + data_list = ThreadSafeList() + with self._data.lock: + for circuit_run_data_dict in self.data(): + data = {} + for data_key, data_value in circuit_run_data_dict.items(): + if data_key == "counts": + data[data_key] = {key: value.item() for key, value in data_value.items()} + else: + data[data_key] = data_value + data_list.append(data) + return data_list + def __json_encode__(self): if any(not fut.done() for fut in self._job_futures.values()): raise QiskitError( @@ -1841,7 +1855,6 @@ def __json_encode__(self): "_tags", "_share_level", "_notes", - "_data", "_analysis_results", "_analysis_callbacks", "_deleted_figures", @@ -1858,6 +1871,7 @@ def __json_encode__(self): # Handle non-serializable objects json_value["_jobs"] = self._safe_serialize_jobs() + json_value["_data"] = self._safe_serialize_data() # the attribute self._service in charge of the connection and communication with the # experiment db. It doesn't have meaning in the json format so there is no need to serialize diff --git a/test/test_qubit_spectroscopy.py b/test/test_qubit_spectroscopy.py index 64d80160fe..ca856b4b19 100644 --- a/test/test_qubit_spectroscopy.py +++ b/test/test_qubit_spectroscopy.py @@ -151,4 +151,57 @@ def test_experiment_config(self): def test_roundtrip_serializable(self): """Test round trip JSON serialization""" exp = QubitSpectroscopy(1, np.linspace(int(100e6), int(150e6), int(20e6))) + # Checking serialization of the experiment self.assertRoundTripSerializable(exp, self.json_equiv) + + def test_expdata_serialization(self): + """Test experiment data and analysis data JSON serialization""" + exp_helper = SpectroscopyHelper(line_width=2e6) + backend = MockIQBackend( + experiment_helper=exp_helper, + iq_cluster_centers=[((-1.0, -1.0), (1.0, 1.0))], + iq_cluster_width=[0.2], + ) + backend._configuration.basis_gates = ["x"] + backend._configuration.timing_constraints = {"granularity": 16} + + qubit = 1 + freq01 = backend.defaults().qubit_freq_est[qubit] + frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 21) + exp = QubitSpectroscopy(qubit, frequencies) + + exp.set_run_options(meas_level=MeasLevel.CLASSIFIED, shots=1024) + expdata = exp.run(backend).block_for_results() + self.assertExperimentDone(expdata) + + # Checking serialization of the experiment data + self.assertRoundTripSerializable(expdata, self.experiment_data_equiv) + + # Checking serialization of the analysis + self.assertRoundTripSerializable(expdata.analysis_results(1), self.analysis_result_equiv) + + def test_kerneled_expdata_serialization(self): + """Test experiment data and analysis data JSON serialization""" + exp_helper = SpectroscopyHelper(line_width=2e6) + backend = MockIQBackend( + experiment_helper=exp_helper, + iq_cluster_centers=[((-1.0, -1.0), (1.0, 1.0))], + iq_cluster_width=[0.2], + ) + backend._configuration.basis_gates = ["x"] + backend._configuration.timing_constraints = {"granularity": 16} + + qubit = 1 + freq01 = backend.defaults().qubit_freq_est[qubit] + frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 21) + exp = QubitSpectroscopy(qubit, frequencies) + + exp.set_run_options(meas_level=MeasLevel.KERNELED, shots=1024) + expdata = exp.run(backend).block_for_results() + self.assertExperimentDone(expdata) + + # Checking serialization of the experiment data + self.assertRoundTripSerializable(expdata, self.experiment_data_equiv) + + # Checking serialization of the analysis + self.assertRoundTripSerializable(expdata.analysis_results(1), self.analysis_result_equiv) diff --git a/test/test_readout_angle.py b/test/test_readout_angle.py index b600b3a7fe..1a1ef3f5df 100644 --- a/test/test_readout_angle.py +++ b/test/test_readout_angle.py @@ -16,6 +16,7 @@ from test.base import QiskitExperimentsTestCase import numpy as np +from qiskit.qobj.utils import MeasLevel from qiskit_experiments.library import ReadoutAngle from qiskit_experiments.test.mock_iq_backend import MockIQBackend from qiskit_experiments.test.mock_iq_helpers import MockIQReadoutAngleHelper @@ -48,3 +49,21 @@ def test_readout_angle_end2end(self): self.assertExperimentDone(expdata) res = expdata.analysis_results(0) self.assertAlmostEqual(res.value % (2 * np.pi), 15 * np.pi / 8, places=2) + + def test_kerneled_expdata_serialization(self): + """Test experiment data and analysis data JSON serialization""" + backend = MockIQBackend( + MockIQReadoutAngleHelper(), iq_cluster_centers=[((-3.0, 3.0), (5.0, 5.0))] + ) + + exp = ReadoutAngle(0) + + exp.set_run_options(meas_level=MeasLevel.KERNELED, shots=1024) + expdata = exp.run(backend).block_for_results() + self.assertExperimentDone(expdata) + + # Checking serialization of the experiment data + self.assertRoundTripSerializable(expdata, self.experiment_data_equiv) + + # Checking serialization of the analysis + self.assertRoundTripSerializable(expdata.analysis_results(0), self.analysis_result_equiv) From 0cc43da5ba059c3161bcfef4efc20afbe3ba6673 Mon Sep 17 00:00:00 2001 From: Itamar Goldman Date: Thu, 12 May 2022 21:02:37 +0300 Subject: [PATCH 6/7] Added tests for serialization --- .../database_service/db_experiment_data.py | 2 +- test/test_resonator_spectroscopy.py | 29 +++++++++++++++++++ test/test_t2hahn.py | 24 ++++++++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 64cc886b18..c66b3176d0 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -1828,7 +1828,7 @@ def _safe_serialize_data(self): data = {} for data_key, data_value in circuit_run_data_dict.items(): if data_key == "counts": - data[data_key] = {key: value.item() for key, value in data_value.items()} + data[data_key] = {key: int(value) for key, value in data_value.items()} else: data[data_key] = data_value data_list.append(data) diff --git a/test/test_resonator_spectroscopy.py b/test/test_resonator_spectroscopy.py index 7eb3895b53..9c99f923de 100644 --- a/test/test_resonator_spectroscopy.py +++ b/test/test_resonator_spectroscopy.py @@ -65,3 +65,32 @@ def test_roundtrip_serializable(self): """Test round trip JSON serialization""" exp = ResonatorSpectroscopy(1, frequencies=np.linspace(int(100e6), int(150e6), int(20e6))) self.assertRoundTripSerializable(exp, self.json_equiv) + + @data(-5e6, 0, 3e6) + def test_kerneled_expdata_serialization(self, freq_shift): + """Test experiment data and analysis data JSON serialization""" + qubit = 1 + backend = MockIQBackend( + experiment_helper=ResonatorSpectroscopyHelper( + gate_name="measure", freq_offset=freq_shift + ), + iq_cluster_centers=[((0.0, 0.0), (-1.0, 0.0))], + iq_cluster_width=[0.2], + ) + backend._configuration.timing_constraints = {"granularity": 16} + + res_freq = backend.defaults().meas_freq_est[qubit] + + frequencies = np.linspace(res_freq - 20e6, res_freq + 20e6, 51) + exp = ResonatorSpectroscopy(qubit, backend=backend, frequencies=frequencies) + + expdata = exp.run(backend).block_for_results() + self.assertExperimentDone(expdata) + + # since under _experiment in kwargs there is an argument of the backend which isn't serializable. + expdata._experiment = None + # Checking serialization of the experiment data + self.assertRoundTripSerializable(expdata, self.experiment_data_equiv) + + # Checking serialization of the analysis + self.assertRoundTripSerializable(expdata.analysis_results(1), self.analysis_result_equiv) diff --git a/test/test_t2hahn.py b/test/test_t2hahn.py index 9cc5d6188f..9a7319a392 100644 --- a/test/test_t2hahn.py +++ b/test/test_t2hahn.py @@ -166,9 +166,31 @@ def test_experiment_config(self): def test_roundtrip_serializable(self): """Test round trip JSON serialization""" - exp = T2Hahn(0, [1, 2, 3, 4, 5]) + + delays0 = list(range(1, 60, 2)) + + exp = T2Hahn(0, delays0) self.assertRoundTripSerializable(exp, self.json_equiv) + osc_freq = 0.08 + estimated_t2hahn = 30 + backend = T2HahnBackend( + t2hahn=[estimated_t2hahn], + frequency=[osc_freq], + initialization_error=[0.0], + readout0to1=[0.02], + readout1to0=[0.02], + ) + exp.analysis.set_options(p0={"amp": 0.5, "tau": estimated_t2hahn, "base": 0.5}, plot=False) + expdata = exp.run(backend=backend, shots=1000).block_for_results() + self.assertExperimentDone(expdata) + + # Checking serialization of the experiment data + self.assertRoundTripSerializable(expdata, self.experiment_data_equiv) + + # Checking serialization of the analysis + self.assertRoundTripSerializable(expdata.analysis_results(1), self.analysis_result_equiv) + def test_analysis_config(self): """ "Test converting analysis to and from config works""" analysis = T2HahnAnalysis() From 43ffb6b5a43e5c53c8c700eefbb017968bdda873 Mon Sep 17 00:00:00 2001 From: Itamar Goldman Date: Wed, 18 May 2022 09:54:29 +0300 Subject: [PATCH 7/7] Serialization of numpy.number in json file --- .../database_service/db_experiment_data.py | 16 +--------------- qiskit_experiments/framework/json.py | 2 ++ 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index ec07e5d655..ba472420bd 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -1816,20 +1816,6 @@ def _safe_serialize_figures(self): figures[name] = figure return figures - def _safe_serialize_data(self): - """Return serializable object for the experiment data""" - data_list = ThreadSafeList() - with self._data.lock: - for circuit_run_data_dict in self.data(): - data = {} - for data_key, data_value in circuit_run_data_dict.items(): - if data_key == "counts": - data[data_key] = {key: int(value) for key, value in data_value.items()} - else: - data[data_key] = data_value - data_list.append(data) - return data_list - def __json_encode__(self): if any(not fut.done() for fut in self._job_futures.values()): raise QiskitError( @@ -1851,6 +1837,7 @@ def __json_encode__(self): "_tags", "_share_level", "_notes", + "_data", "_analysis_results", "_analysis_callbacks", "_deleted_figures", @@ -1867,7 +1854,6 @@ def __json_encode__(self): # Handle non-serializable objects json_value["_jobs"] = self._safe_serialize_jobs() - json_value["_data"] = self._safe_serialize_data() # the attribute self._service in charge of the connection and communication with the # experiment db. It doesn't have meaning in the json format so there is no need to serialize diff --git a/qiskit_experiments/framework/json.py b/qiskit_experiments/framework/json.py index 032f401ff1..5d8e113b84 100644 --- a/qiskit_experiments/framework/json.py +++ b/qiskit_experiments/framework/json.py @@ -456,6 +456,8 @@ def default(self, obj: Any) -> Any: # pylint: disable=arguments-differ return {"__type__": "spmatrix", "__value__": value} if isinstance(obj, bytes): return _serialize_bytes(obj) + if isinstance(obj, np.number): + return obj.item() if dataclasses.is_dataclass(obj): return _serialize_object(obj, settings=dataclasses.asdict(obj)) if isinstance(obj, uncertainties.UFloat):