From 9d8793847cfba616ec1872bafca208dfe72ea863 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 22 Aug 2021 12:52:49 +0300 Subject: [PATCH 01/55] tags of composite experiments --- .../framework/composite/composite_experiment_data.py | 2 ++ qiskit_experiments/framework/experiment_data.py | 1 + 2 files changed, 3 insertions(+) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 8667e30e7c..59418a4e2b 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -50,6 +50,8 @@ def __init__( self._components = [ expr.__experiment_data__(expr, backend, job_ids) for expr in experiment._experiments ] + for comp in self._components: + comp.tags.extend(self.tags) def __str__(self): line = 51 * "-" diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 406be2a6fb..190f35ac1e 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -47,6 +47,7 @@ def __init__( job_ids=job_ids, metadata=experiment._metadata() if experiment else {}, ) + self.tags = [self.experiment_id] @property def experiment(self): From 563c07f86398d4621fa5d295e1e0429bac712de1 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 26 Aug 2021 18:24:04 +0300 Subject: [PATCH 02/55] CompositeExperimentData.save --- .../database_service/db_experiment_data.py | 2 +- .../framework/composite/composite_experiment_data.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 5ebf101555..e84647cee2 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -737,7 +737,7 @@ def save(self) -> None: ) return - self.save_metadata() + DbExperimentDataV1.save_metadata(self) for result in self._analysis_results.values(): result.save() diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 59418a4e2b..0bf4d816b3 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -105,3 +105,13 @@ def _add_single_data(self, data): else: sub_data["counts"] = data["counts"] self._components[index].add_data(sub_data) + + def save(self) -> None: + super().save() + for comp in self._components: + comp.save() + + def save_metadata(self) -> None: + super().save_metadata() + for comp in self._components: + comp.save_metadata() From 1674ebe35a53f0a71e6da9d9b13dc2a29ce72b56 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 26 Aug 2021 18:39:38 +0300 Subject: [PATCH 03/55] store component ids in composite metadata --- .../framework/composite/composite_experiment_data.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 0bf4d816b3..70b41e7c3c 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -50,6 +50,8 @@ def __init__( self._components = [ expr.__experiment_data__(expr, backend, job_ids) for expr in experiment._experiments ] + + self.metadata["component_ids"] = [comp.experiment_id for comp in self._components] for comp in self._components: comp.tags.extend(self.tags) From 558a65ffb21876e88f6ad503baa2043462b6b1d8 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 26 Aug 2021 18:59:31 +0300 Subject: [PATCH 04/55] initialize composite experiment data with components --- .../composite/composite_experiment_data.py | 27 ++++++++++++------- .../framework/experiment_data.py | 3 --- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 70b41e7c3c..714042d9ab 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -22,12 +22,7 @@ class CompositeExperimentData(ExperimentData): """Composite experiment data class""" - def __init__( - self, - experiment, - backend=None, - job_ids=None, - ): + def __init__(self, experiment, backend=None, job_ids=None, components=None): """Initialize experiment data. Args: @@ -35,11 +30,20 @@ def __init__( backend (Backend): Optional, Backend the experiment runs on. It can either be a :class:`~qiskit.providers.Backend` instance or just backend name. job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. + components (list[DbExperimentDataV1]): Optional, a list of already prepared experiment + data objects of the components. Applicable only if ``experiment`` is ``None``, + otherwise the components are created from the experiment's components. Raises: - ExperimentError: If an input argument is invalid. + ValueError: If both ``experiment`` and ``components`` are not ``None``. """ + if experiment is not None and components is not None: + raise ValueError( + "CompositeExperimentData initialization does not accept experiment " + "and component parameters that are both not None" + ) + super().__init__( experiment, backend=backend, @@ -47,9 +51,12 @@ def __init__( ) # Initialize sub experiments - self._components = [ - expr.__experiment_data__(expr, backend, job_ids) for expr in experiment._experiments - ] + if components: + self._components = components + else: + self._components = [ + expr.__experiment_data__(expr, backend, job_ids) for expr in experiment._experiments + ] self.metadata["component_ids"] = [comp.experiment_id for comp in self._components] for comp in self._components: diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 190f35ac1e..5e52a0de12 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -36,9 +36,6 @@ def __init__( experiment (BaseExperiment): Optional, experiment object that generated the data. backend (Backend): Optional, Backend the experiment runs on. job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. - - Raises: - ExperimentError: If an input argument is invalid. """ self._experiment = experiment super().__init__( From 49056e3a1027b66eaeb7b6926ac117eaa385474f Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 29 Aug 2021 11:49:32 +0300 Subject: [PATCH 05/55] CompositeExperimentData.load --- .../composite/composite_experiment_data.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 714042d9ab..198d366423 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -17,6 +17,7 @@ from qiskit.result import marginal_counts from qiskit.exceptions import QiskitError from qiskit_experiments.framework.experiment_data import ExperimentData +from qiskit_experiments.database_service import DbExperimentDataV1, DatabaseServiceV1 class CompositeExperimentData(ExperimentData): @@ -59,6 +60,7 @@ def __init__(self, experiment, backend=None, job_ids=None, components=None): ] self.metadata["component_ids"] = [comp.experiment_id for comp in self._components] + self.metadata["component_classes"] = [comp.__class__ for comp in self._components] for comp in self._components: comp.tags.extend(self.tags) @@ -124,3 +126,23 @@ def save_metadata(self) -> None: super().save_metadata() for comp in self._components: comp.save_metadata() + + @classmethod + def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "CompositeExperimentData": + expdata1 = DbExperimentDataV1.load(experiment_id, service) + components = [] + for comp_id, comp_class in zip( + expdata1.metadata["component_ids"], expdata1.metadata["component_classes"] + ): + cmd = comp_class + ".load(" + comp_id + ", " + service + ")" + # pylint: disable=eval-used + components.append(eval(cmd)) + + expdata2 = CompositeExperimentData( + experiment=None, + backend=expdata1.backend, + job_ids=expdata1.job_ids, + components=components, + ) + + return expdata2 From ea805557662d720093049b1c37b2627148de7345 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 2 Sep 2021 17:30:12 +0300 Subject: [PATCH 06/55] TestCompositeExperimentData --- test/test_composite.py | 62 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/test/test_composite.py b/test/test_composite.py index c579eb79ab..51582091a5 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -12,12 +12,23 @@ """Class to test composite experiments.""" +from typing import Optional, List + from test.fake_backend import FakeBackend from test.fake_experiment import FakeExperiment +from qiskit import QuantumCircuit from qiskit.test import QiskitTestCase +from qiskit.test.mock import FakeMelbourne +from qiskit.providers.backend import Backend -from qiskit_experiments.framework import ParallelExperiment, Options +from qiskit_experiments.framework import ( + ParallelExperiment, + Options, + CompositeExperimentData, + BaseExperiment, + BatchExperiment, +) class TestComposite(QiskitTestCase): @@ -29,7 +40,6 @@ def test_parallel_options(self): """ Test parallel experiments overriding sub-experiment run and transpile options. """ - # These options will all be overridden exp0 = FakeExperiment(0) exp0.set_transpile_options(optimization_level=1) @@ -52,3 +62,51 @@ def test_parallel_options(self): self.assertEqual(par_exp.analysis_options, Options()) par_exp.run(FakeBackend()) + + +class DummyExperiment(BaseExperiment): + """ + An experiment that does nothing, to fill in the experiment tree + """ + + def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: + return [] + + +class TestCompositeExperimentData(QiskitTestCase): + """ + Test operations on objects of CompositeExperimentData + """ + + def setUp(self): + super().setUp() + + self.backend = FakeMelbourne() + self.job_ids = [1, 2, 3, 4, 5] + + exp1 = DummyExperiment([0, 2]) + exp2 = DummyExperiment([1, 3]) + par_exp = ParallelExperiment([exp1, exp2]) + exp3 = DummyExperiment(4) + batch_exp = BatchExperiment([par_exp, exp3]) + + self.rootdata = CompositeExperimentData( + batch_exp, backend=self.backend, job_ids=self.job_ids + ) + + def check_attributes(self, expdata): + """ + Recursively traverse the tree to verify attributes + """ + self.assertEqual(expdata.backend, self.backend) + self.assertEqual(expdata.job_ids, self.job_ids) + + if isinstance(expdata, CompositeExperimentData): + for childdata in expdata.component_experiment_data(): + self.check_attributes(childdata) + + def test_composite_experiment_data_attributes(self): + """ + Verify correct attributes of parents and children + """ + self.check_attributes(self.rootdata) From aeab266dcfb2c80d63b63f75103188d0fec456bc Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 5 Sep 2021 09:47:34 +0300 Subject: [PATCH 07/55] temporarily remove handling of tags --- qiskit_experiments/database_service/database_service.py | 2 +- .../framework/composite/composite_experiment_data.py | 2 -- qiskit_experiments/framework/experiment_data.py | 1 - test/test_composite.py | 6 ++++++ 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/qiskit_experiments/database_service/database_service.py b/qiskit_experiments/database_service/database_service.py index 58d58d91a7..d2b649a4e1 100644 --- a/qiskit_experiments/database_service/database_service.py +++ b/qiskit_experiments/database_service/database_service.py @@ -78,7 +78,7 @@ def create_experiment( Experiment ID. Raises: - ExperimentEntryExists: If the experiment already exits. + ExperimentEntryExists: If the experiment already exists. """ pass diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 198d366423..648cfff44b 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -61,8 +61,6 @@ def __init__(self, experiment, backend=None, job_ids=None, components=None): self.metadata["component_ids"] = [comp.experiment_id for comp in self._components] self.metadata["component_classes"] = [comp.__class__ for comp in self._components] - for comp in self._components: - comp.tags.extend(self.tags) def __str__(self): line = 51 * "-" diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 5e52a0de12..2f40b6fc03 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -44,7 +44,6 @@ def __init__( job_ids=job_ids, metadata=experiment._metadata() if experiment else {}, ) - self.tags = [self.experiment_id] @property def experiment(self): diff --git a/test/test_composite.py b/test/test_composite.py index 51582091a5..1bddebd076 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -110,3 +110,9 @@ def test_composite_experiment_data_attributes(self): Verify correct attributes of parents and children """ self.check_attributes(self.rootdata) + + def test_composite_save_load(self): + """ + Verify that saving and loading restores the original composite experiment data object + """ + pass From 360f04f3bd8a869f1fb8958a75d59a4f74b7e6ef Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 5 Sep 2021 11:39:31 +0300 Subject: [PATCH 08/55] tags in composite experiments and their descendants --- .../composite/composite_experiment_data.py | 17 ++++++++++++++--- qiskit_experiments/framework/experiment_data.py | 10 ++++++++++ test/test_composite.py | 7 +++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 648cfff44b..a0e8af0bdc 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -23,7 +23,7 @@ class CompositeExperimentData(ExperimentData): """Composite experiment data class""" - def __init__(self, experiment, backend=None, job_ids=None, components=None): + def __init__(self, experiment, backend=None, job_ids=None, parent_id=None, root_id=None, components=None): """Initialize experiment data. Args: @@ -31,7 +31,9 @@ def __init__(self, experiment, backend=None, job_ids=None, components=None): backend (Backend): Optional, Backend the experiment runs on. It can either be a :class:`~qiskit.providers.Backend` instance or just backend name. job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. - components (list[DbExperimentDataV1]): Optional, a list of already prepared experiment + parent_id (str): Optional, ID of the parent experiment data in a composite experiment + root_id (str): Optional, ID of the root experiment data in a composite experiment + components (list[ExperimentData]): Optional, a list of already prepared experiment data objects of the components. Applicable only if ``experiment`` is ``None``, otherwise the components are created from the experiment's components. @@ -49,14 +51,23 @@ def __init__(self, experiment, backend=None, job_ids=None, components=None): experiment, backend=backend, job_ids=job_ids, + parent_id=parent_id, + root_id=root_id ) + # In a composite setting, an experiment is tagged with its direct parent and with the root. + # This is done in the ExperimentData constructir, except for the root experiment, + # for whom this is done here + root_id = root_id if root_id is not None else self.experiment_id + if root_id not in self.tags: + self.tags.append(root_id) + # Initialize sub experiments if components: self._components = components else: self._components = [ - expr.__experiment_data__(expr, backend, job_ids) for expr in experiment._experiments + expr.__experiment_data__(expr, backend, job_ids, self.experiment_id, root_id) for expr in experiment._experiments ] self.metadata["component_ids"] = [comp.experiment_id for comp in self._components] diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 2f40b6fc03..0a4e9dea59 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -29,6 +29,8 @@ def __init__( experiment=None, backend=None, job_ids=None, + parent_id=None, + root_id=None ): """Initialize experiment data. @@ -36,6 +38,8 @@ def __init__( experiment (BaseExperiment): Optional, experiment object that generated the data. backend (Backend): Optional, Backend the experiment runs on. job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. + parent_id (str): Optional, ID of the parent experiment data in the setting of a composite experiment + root_id (str): Optional, ID of the root experiment data in the setting of a composite experiment """ self._experiment = experiment super().__init__( @@ -45,6 +49,12 @@ def __init__( metadata=experiment._metadata() if experiment else {}, ) + # In a composite setting, an experiment is tagged with its direct parent and with the root + if root_id is not None: + self.tags = [parent_id] + if parent_id != root_id: + self.tags.append(root_id) + @property def experiment(self): """Return Experiment object. diff --git a/test/test_composite.py b/test/test_composite.py index 1bddebd076..e761bf9f59 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -101,9 +101,16 @@ def check_attributes(self, expdata): self.assertEqual(expdata.backend, self.backend) self.assertEqual(expdata.job_ids, self.job_ids) + # Experiments have to be tagged with their direct parents and the root + self.assertTrue(len(expdata.tags) == 1 or len(expdata.tags) == 2) + if len(expdata.tags) == 2: + self.assertNotEqual(expdata.tags[0], expdata.tags[1]) + self.assertTrue(self.rootdata.experiment_id in expdata.tags) + if isinstance(expdata, CompositeExperimentData): for childdata in expdata.component_experiment_data(): self.check_attributes(childdata) + self.assertTrue(expdata.experiment_id in childdata.tags) def test_composite_experiment_data_attributes(self): """ From 257eb704e195f3d035322d419f4c2c617f7e6fc3 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 5 Sep 2021 18:03:42 +0300 Subject: [PATCH 09/55] test save and load of composite experiment --- .../database_service/db_experiment_data.py | 11 + .../composite/composite_experiment_data.py | 34 ++- .../framework/experiment_data.py | 9 +- test/test_composite.py | 214 +++++++++++++++++- 4 files changed, 247 insertions(+), 21 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index e84647cee2..54940af888 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -1092,6 +1092,17 @@ def service(self) -> Optional[DatabaseServiceV1]: def service(self, service: DatabaseServiceV1) -> None: """Set the service to be used for storing experiment data. + Args: + service: Service to be used. + + Raises: + DbExperimentDataError: If an experiment service is already being used. + """ + self._set_service(service) + + def _set_service(self, service: DatabaseServiceV1) -> None: + """Set the service to be used for storing experiment data. + Args: service: Service to be used. diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index a0e8af0bdc..dd1f7e3f12 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -23,7 +23,9 @@ class CompositeExperimentData(ExperimentData): """Composite experiment data class""" - def __init__(self, experiment, backend=None, job_ids=None, parent_id=None, root_id=None, components=None): + def __init__( + self, experiment, backend=None, job_ids=None, parent_id=None, root_id=None, components=None + ): """Initialize experiment data. Args: @@ -48,11 +50,7 @@ def __init__(self, experiment, backend=None, job_ids=None, parent_id=None, root_ ) super().__init__( - experiment, - backend=backend, - job_ids=job_ids, - parent_id=parent_id, - root_id=root_id + experiment, backend=backend, job_ids=job_ids, parent_id=parent_id, root_id=root_id ) # In a composite setting, an experiment is tagged with its direct parent and with the root. @@ -67,11 +65,12 @@ def __init__(self, experiment, backend=None, job_ids=None, parent_id=None, root_ self._components = components else: self._components = [ - expr.__experiment_data__(expr, backend, job_ids, self.experiment_id, root_id) for expr in experiment._experiments + expr.__experiment_data__(expr, backend, job_ids, self.experiment_id, root_id) + for expr in experiment._experiments ] self.metadata["component_ids"] = [comp.experiment_id for comp in self._components] - self.metadata["component_classes"] = [comp.__class__ for comp in self._components] + self.metadata["component_classes"] = [comp.__class__.__name__ for comp in self._components] def __str__(self): line = 51 * "-" @@ -143,9 +142,9 @@ def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "CompositeExper for comp_id, comp_class in zip( expdata1.metadata["component_ids"], expdata1.metadata["component_classes"] ): - cmd = comp_class + ".load(" + comp_id + ", " + service + ")" - # pylint: disable=eval-used - components.append(eval(cmd)) + load_class = globals()[comp_class] + load_func = getattr(load_class, "load") + components.append(load_func(experiment_id, service)) expdata2 = CompositeExperimentData( experiment=None, @@ -155,3 +154,16 @@ def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "CompositeExper ) return expdata2 + + def _set_service(self, service: DatabaseServiceV1) -> None: + """Set the service to be used for storing experiment data. + + Args: + service: Service to be used. + + Raises: + DbExperimentDataError: If an experiment service is already being used. + """ + DbExperimentDataV1._set_service(self, service) + for comp in self._components: + comp._set_service(service) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 0a4e9dea59..ae33a1c560 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -24,14 +24,7 @@ class ExperimentData(DbExperimentDataV1): """Qiskit Experiments Data container class""" - def __init__( - self, - experiment=None, - backend=None, - job_ids=None, - parent_id=None, - root_id=None - ): + def __init__(self, experiment=None, backend=None, job_ids=None, parent_id=None, root_id=None): """Initialize experiment data. Args: diff --git a/test/test_composite.py b/test/test_composite.py index e761bf9f59..9bf04a9106 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -12,7 +12,9 @@ """Class to test composite experiments.""" -from typing import Optional, List +from typing import Optional, List, Dict, Type, Any, Union, Tuple +import json +import copy from test.fake_backend import FakeBackend from test.fake_experiment import FakeExperiment @@ -29,6 +31,8 @@ BaseExperiment, BatchExperiment, ) +from qiskit_experiments.database_service import DatabaseServiceV1 +from qiskit_experiments.database_service.device_component import DeviceComponent class TestComposite(QiskitTestCase): @@ -73,6 +77,189 @@ def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: return [] +class DummyService(DatabaseServiceV1): + db = {} + + def create_experiment( + self, + experiment_type: str, + backend_name: str, + metadata: Optional[Dict] = None, + experiment_id: Optional[str] = None, + job_ids: Optional[List[str]] = None, + tags: Optional[List[str]] = None, + notes: Optional[str] = None, + json_encoder: Type[json.JSONEncoder] = json.JSONEncoder, + **kwargs: Any, + ) -> str: + """Create a new experiment in the database. + + Args: + experiment_type: Experiment type. + backend_name: Name of the backend the experiment ran on. + metadata: Experiment metadata. + experiment_id: Experiment ID. It must be in the ``uuid4`` format. + One will be generated if not supplied. + job_ids: IDs of experiment jobs. + tags: Tags to be associated with the experiment. + notes: Freeform notes about the experiment. + json_encoder: Custom JSON encoder to use to encode the experiment. + kwargs: Additional keywords supported by the service provider. + + Returns: + Experiment ID. + """ + + DummyService.db[experiment_id] = { + "experiment_type": experiment_type, + "backend_name": backend_name, + "metadata": metadata, + "job_ids": job_ids, + "tags": tags, + "notes": notes, + "share_level": kwargs.get("share_level", None), + "figure_names": kwargs.get("figure_names", None), + } + return experiment_id + + def update_experiment( + self, + experiment_id: str, + metadata: Optional[Dict] = None, + job_ids: Optional[List[str]] = None, + notes: Optional[str] = None, + tags: Optional[List[str]] = None, + **kwargs: Any, + ) -> None: + """Update an existing experiment. + + Args: + experiment_id: Experiment ID. + metadata: Experiment metadata. + job_ids: IDs of experiment jobs. + notes: Freeform notes about the experiment. + tags: Tags to be associated with the experiment. + kwargs: Additional keywords supported by the service provider. + """ + + DummyService.db[experiment_id]["metadata"] = metadata + DummyService.db[experiment_id]["job_ids"] = job_ids + DummyService.db[experiment_od]["tags"] = tags + + def experiment( + self, experiment_id: str, json_decoder: Type[json.JSONDecoder] = json.JSONDecoder + ) -> Dict: + """Retrieve a previously stored experiment. + + Args: + experiment_id: Experiment ID. + json_decoder: Custom JSON decoder to use to decode the retrieved experiment. + + Returns: + A dictionary containing the retrieved experiment data. + """ + + db_entry = copy.deepcopy(DummyService.db[experiment_id]) + backend_name = db_entry.pop("backend_name") + backend = FakeMelbourne() + if backend_name == backend.name(): + db_entry["backend"] = backend + db_entry["experiment_id"] = experiment_id + + return db_entry + + def experiments( + self, + limit: Optional[int] = 10, + json_decoder: Type[json.JSONDecoder] = json.JSONDecoder, + device_components: Optional[Union[str, DeviceComponent]] = None, + experiment_type: Optional[str] = None, + backend_name: Optional[str] = None, + tags: Optional[List[str]] = None, + tags_operator: Optional[str] = "OR", + **filters: Any, + ) -> List[Dict]: + pass + + def delete_experiment(self, experiment_id: str) -> None: + pass + + def create_analysis_result( + self, + experiment_id: str, + result_data: Dict, + result_type: str, + device_components: Optional[Union[str, DeviceComponent]] = None, + tags: Optional[List[str]] = None, + quality: Optional[str] = None, + verified: bool = False, + result_id: Optional[str] = None, + json_encoder: Type[json.JSONEncoder] = json.JSONEncoder, + **kwargs: Any, + ) -> str: + pass + + def update_analysis_result( + self, + result_id: str, + result_data: Optional[Dict] = None, + tags: Optional[List[str]] = None, + quality: Optional[str] = None, + verified: bool = None, + **kwargs: Any, + ) -> None: + pass + + def analysis_result( + self, result_id: str, json_decoder: Type[json.JSONDecoder] = json.JSONDecoder + ) -> Dict: + pass + + def analysis_results( + self, + limit: Optional[int] = 10, + json_decoder: Type[json.JSONDecoder] = json.JSONDecoder, + device_components: Optional[Union[str, DeviceComponent]] = None, + experiment_id: Optional[str] = None, + result_type: Optional[str] = None, + backend_name: Optional[str] = None, + quality: Optional[str] = None, + verified: Optional[bool] = None, + tags: Optional[List[str]] = None, + tags_operator: Optional[str] = "OR", + **filters: Any, + ) -> List[Dict]: + pass + + def delete_analysis_result(self, result_id: str) -> None: + pass + + def create_figure( + self, experiment_id: str, figure: Union[str, bytes], figure_name: Optional[str] + ) -> Tuple[str, int]: + pass + + def update_figure( + self, experiment_id: str, figure: Union[str, bytes], figure_name: str + ) -> Tuple[str, int]: + pass + + def figure( + self, experiment_id: str, figure_name: str, file_name: Optional[str] = None + ) -> Union[int, bytes]: + pass + + def delete_figure( + self, + experiment_id: str, + figure_name: str, + ) -> None: + pass + + def preferences(self) -> Dict: + pass + + class TestCompositeExperimentData(QiskitTestCase): """ Test operations on objects of CompositeExperimentData @@ -112,6 +299,22 @@ def check_attributes(self, expdata): self.check_attributes(childdata) self.assertTrue(expdata.experiment_id in childdata.tags) + def check_if_equal(self, expdata1, expdata2): + """ + Recursively traverse the tree and checkequality of expdata1 and expdata2 + """ + self.assertEqual(expdata1.backend, expdata2.backend) + self.assertEqual(expdata1.job_ids, expdata2.job_ids) + self.assertEqual(expdata1.tags, expdata2.tags) + self.assertEqual(expdata1.experiment_type, expdata2.experiment_type) + self.assertEqual(expdata1.metadata, expdata2.metadata) + + if isinstance(expdata1, CompositeExperimentData): + for childdata1, childdata2 in zip( + expdata1.component_experiment_data(), expdata2.component_experiment_data() + ): + self.check_if_equal(childdata1, childdata2) + def test_composite_experiment_data_attributes(self): """ Verify correct attributes of parents and children @@ -122,4 +325,11 @@ def test_composite_save_load(self): """ Verify that saving and loading restores the original composite experiment data object """ - pass + + self.rootdata.service = DummyService() + self.rootdata.save() + loaded_data = CompositeExperimentData.load( + self.rootdata.experiment_id, self.rootdata.service + ) + + check_if_equal(loaded_data, self.rootdata) From 0006f1f55f4f5001281384a28eb10255bcbc27a7 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 5 Sep 2021 18:13:47 +0300 Subject: [PATCH 10/55] bug fixes --- .../framework/composite/composite_experiment_data.py | 3 ++- test/test_composite.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index dd1f7e3f12..384307b85e 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -144,7 +144,8 @@ def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "CompositeExper ): load_class = globals()[comp_class] load_func = getattr(load_class, "load") - components.append(load_func(experiment_id, service)) + loaded_comp = load_func(comp_id, service) + components.append(loaded_comp) expdata2 = CompositeExperimentData( experiment=None, diff --git a/test/test_composite.py b/test/test_composite.py index 9bf04a9106..d7f11d377a 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -303,7 +303,7 @@ def check_if_equal(self, expdata1, expdata2): """ Recursively traverse the tree and checkequality of expdata1 and expdata2 """ - self.assertEqual(expdata1.backend, expdata2.backend) + self.assertEqual(expdata1.backend.name(), expdata2.backend.name()) self.assertEqual(expdata1.job_ids, expdata2.job_ids) self.assertEqual(expdata1.tags, expdata2.tags) self.assertEqual(expdata1.experiment_type, expdata2.experiment_type) @@ -332,4 +332,4 @@ def test_composite_save_load(self): self.rootdata.experiment_id, self.rootdata.service ) - check_if_equal(loaded_data, self.rootdata) + self.check_if_equal(loaded_data, self.rootdata) From c7dc8577eaf9413a9b6270fad19c92811d5b1507 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 5 Sep 2021 18:25:40 +0300 Subject: [PATCH 11/55] bug fix --- .../framework/composite/composite_experiment_data.py | 6 ++++-- qiskit_experiments/framework/experiment_data.py | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 384307b85e..cfed3292b1 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -24,7 +24,7 @@ class CompositeExperimentData(ExperimentData): """Composite experiment data class""" def __init__( - self, experiment, backend=None, job_ids=None, parent_id=None, root_id=None, components=None + self, experiment, backend=None, experiment_id=None, job_ids=None, parent_id=None, root_id=None, components=None ): """Initialize experiment data. @@ -32,6 +32,7 @@ def __init__( experiment (CompositeExperiment): experiment object that generated the data. backend (Backend): Optional, Backend the experiment runs on. It can either be a :class:`~qiskit.providers.Backend` instance or just backend name. + experiment_id (str): Experiment ID. One will be generated if not supplied. job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. parent_id (str): Optional, ID of the parent experiment data in a composite experiment root_id (str): Optional, ID of the root experiment data in a composite experiment @@ -50,7 +51,7 @@ def __init__( ) super().__init__( - experiment, backend=backend, job_ids=job_ids, parent_id=parent_id, root_id=root_id + experiment, backend=backend, experiment_id=experiment_id, job_ids=job_ids, parent_id=parent_id, root_id=root_id ) # In a composite setting, an experiment is tagged with its direct parent and with the root. @@ -150,6 +151,7 @@ def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "CompositeExper expdata2 = CompositeExperimentData( experiment=None, backend=expdata1.backend, + experiment_id=expdata1.experiment_id, job_ids=expdata1.job_ids, components=components, ) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index ae33a1c560..38a43648e4 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -24,12 +24,13 @@ class ExperimentData(DbExperimentDataV1): """Qiskit Experiments Data container class""" - def __init__(self, experiment=None, backend=None, job_ids=None, parent_id=None, root_id=None): + def __init__(self, experiment=None, backend=None, experiment_id=None, job_ids=None, parent_id=None, root_id=None): """Initialize experiment data. Args: experiment (BaseExperiment): Optional, experiment object that generated the data. backend (Backend): Optional, Backend the experiment runs on. + experiment_id (str): Experiment ID. One will be generated if not supplied. job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. parent_id (str): Optional, ID of the parent experiment data in the setting of a composite experiment root_id (str): Optional, ID of the root experiment data in the setting of a composite experiment @@ -38,6 +39,7 @@ def __init__(self, experiment=None, backend=None, job_ids=None, parent_id=None, super().__init__( experiment_type=experiment.experiment_type if experiment else None, backend=backend, + experiment_id=experiment_id, job_ids=job_ids, metadata=experiment._metadata() if experiment else {}, ) From f39944461429a89197f21d8b18290fd929d26dba Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 5 Sep 2021 18:55:12 +0300 Subject: [PATCH 12/55] bug fixes --- .../framework/composite/composite_experiment_data.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index cfed3292b1..88cf33bd42 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -66,7 +66,7 @@ def __init__( self._components = components else: self._components = [ - expr.__experiment_data__(expr, backend, job_ids, self.experiment_id, root_id) + expr.__experiment_data__(expr, backend=backend, job_ids=job_ids, root_id=root_id) for expr in experiment._experiments ] @@ -155,6 +155,11 @@ def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "CompositeExper job_ids=expdata1.job_ids, components=components, ) + expdata2.metadata = expdata1.metadata + expdata2.tags = expdata1.tags + expdata2.share_level = expdata1.share_level + expdata2.figure_names = expdata1.figure_names + expdata2.notes = expdata1.notes return expdata2 From db70174af2d787b2cf21e3aae6d5da0b254cce0f Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 5 Sep 2021 22:07:18 +0300 Subject: [PATCH 13/55] composite load seems to work now --- .../composite/composite_experiment_data.py | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 88cf33bd42..adffc051f6 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -138,30 +138,20 @@ def save_metadata(self) -> None: @classmethod def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "CompositeExperimentData": - expdata1 = DbExperimentDataV1.load(experiment_id, service) + expdata = DbExperimentDataV1.load(experiment_id, service) components = [] for comp_id, comp_class in zip( - expdata1.metadata["component_ids"], expdata1.metadata["component_classes"] + expdata.metadata["component_ids"], expdata.metadata["component_classes"] ): load_class = globals()[comp_class] load_func = getattr(load_class, "load") loaded_comp = load_func(comp_id, service) components.append(loaded_comp) - expdata2 = CompositeExperimentData( - experiment=None, - backend=expdata1.backend, - experiment_id=expdata1.experiment_id, - job_ids=expdata1.job_ids, - components=components, - ) - expdata2.metadata = expdata1.metadata - expdata2.tags = expdata1.tags - expdata2.share_level = expdata1.share_level - expdata2.figure_names = expdata1.figure_names - expdata2.notes = expdata1.notes + expdata.__class__ = CompositeExperimentData + expdata._components = components - return expdata2 + return expdata def _set_service(self, service: DatabaseServiceV1) -> None: """Set the service to be used for storing experiment data. From 6a81e89c1ca065d9b5ff4c73d2a1a63155f53472 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 9 Sep 2021 08:15:07 +0300 Subject: [PATCH 14/55] code clean --- .../composite/composite_experiment_data.py | 28 +++++-------------- .../framework/experiment_data.py | 4 +-- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index adffc051f6..f7f4a19545 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -24,51 +24,37 @@ class CompositeExperimentData(ExperimentData): """Composite experiment data class""" def __init__( - self, experiment, backend=None, experiment_id=None, job_ids=None, parent_id=None, root_id=None, components=None - ): + self, experiment, backend=None, job_ids=None, parent_id=None, root_id=None): """Initialize experiment data. Args: experiment (CompositeExperiment): experiment object that generated the data. backend (Backend): Optional, Backend the experiment runs on. It can either be a :class:`~qiskit.providers.Backend` instance or just backend name. - experiment_id (str): Experiment ID. One will be generated if not supplied. job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. parent_id (str): Optional, ID of the parent experiment data in a composite experiment root_id (str): Optional, ID of the root experiment data in a composite experiment - components (list[ExperimentData]): Optional, a list of already prepared experiment - data objects of the components. Applicable only if ``experiment`` is ``None``, - otherwise the components are created from the experiment's components. Raises: ValueError: If both ``experiment`` and ``components`` are not ``None``. """ - if experiment is not None and components is not None: - raise ValueError( - "CompositeExperimentData initialization does not accept experiment " - "and component parameters that are both not None" - ) - super().__init__( - experiment, backend=backend, experiment_id=experiment_id, job_ids=job_ids, parent_id=parent_id, root_id=root_id + experiment, backend=backend, job_ids=job_ids, parent_id=parent_id, root_id=root_id ) # In a composite setting, an experiment is tagged with its direct parent and with the root. - # This is done in the ExperimentData constructir, except for the root experiment, + # This is done in the ExperimentData constructor, except for the root experiment, # for whom this is done here root_id = root_id if root_id is not None else self.experiment_id if root_id not in self.tags: self.tags.append(root_id) # Initialize sub experiments - if components: - self._components = components - else: - self._components = [ - expr.__experiment_data__(expr, backend=backend, job_ids=job_ids, root_id=root_id) - for expr in experiment._experiments - ] + self._components = [ + expr.__experiment_data__(expr, backend=backend, job_ids=job_ids, root_id=root_id) + for expr in experiment._experiments + ] self.metadata["component_ids"] = [comp.experiment_id for comp in self._components] self.metadata["component_classes"] = [comp.__class__.__name__ for comp in self._components] diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 38a43648e4..ae33a1c560 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -24,13 +24,12 @@ class ExperimentData(DbExperimentDataV1): """Qiskit Experiments Data container class""" - def __init__(self, experiment=None, backend=None, experiment_id=None, job_ids=None, parent_id=None, root_id=None): + def __init__(self, experiment=None, backend=None, job_ids=None, parent_id=None, root_id=None): """Initialize experiment data. Args: experiment (BaseExperiment): Optional, experiment object that generated the data. backend (Backend): Optional, Backend the experiment runs on. - experiment_id (str): Experiment ID. One will be generated if not supplied. job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. parent_id (str): Optional, ID of the parent experiment data in the setting of a composite experiment root_id (str): Optional, ID of the root experiment data in the setting of a composite experiment @@ -39,7 +38,6 @@ def __init__(self, experiment=None, backend=None, experiment_id=None, job_ids=No super().__init__( experiment_type=experiment.experiment_type if experiment else None, backend=backend, - experiment_id=experiment_id, job_ids=job_ids, metadata=experiment._metadata() if experiment else {}, ) From f251a562e7f6e1aa032169cf377abd9f0fb7fd0d Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 9 Sep 2021 08:24:08 +0300 Subject: [PATCH 15/55] bug fix --- .../framework/composite/composite_experiment_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index f7f4a19545..ee642971f0 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -52,7 +52,7 @@ def __init__( # Initialize sub experiments self._components = [ - expr.__experiment_data__(expr, backend=backend, job_ids=job_ids, root_id=root_id) + expr.__experiment_data__(expr, backend=backend, job_ids=job_ids, parent_id=self.experiment_id, root_id=root_id) for expr in experiment._experiments ] From 1c97c7f78a2a892980d0bf4c80ec9f06b6868914 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 9 Sep 2021 08:50:37 +0300 Subject: [PATCH 16/55] recursive share_level in composite --- .../database_service/db_experiment_data.py | 12 +++++++- .../composite/composite_experiment_data.py | 12 ++++++++ test/test_composite.py | 30 +++++++++++++++---- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 54940af888..69d2c16b09 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -1055,10 +1055,20 @@ def share_level(self, new_level: str) -> None: specified. For example, IBM Quantum experiment service allows "public", "hub", "group", "project", and "private". """ - self._share_level = new_level + self._set_share_level_without_save(new_level) if self.auto_save: self.save_metadata() + def _set_share_level_without_save(self, new_level: str) -> None: + """Set the experiment share level. Doesn't save, not even when auto_save is set to True. + + Args: + new_level: New experiment share level. Valid share levels are provider- + specified. For example, IBM Quantum experiment service allows + "public", "hub", "group", "project", and "private". + """ + self._share_level = new_level + @property def notes(self) -> str: """Return experiment notes. diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index ee642971f0..4efc7bcde7 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -151,3 +151,15 @@ def _set_service(self, service: DatabaseServiceV1) -> None: DbExperimentDataV1._set_service(self, service) for comp in self._components: comp._set_service(service) + + def _set_share_level_without_save(self, new_level: str) -> None: + """Set the experiment share level. Doesn't save, not even when auto_save is set to True. + + Args: + new_level: New experiment share level. Valid share levels are provider- + specified. For example, IBM Quantum experiment service allows + "public", "hub", "group", "project", and "private". + """ + DbExperimentDataV1._set_share_level_without_save(self, new_level) + for comp in self._components: + comp._set_share_level_without_save(new_level) diff --git a/test/test_composite.py b/test/test_composite.py index d7f11d377a..85b6e02312 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -78,7 +78,8 @@ def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: class DummyService(DatabaseServiceV1): - db = {} + def __init__(self): + self.db = {} def create_experiment( self, @@ -110,7 +111,7 @@ def create_experiment( Experiment ID. """ - DummyService.db[experiment_id] = { + self.db[experiment_id] = { "experiment_type": experiment_type, "backend_name": backend_name, "metadata": metadata, @@ -142,9 +143,9 @@ def update_experiment( kwargs: Additional keywords supported by the service provider. """ - DummyService.db[experiment_id]["metadata"] = metadata - DummyService.db[experiment_id]["job_ids"] = job_ids - DummyService.db[experiment_od]["tags"] = tags + self.db[experiment_id]["metadata"] = metadata + self.db[experiment_id]["job_ids"] = job_ids + self.db[experiment_od]["tags"] = tags def experiment( self, experiment_id: str, json_decoder: Type[json.JSONDecoder] = json.JSONDecoder @@ -159,7 +160,7 @@ def experiment( A dictionary containing the retrieved experiment data. """ - db_entry = copy.deepcopy(DummyService.db[experiment_id]) + db_entry = copy.deepcopy(self.db[experiment_id]) backend_name = db_entry.pop("backend_name") backend = FakeMelbourne() if backend_name == backend.name(): @@ -270,6 +271,7 @@ def setUp(self): self.backend = FakeMelbourne() self.job_ids = [1, 2, 3, 4, 5] + self.share_level = "hey" exp1 = DummyExperiment([0, 2]) exp2 = DummyExperiment([1, 3]) @@ -280,6 +282,7 @@ def setUp(self): self.rootdata = CompositeExperimentData( batch_exp, backend=self.backend, job_ids=self.job_ids ) + self.rootdata.share_level = self.share_level def check_attributes(self, expdata): """ @@ -287,6 +290,7 @@ def check_attributes(self, expdata): """ self.assertEqual(expdata.backend, self.backend) self.assertEqual(expdata.job_ids, self.job_ids) + self.assertEqual(expdata.share_level, self.share_level) # Experiments have to be tagged with their direct parents and the root self.assertTrue(len(expdata.tags) == 1 or len(expdata.tags) == 2) @@ -308,6 +312,7 @@ def check_if_equal(self, expdata1, expdata2): self.assertEqual(expdata1.tags, expdata2.tags) self.assertEqual(expdata1.experiment_type, expdata2.experiment_type) self.assertEqual(expdata1.metadata, expdata2.metadata) + self.assertEqual(expdata1.share_level, expdata2.share_level) if isinstance(expdata1, CompositeExperimentData): for childdata1, childdata2 in zip( @@ -333,3 +338,16 @@ def test_composite_save_load(self): ) self.check_if_equal(loaded_data, self.rootdata) + + def test_composite_save_metadata(self): + """ + Verify that saving metadata and loading restores the original composite experiment data object + """ + + self.rootdata.service = DummyService() + self.rootdata.save_metadata() + loaded_data = CompositeExperimentData.load( + self.rootdata.experiment_id, self.rootdata.service + ) + + self.check_if_equal(loaded_data, self.rootdata) From a7a78e59bf87255b066fb32ae0e721b3db42f4f2 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 9 Sep 2021 08:52:25 +0300 Subject: [PATCH 17/55] black --- .../framework/composite/composite_experiment_data.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 4efc7bcde7..cd81d89b2d 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -23,8 +23,7 @@ class CompositeExperimentData(ExperimentData): """Composite experiment data class""" - def __init__( - self, experiment, backend=None, job_ids=None, parent_id=None, root_id=None): + def __init__(self, experiment, backend=None, job_ids=None, parent_id=None, root_id=None): """Initialize experiment data. Args: @@ -52,7 +51,13 @@ def __init__( # Initialize sub experiments self._components = [ - expr.__experiment_data__(expr, backend=backend, job_ids=job_ids, parent_id=self.experiment_id, root_id=root_id) + expr.__experiment_data__( + expr, + backend=backend, + job_ids=job_ids, + parent_id=self.experiment_id, + root_id=root_id, + ) for expr in experiment._experiments ] From df3b246dda611be0935b4817ea72c5c49ecee66c Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 9 Sep 2021 09:42:29 +0300 Subject: [PATCH 18/55] lint --- .../framework/experiment_data.py | 6 ++- test/test_composite.py | 41 ++++++++++--------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index ae33a1c560..fc426ce8d7 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -31,8 +31,10 @@ def __init__(self, experiment=None, backend=None, job_ids=None, parent_id=None, experiment (BaseExperiment): Optional, experiment object that generated the data. backend (Backend): Optional, Backend the experiment runs on. job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. - parent_id (str): Optional, ID of the parent experiment data in the setting of a composite experiment - root_id (str): Optional, ID of the root experiment data in the setting of a composite experiment + parent_id (str): Optional, ID of the parent experiment data + in the setting of a composite experiment + root_id (str): Optional, ID of the root experiment data + in the setting of a composite experiment """ self._experiment = experiment super().__init__( diff --git a/test/test_composite.py b/test/test_composite.py index 85b6e02312..3b7c1f9bd3 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -34,6 +34,7 @@ from qiskit_experiments.database_service import DatabaseServiceV1 from qiskit_experiments.database_service.device_component import DeviceComponent +# pylint: disable=missing-raises-doc class TestComposite(QiskitTestCase): """ @@ -78,8 +79,12 @@ def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: class DummyService(DatabaseServiceV1): + """ + Extremely simple database for testing + """ + def __init__(self): - self.db = {} + self.database = {} def create_experiment( self, @@ -111,7 +116,7 @@ def create_experiment( Experiment ID. """ - self.db[experiment_id] = { + self.database[experiment_id] = { "experiment_type": experiment_type, "backend_name": backend_name, "metadata": metadata, @@ -142,10 +147,7 @@ def update_experiment( tags: Tags to be associated with the experiment. kwargs: Additional keywords supported by the service provider. """ - - self.db[experiment_id]["metadata"] = metadata - self.db[experiment_id]["job_ids"] = job_ids - self.db[experiment_od]["tags"] = tags + raise Exception("not implemented") def experiment( self, experiment_id: str, json_decoder: Type[json.JSONDecoder] = json.JSONDecoder @@ -160,7 +162,7 @@ def experiment( A dictionary containing the retrieved experiment data. """ - db_entry = copy.deepcopy(self.db[experiment_id]) + db_entry = copy.deepcopy(self.database[experiment_id]) backend_name = db_entry.pop("backend_name") backend = FakeMelbourne() if backend_name == backend.name(): @@ -180,10 +182,10 @@ def experiments( tags_operator: Optional[str] = "OR", **filters: Any, ) -> List[Dict]: - pass + raise Exception("not implemented") def delete_experiment(self, experiment_id: str) -> None: - pass + raise Exception("not implemented") def create_analysis_result( self, @@ -198,7 +200,7 @@ def create_analysis_result( json_encoder: Type[json.JSONEncoder] = json.JSONEncoder, **kwargs: Any, ) -> str: - pass + raise Exception("not implemented") def update_analysis_result( self, @@ -209,12 +211,12 @@ def update_analysis_result( verified: bool = None, **kwargs: Any, ) -> None: - pass + raise Exception("not implemented") def analysis_result( self, result_id: str, json_decoder: Type[json.JSONDecoder] = json.JSONDecoder ) -> Dict: - pass + raise Exception("not implemented") def analysis_results( self, @@ -230,35 +232,36 @@ def analysis_results( tags_operator: Optional[str] = "OR", **filters: Any, ) -> List[Dict]: - pass + raise Exception("not implemented") def delete_analysis_result(self, result_id: str) -> None: - pass + raise Exception("not implemented") def create_figure( self, experiment_id: str, figure: Union[str, bytes], figure_name: Optional[str] ) -> Tuple[str, int]: - pass + raise Exception("not implemented") def update_figure( self, experiment_id: str, figure: Union[str, bytes], figure_name: str ) -> Tuple[str, int]: - pass + raise Exception("not implemented") def figure( self, experiment_id: str, figure_name: str, file_name: Optional[str] = None ) -> Union[int, bytes]: - pass + raise Exception("not implemented") def delete_figure( self, experiment_id: str, figure_name: str, ) -> None: - pass + raise Exception("not implemented") + @property def preferences(self) -> Dict: - pass + raise Exception("not implemented") class TestCompositeExperimentData(QiskitTestCase): From 1ad83851e8ce2896837321de48a2b94b63e9dd0e Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 9 Sep 2021 09:45:49 +0300 Subject: [PATCH 19/55] black --- test/test_composite.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_composite.py b/test/test_composite.py index 3b7c1f9bd3..fd47d96e04 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -36,6 +36,7 @@ # pylint: disable=missing-raises-doc + class TestComposite(QiskitTestCase): """ Test composite experiment behavior. From 29ae28ed89a3e6cb5d64118dd1276432c5c2b326 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 9 Sep 2021 10:33:04 +0300 Subject: [PATCH 20/55] lint --- .../framework/composite/composite_experiment_data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index cd81d89b2d..9455368107 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -140,6 +140,7 @@ def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "CompositeExper components.append(loaded_comp) expdata.__class__ = CompositeExperimentData + expdata._experiment = None expdata._components = components return expdata From 92041e37a1165cc0f90d6dd98317a1742ffbb627 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 9 Sep 2021 10:43:04 +0300 Subject: [PATCH 21/55] lint --- qiskit_experiments/database_service/db_experiment_data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 69d2c16b09..e20784c7aa 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -903,6 +903,7 @@ def errors(self) -> str: self.status() # Collect new errors. return "\n".join(self._errors) + # pylint disable=no-value-for-parameter def _copy_metadata( self, new_instance: Optional["DbExperimentDataV1"] = None ) -> "DbExperimentDataV1": From 7f04300169280d2fc0b3f5dc8fb75f9bb7aa949e Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 9 Sep 2021 12:36:19 +0300 Subject: [PATCH 22/55] lint --- qiskit_experiments/database_service/db_experiment_data.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index e20784c7aa..09eec82f5f 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -903,7 +903,6 @@ def errors(self) -> str: self.status() # Collect new errors. return "\n".join(self._errors) - # pylint disable=no-value-for-parameter def _copy_metadata( self, new_instance: Optional["DbExperimentDataV1"] = None ) -> "DbExperimentDataV1": @@ -919,7 +918,7 @@ def _copy_metadata( and metadata but different ID. """ if new_instance is None: - new_instance = self.__class__() + new_instance = DbExperimentDataV1() new_instance._type = self.experiment_type new_instance._backend = self._backend From 93607ab3d50acb19dd2ed8c4ad93dbdc444b9b3c Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 9 Sep 2021 14:39:09 +0300 Subject: [PATCH 23/55] fixed comment --- .../framework/composite/composite_experiment_data.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 9455368107..000daea191 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -33,9 +33,6 @@ def __init__(self, experiment, backend=None, job_ids=None, parent_id=None, root_ job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. parent_id (str): Optional, ID of the parent experiment data in a composite experiment root_id (str): Optional, ID of the root experiment data in a composite experiment - - Raises: - ValueError: If both ``experiment`` and ``components`` are not ``None``. """ super().__init__( From 42b617a7ae5e937cb046e5ba33b7b2f61a0abeb2 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 19 Sep 2021 10:44:56 +0300 Subject: [PATCH 24/55] release notes --- releasenotes/notes/save-load-composite-cb882d855dd669be.yaml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 releasenotes/notes/save-load-composite-cb882d855dd669be.yaml diff --git a/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml b/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml new file mode 100644 index 0000000000..5204df21c8 --- /dev/null +++ b/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml @@ -0,0 +1,3 @@ +features: + - | + Composite experiments: saving a composite experiment saves also all its descendants; similarly, setting the share level and service of a composite experiment sets the share level and service of all its descendants; loading a composite experiment is now enabled; composite experiments and their descnedants are tagged with the root experiment and the direct parent experiment. From 8c9cfc3a778b109af78e906eef6333249f5dd64e Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 29 Sep 2021 11:47:33 +0300 Subject: [PATCH 25/55] update tags of composite --- .../framework/composite/composite_experiment_data.py | 4 ++-- qiskit_experiments/framework/experiment_data.py | 4 +--- test/test_composite.py | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 000daea191..c94575eb5f 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -43,8 +43,8 @@ def __init__(self, experiment, backend=None, job_ids=None, parent_id=None, root_ # This is done in the ExperimentData constructor, except for the root experiment, # for whom this is done here root_id = root_id if root_id is not None else self.experiment_id - if root_id not in self.tags: - self.tags.append(root_id) + if len(self.tags)==0: + self.tags = ["root exp id: " + root_id] # Initialize sub experiments self._components = [ diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index fc426ce8d7..1a56828c0c 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -46,9 +46,7 @@ def __init__(self, experiment=None, backend=None, job_ids=None, parent_id=None, # In a composite setting, an experiment is tagged with its direct parent and with the root if root_id is not None: - self.tags = [parent_id] - if parent_id != root_id: - self.tags.append(root_id) + self.tags = ["parent exp id: " + parent_id, "root exp id: " + root_id] @property def experiment(self): diff --git a/test/test_composite.py b/test/test_composite.py index fd47d96e04..881af986d7 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -300,12 +300,12 @@ def check_attributes(self, expdata): self.assertTrue(len(expdata.tags) == 1 or len(expdata.tags) == 2) if len(expdata.tags) == 2: self.assertNotEqual(expdata.tags[0], expdata.tags[1]) - self.assertTrue(self.rootdata.experiment_id in expdata.tags) + self.assertTrue("root exp id: " + self.rootdata.experiment_id in expdata.tags) if isinstance(expdata, CompositeExperimentData): for childdata in expdata.component_experiment_data(): self.check_attributes(childdata) - self.assertTrue(expdata.experiment_id in childdata.tags) + self.assertTrue("parent exp id: " + expdata.experiment_id in childdata.tags) def check_if_equal(self, expdata1, expdata2): """ From 830aa5a9920dea45a67ea4d095d994f6aaafaa14 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 29 Sep 2021 12:05:11 +0300 Subject: [PATCH 26/55] black --- .../framework/composite/composite_experiment_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index c94575eb5f..4c26173a3e 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -43,7 +43,7 @@ def __init__(self, experiment, backend=None, job_ids=None, parent_id=None, root_ # This is done in the ExperimentData constructor, except for the root experiment, # for whom this is done here root_id = root_id if root_id is not None else self.experiment_id - if len(self.tags)==0: + if len(self.tags) == 0: self.tags = ["root exp id: " + root_id] # Initialize sub experiments From 871e36117bb61d4be9921f1a7e5d19121533c57c Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 6 Oct 2021 13:03:14 +0300 Subject: [PATCH 27/55] added verbose flag to save --- .../database_service/db_experiment_data.py | 14 +++++++++----- .../composite/composite_experiment_data.py | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 1ccd50af93..5bbf7c95e9 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -717,9 +717,12 @@ def save_metadata(self) -> None: json_encoder=self._json_encoder, ) - def save(self) -> None: + def save(self, verbose: bool = True) -> None: """Save the experiment data to a database service. + Args: + verbose: if True then the method may print messages to the standard output + .. note:: This saves the experiment metadata, all analysis results, and all figures. Depending on the number of figures and analysis results this @@ -766,10 +769,11 @@ def save(self) -> None: self._service.delete_figure(experiment_id=self.experiment_id, figure_name=name) self._deleted_figures.remove(name) - print( - "You can view the experiment online at https://quantum-computing.ibm.com/experiments/" - + self.experiment_id - ) + if verbose: + print( + "You can view the experiment online at https://quantum-computing.ibm.com/experiments/" + + self.experiment_id + ) @classmethod def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "DbExperimentDataV1": diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 4c26173a3e..0b4727ee80 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -114,10 +114,10 @@ def _add_single_data(self, data): sub_data["counts"] = data["counts"] self._components[index].add_data(sub_data) - def save(self) -> None: - super().save() + def save(self, verbose=True) -> None: + super().save(verbose) for comp in self._components: - comp.save() + comp.save(verbose=False) def save_metadata(self) -> None: super().save_metadata() From ee81f544c6fed9bf8e935a9cf19c5502cf177646 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 6 Oct 2021 13:33:47 +0300 Subject: [PATCH 28/55] removed DummyExperiment --- test/fake_experiment.py | 5 ++--- test/test_composite.py | 22 +++++----------------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/test/fake_experiment.py b/test/fake_experiment.py index 5c6b1fa46f..fc3421f89a 100644 --- a/test/fake_experiment.py +++ b/test/fake_experiment.py @@ -33,10 +33,9 @@ class FakeExperiment(BaseExperiment): def _default_experiment_options(cls) -> Options: return Options(dummyoption=None) - def __init__(self, qubit=0): + def __init__(self, qubits=0): """Initialise the fake experiment.""" - self._type = None - super().__init__((qubit,), "fake_test_experiment") + super().__init__(qubits) def circuits(self, backend=None): """Fake circuits.""" diff --git a/test/test_composite.py b/test/test_composite.py index 881af986d7..b2d026ca73 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -19,16 +19,13 @@ from test.fake_backend import FakeBackend from test.fake_experiment import FakeExperiment -from qiskit import QuantumCircuit from qiskit.test import QiskitTestCase from qiskit.test.mock import FakeMelbourne -from qiskit.providers.backend import Backend from qiskit_experiments.framework import ( ParallelExperiment, Options, CompositeExperimentData, - BaseExperiment, BatchExperiment, ) from qiskit_experiments.database_service import DatabaseServiceV1 @@ -47,9 +44,9 @@ def test_parallel_options(self): Test parallel experiments overriding sub-experiment run and transpile options. """ # These options will all be overridden - exp0 = FakeExperiment(0) + exp0 = FakeExperiment([0]) exp0.set_transpile_options(optimization_level=1) - exp2 = FakeExperiment(2) + exp2 = FakeExperiment([2]) exp2.set_experiment_options(dummyoption="test") exp2.set_run_options(shots=2000) exp2.set_transpile_options(optimization_level=1) @@ -70,15 +67,6 @@ def test_parallel_options(self): par_exp.run(FakeBackend()) -class DummyExperiment(BaseExperiment): - """ - An experiment that does nothing, to fill in the experiment tree - """ - - def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]: - return [] - - class DummyService(DatabaseServiceV1): """ Extremely simple database for testing @@ -277,10 +265,10 @@ def setUp(self): self.job_ids = [1, 2, 3, 4, 5] self.share_level = "hey" - exp1 = DummyExperiment([0, 2]) - exp2 = DummyExperiment([1, 3]) + exp1 = FakeExperiment([0, 2]) + exp2 = FakeExperiment([1, 3]) par_exp = ParallelExperiment([exp1, exp2]) - exp3 = DummyExperiment(4) + exp3 = FakeExperiment(4) batch_exp = BatchExperiment([par_exp, exp3]) self.rootdata = CompositeExperimentData( From c84fdf017a68dde29249d5f9d9f2881c93870f8e Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 7 Oct 2021 13:11:01 +0300 Subject: [PATCH 29/55] reworked root id, parent id, and tags --- .../composite/composite_experiment_data.py | 17 ++++++++--------- qiskit_experiments/framework/experiment_data.py | 8 +------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 0b4727ee80..7ae97aed39 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -23,7 +23,7 @@ class CompositeExperimentData(ExperimentData): """Composite experiment data class""" - def __init__(self, experiment, backend=None, job_ids=None, parent_id=None, root_id=None): + def __init__(self, experiment, backend=None, job_ids=None, root_id=None): """Initialize experiment data. Args: @@ -31,19 +31,15 @@ def __init__(self, experiment, backend=None, job_ids=None, parent_id=None, root_ backend (Backend): Optional, Backend the experiment runs on. It can either be a :class:`~qiskit.providers.Backend` instance or just backend name. job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. - parent_id (str): Optional, ID of the parent experiment data in a composite experiment root_id (str): Optional, ID of the root experiment data in a composite experiment """ super().__init__( - experiment, backend=backend, job_ids=job_ids, parent_id=parent_id, root_id=root_id + experiment, backend=backend, job_ids=job_ids, root_id=root_id ) - # In a composite setting, an experiment is tagged with its direct parent and with the root. - # This is done in the ExperimentData constructor, except for the root experiment, - # for whom this is done here - root_id = root_id if root_id is not None else self.experiment_id - if len(self.tags) == 0: + if root_id is None: + root_id = self.experiment_id self.tags = ["root exp id: " + root_id] # Initialize sub experiments @@ -52,7 +48,6 @@ def __init__(self, experiment, backend=None, job_ids=None, parent_id=None, root_ expr, backend=backend, job_ids=job_ids, - parent_id=self.experiment_id, root_id=root_id, ) for expr in experiment._experiments @@ -61,6 +56,10 @@ def __init__(self, experiment, backend=None, job_ids=None, parent_id=None, root_ self.metadata["component_ids"] = [comp.experiment_id for comp in self._components] self.metadata["component_classes"] = [comp.__class__.__name__ for comp in self._components] + # In a composite setting, an experiment is tagged with its direct parent and with the root. + for comp in self._components: + comp.tags = ["root exp id: " + root_id, "parent exp id: " + self.experiment_id] + def __str__(self): line = 51 * "-" n_res = len(self._analysis_results) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 1a56828c0c..991fff1f36 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -24,15 +24,13 @@ class ExperimentData(DbExperimentDataV1): """Qiskit Experiments Data container class""" - def __init__(self, experiment=None, backend=None, job_ids=None, parent_id=None, root_id=None): + def __init__(self, experiment=None, backend=None, job_ids=None, root_id=None): """Initialize experiment data. Args: experiment (BaseExperiment): Optional, experiment object that generated the data. backend (Backend): Optional, Backend the experiment runs on. job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. - parent_id (str): Optional, ID of the parent experiment data - in the setting of a composite experiment root_id (str): Optional, ID of the root experiment data in the setting of a composite experiment """ @@ -44,10 +42,6 @@ def __init__(self, experiment=None, backend=None, job_ids=None, parent_id=None, metadata=experiment._metadata() if experiment else {}, ) - # In a composite setting, an experiment is tagged with its direct parent and with the root - if root_id is not None: - self.tags = ["parent exp id: " + parent_id, "root exp id: " + root_id] - @property def experiment(self): """Return Experiment object. From 3d303082576a34480998b5c2f1d1667aae8f0cfd Mon Sep 17 00:00:00 2001 From: Kevin Tian Date: Tue, 28 Sep 2021 22:57:33 -0400 Subject: [PATCH 30/55] add parent_id support --- qiskit_experiments/database_service/database_service.py | 6 ++++++ qiskit_experiments/database_service/db_experiment_data.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/qiskit_experiments/database_service/database_service.py b/qiskit_experiments/database_service/database_service.py index d2b649a4e1..a0a8c11d31 100644 --- a/qiskit_experiments/database_service/database_service.py +++ b/qiskit_experiments/database_service/database_service.py @@ -54,6 +54,7 @@ def create_experiment( backend_name: str, metadata: Optional[Dict] = None, experiment_id: Optional[str] = None, + parent_id: Optional[str] = None, job_ids: Optional[List[str]] = None, tags: Optional[List[str]] = None, notes: Optional[str] = None, @@ -68,6 +69,9 @@ def create_experiment( metadata: Experiment metadata. experiment_id: Experiment ID. It must be in the ``uuid4`` format. One will be generated if not supplied. + parent_id: The experiment ID of the parent experiment. + The parent experiment must exist, must be on the same backend as the child, + and an experiment cannot be its own parent. job_ids: IDs of experiment jobs. tags: Tags to be associated with the experiment. notes: Freeform notes about the experiment. @@ -134,6 +138,7 @@ def experiments( experiment_type: Optional[str] = None, backend_name: Optional[str] = None, tags: Optional[List[str]] = None, + parent_id: Optional[str] = None, tags_operator: Optional[str] = "OR", **filters: Any, ) -> List[Dict]: @@ -148,6 +153,7 @@ def experiments( backend_name: Backend name used for filtering. tags: Filter by tags assigned to experiments. This can be used with `tags_operator` for granular filtering. + parent_id: Filter by parent experiment ID. tags_operator: Logical operator to use when filtering by tags. Valid values are "AND" and "OR": diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 5bbf7c95e9..ca4729e264 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -99,6 +99,7 @@ def __init__( experiment_type: Optional[str] = "Unknown", backend: Optional[Union[Backend, BaseBackend]] = None, experiment_id: Optional[str] = None, + parent_id: Optional[str] = None, tags: Optional[List[str]] = None, job_ids: Optional[List[str]] = None, share_level: Optional[str] = None, @@ -113,6 +114,7 @@ def __init__( experiment_type: Experiment type. backend: Backend the experiment runs on. experiment_id: Experiment ID. One will be generated if not supplied. + parent_id: The experiment ID of the parent experiment. tags: Tags to be associated with the experiment. job_ids: IDs of jobs submitted for the experiment. share_level: Whether this experiment can be shared with others. This @@ -140,6 +142,7 @@ def __init__( self._set_service_from_backend(backend) self._id = experiment_id or str(uuid.uuid4()) + self._parent_id = parent_id self._type = experiment_type self._tags = tags or [] self._share_level = share_level @@ -796,6 +799,7 @@ def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "DbExperimentDa experiment_type=service_data.pop("experiment_type"), backend=service_data.pop("backend"), experiment_id=service_data.pop("experiment_id"), + parent_id=service_data.pop("parent_id"), tags=service_data.pop("tags"), job_ids=service_data.pop("job_ids"), share_level=service_data.pop("share_level"), @@ -1166,6 +1170,8 @@ def source(self) -> Dict: def __repr__(self): out = f"{type(self).__name__}({self.experiment_type}" out += f", {self.experiment_id}" + if self._parent_id: + out += f", parent_id={self._parent_id}" if self._tags: out += f", tags={self._tags}" if self.job_ids: From 5472cf033d6240eda8ded4f10c1c6693ee756109 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 10 Oct 2021 11:11:29 +0300 Subject: [PATCH 31/55] added property DbExperimentDataV1.parent_id --- .../database_service/db_experiment_data.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index ca4729e264..b45adff01e 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -1015,6 +1015,15 @@ def experiment_id(self) -> str: """ return self._id + @property + def parent_id(self) -> str: + """Return parent ID + + Returns: + Parent ID. + """ + return self._parent_id + @property def job_ids(self) -> List[str]: """Return experiment job IDs. From bee575cd82f9df8952be11e0a3427735106d68e1 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 10 Oct 2021 12:26:33 +0300 Subject: [PATCH 32/55] remove root id and tags hack --- .../database_service/db_experiment_data.py | 2 ++ .../composite/composite_experiment_data.py | 17 +++++------------ qiskit_experiments/framework/experiment_data.py | 7 ++++--- test/test_composite.py | 15 ++++++++------- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index b45adff01e..e06e1c14f5 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -710,6 +710,8 @@ def save_metadata(self) -> None: new_data = {"experiment_type": self._type, "backend_name": self._backend.name()} if self.share_level: update_data["share_level"] = self.share_level + if self.parent_id: + update_data["parent_id"] = self.parent_id self._created_in_db, _ = save_data( is_new=(not self._created_in_db), diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 7ae97aed39..ddec18b2db 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -23,32 +23,29 @@ class CompositeExperimentData(ExperimentData): """Composite experiment data class""" - def __init__(self, experiment, backend=None, job_ids=None, root_id=None): + def __init__(self, experiment, backend=None, parent_id=None, job_ids=None): """Initialize experiment data. Args: experiment (CompositeExperiment): experiment object that generated the data. backend (Backend): Optional, Backend the experiment runs on. It can either be a :class:`~qiskit.providers.Backend` instance or just backend name. + parent_id (str): Optional, ID of the parent experiment data + in the setting of a composite experiment. job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. - root_id (str): Optional, ID of the root experiment data in a composite experiment """ super().__init__( - experiment, backend=backend, job_ids=job_ids, root_id=root_id + experiment, backend=backend, parent_id=parent_id, job_ids=job_ids ) - if root_id is None: - root_id = self.experiment_id - self.tags = ["root exp id: " + root_id] - # Initialize sub experiments self._components = [ expr.__experiment_data__( expr, backend=backend, + parent_id=self.experiment_id, job_ids=job_ids, - root_id=root_id, ) for expr in experiment._experiments ] @@ -56,10 +53,6 @@ def __init__(self, experiment, backend=None, job_ids=None, root_id=None): self.metadata["component_ids"] = [comp.experiment_id for comp in self._components] self.metadata["component_classes"] = [comp.__class__.__name__ for comp in self._components] - # In a composite setting, an experiment is tagged with its direct parent and with the root. - for comp in self._components: - comp.tags = ["root exp id: " + root_id, "parent exp id: " + self.experiment_id] - def __str__(self): line = 51 * "-" n_res = len(self._analysis_results) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 991fff1f36..43e8a1261a 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -24,20 +24,21 @@ class ExperimentData(DbExperimentDataV1): """Qiskit Experiments Data container class""" - def __init__(self, experiment=None, backend=None, job_ids=None, root_id=None): + def __init__(self, experiment=None, backend=None, parent_id=None, job_ids=None): """Initialize experiment data. Args: experiment (BaseExperiment): Optional, experiment object that generated the data. backend (Backend): Optional, Backend the experiment runs on. - job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. - root_id (str): Optional, ID of the root experiment data + parent_id (str): Optional, ID of the parent experiment data in the setting of a composite experiment + job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. """ self._experiment = experiment super().__init__( experiment_type=experiment.experiment_type if experiment else None, backend=backend, + parent_id=parent_id, job_ids=job_ids, metadata=experiment._metadata() if experiment else {}, ) diff --git a/test/test_composite.py b/test/test_composite.py index b2d026ca73..93836a1e54 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -81,6 +81,7 @@ def create_experiment( backend_name: str, metadata: Optional[Dict] = None, experiment_id: Optional[str] = None, + parent_id: Optional[str] = None, job_ids: Optional[List[str]] = None, tags: Optional[List[str]] = None, notes: Optional[str] = None, @@ -95,6 +96,9 @@ def create_experiment( metadata: Experiment metadata. experiment_id: Experiment ID. It must be in the ``uuid4`` format. One will be generated if not supplied. + parent_id: The experiment ID of the parent experiment. + The parent experiment must exist, must be on the same backend as the child, + and an experiment cannot be its own parent. job_ids: IDs of experiment jobs. tags: Tags to be associated with the experiment. notes: Freeform notes about the experiment. @@ -107,6 +111,7 @@ def create_experiment( self.database[experiment_id] = { "experiment_type": experiment_type, + "parent_id": parent_id, "backend_name": backend_name, "metadata": metadata, "job_ids": job_ids, @@ -168,6 +173,7 @@ def experiments( experiment_type: Optional[str] = None, backend_name: Optional[str] = None, tags: Optional[List[str]] = None, + parent_id: Optional[str] = None, tags_operator: Optional[str] = "OR", **filters: Any, ) -> List[Dict]: @@ -284,21 +290,16 @@ def check_attributes(self, expdata): self.assertEqual(expdata.job_ids, self.job_ids) self.assertEqual(expdata.share_level, self.share_level) - # Experiments have to be tagged with their direct parents and the root - self.assertTrue(len(expdata.tags) == 1 or len(expdata.tags) == 2) - if len(expdata.tags) == 2: - self.assertNotEqual(expdata.tags[0], expdata.tags[1]) - self.assertTrue("root exp id: " + self.rootdata.experiment_id in expdata.tags) - if isinstance(expdata, CompositeExperimentData): for childdata in expdata.component_experiment_data(): self.check_attributes(childdata) - self.assertTrue("parent exp id: " + expdata.experiment_id in childdata.tags) + self.assertEqual(childdata.parent_id, expdata.experiment_id) def check_if_equal(self, expdata1, expdata2): """ Recursively traverse the tree and checkequality of expdata1 and expdata2 """ + self.assertEqual(expdata1.parent_id, expdata2.parent_id) self.assertEqual(expdata1.backend.name(), expdata2.backend.name()) self.assertEqual(expdata1.job_ids, expdata2.job_ids) self.assertEqual(expdata1.tags, expdata2.tags) From f7d535b742b3d0d19134766240de8bb88fd0c7a8 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 10 Oct 2021 12:40:56 +0300 Subject: [PATCH 33/55] reverted a recent change in DbExperimentDataV1._copy_metadata --- qiskit_experiments/database_service/db_experiment_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index e06e1c14f5..7dd45e722e 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -933,7 +933,7 @@ def _copy_metadata( and metadata but different ID. """ if new_instance is None: - new_instance = DbExperimentDataV1() + new_instance = self.__class__() new_instance._type = self.experiment_type new_instance._backend = self._backend From a0b1c55e0179f73deb9ac3096613914ff3ad5d47 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 10 Oct 2021 13:21:09 +0300 Subject: [PATCH 34/55] testing component ids and classes --- test/test_composite.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/test_composite.py b/test/test_composite.py index 93836a1e54..3a80f7147d 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -291,9 +291,14 @@ def check_attributes(self, expdata): self.assertEqual(expdata.share_level, self.share_level) if isinstance(expdata, CompositeExperimentData): - for childdata in expdata.component_experiment_data(): + components = expdata.component_experiment_data() + comp_ids = expdata.metadata["component_ids"] + comp_classes = expdata.metadata["component_classes"] + for childdata, comp_id, comp_class in zip(components, comp_ids, comp_classes): self.check_attributes(childdata) self.assertEqual(childdata.parent_id, expdata.experiment_id) + self.assertEqual(childdata.experiment_id, comp_id) + self.assertEqual(childdata.__class__.__name__, comp_class) def check_if_equal(self, expdata1, expdata2): """ From 4148f148bbcfc0e63fd3742d0882f6ae55d92196 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 10 Oct 2021 16:06:14 +0300 Subject: [PATCH 35/55] CompositeExperimentData._copy_metadata --- .../database_service/db_experiment_data.py | 3 +- .../composite/composite_experiment_data.py | 21 +++++++++++ .../framework/experiment_data.py | 2 +- test/test_composite.py | 36 +++++++++++++++---- 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 7dd45e722e..52d8f7e5cc 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -940,7 +940,7 @@ def _copy_metadata( new_instance._tags = self._tags new_instance._jobs = self._jobs.copy_object() new_instance._share_level = self._share_level - new_instance._metadata = self._metadata + new_instance._metadata = copy.deepcopy(self._metadata) new_instance._notes = self._notes new_instance._auto_save = self._auto_save new_instance._service = self._service @@ -965,6 +965,7 @@ def _copy_metadata( timeout=orig_kwargs["timeout"], **extra_kwargs, ) + return new_instance @property diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index ddec18b2db..0114b3742d 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -158,3 +158,24 @@ def _set_share_level_without_save(self, new_level: str) -> None: DbExperimentDataV1._set_share_level_without_save(self, new_level) for comp in self._components: comp._set_share_level_without_save(new_level) + + def _copy_metadata(self, new_instance: Optional["CompositeExperimentData"] = None) -> "CompositeExperimentData": + """Make a copy of the composite experiment metadata. + + Note: + This method only copies experiment data and metadata, not its + figures nor analysis results. The copy also contains a different + experiment ID. + + Returns: + A copy of the ``CompositeExperimentData`` object with the same data + and metadata but different ID. + """ + new_instance = super()._copy_metadata(new_instance) + + for original_comp, new_comp in zip(self.component_experiment_data(), new_instance.component_experiment_data()): + original_comp._copy_metadata(new_comp) + + new_instance.metadata["component_ids"] = [comp.experiment_id for comp in new_instance.component_experiment_data()] + return new_instance + diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 43e8a1261a..0742e27cec 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -75,7 +75,7 @@ def _copy_metadata(self, new_instance: Optional["ExperimentData"] = None) -> "Ex and metadata but different ID. """ if new_instance is None: - new_instance = ExperimentData( + new_instance = self.__class__( experiment=self.experiment, backend=self.backend, job_ids=self.job_ids ) return super()._copy_metadata(new_instance) diff --git a/test/test_composite.py b/test/test_composite.py index 3a80f7147d..b5aa4ec67f 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -280,6 +280,7 @@ def setUp(self): self.rootdata = CompositeExperimentData( batch_exp, backend=self.backend, job_ids=self.job_ids ) + self.rootdata.share_level = self.share_level def check_attributes(self, expdata): @@ -300,29 +301,45 @@ def check_attributes(self, expdata): self.assertEqual(childdata.experiment_id, comp_id) self.assertEqual(childdata.__class__.__name__, comp_class) - def check_if_equal(self, expdata1, expdata2): + def check_if_equal(self, expdata1, expdata2, is_a_copy): """ - Recursively traverse the tree and checkequality of expdata1 and expdata2 + Recursively traverse the tree and check equality of expdata1 and expdata2 """ - self.assertEqual(expdata1.parent_id, expdata2.parent_id) self.assertEqual(expdata1.backend.name(), expdata2.backend.name()) self.assertEqual(expdata1.job_ids, expdata2.job_ids) self.assertEqual(expdata1.tags, expdata2.tags) self.assertEqual(expdata1.experiment_type, expdata2.experiment_type) - self.assertEqual(expdata1.metadata, expdata2.metadata) self.assertEqual(expdata1.share_level, expdata2.share_level) + metadata1 = copy.copy(expdata1.metadata) + metadata2 = copy.copy(expdata2.metadata) + if is_a_copy: + comp_ids1 = metadata1.pop("component_ids", None) + comp_ids2 = metadata2.pop("component_ids", None) + if comp_ids1 is None: + self.assertEqual(comp_ids2, None) + else: + self.assertNotEqual(comp_ids1, comp_ids2) + if expdata1.parent_id is None: + self.assertEqual(expdata2.parent_id, None) + else: + self.assertNotEqual(expdata1.parent_id, expdata2.parent_id) + else: + self.assertEqual(expdata1.parent_id, expdata2.parent_id) + self.assertEqual(metadata1, metadata2) + if isinstance(expdata1, CompositeExperimentData): for childdata1, childdata2 in zip( expdata1.component_experiment_data(), expdata2.component_experiment_data() ): - self.check_if_equal(childdata1, childdata2) + self.check_if_equal(childdata1, childdata2, is_a_copy) def test_composite_experiment_data_attributes(self): """ Verify correct attributes of parents and children """ self.check_attributes(self.rootdata) + self.assertEqual(self.rootdata.parent_id, None) def test_composite_save_load(self): """ @@ -335,7 +352,7 @@ def test_composite_save_load(self): self.rootdata.experiment_id, self.rootdata.service ) - self.check_if_equal(loaded_data, self.rootdata) + self.check_if_equal(loaded_data, self.rootdata, is_a_copy=False) def test_composite_save_metadata(self): """ @@ -348,4 +365,9 @@ def test_composite_save_metadata(self): self.rootdata.experiment_id, self.rootdata.service ) - self.check_if_equal(loaded_data, self.rootdata) + self.check_if_equal(loaded_data, self.rootdata, is_a_copy=False) + + def test_composite_copy_metadata(self): + new_instance = self.rootdata._copy_metadata() + self.check_if_equal(new_instance, self.rootdata, is_a_copy=True) + self.check_attributes(new_instance) From 11ccf058d10971f743afc8f615fab906910a63da Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 10 Oct 2021 16:13:32 +0300 Subject: [PATCH 36/55] black --- .../composite/composite_experiment_data.py | 17 ++++++++++------- test/test_composite.py | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 0114b3742d..b386c466f8 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -35,9 +35,7 @@ def __init__(self, experiment, backend=None, parent_id=None, job_ids=None): job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. """ - super().__init__( - experiment, backend=backend, parent_id=parent_id, job_ids=job_ids - ) + super().__init__(experiment, backend=backend, parent_id=parent_id, job_ids=job_ids) # Initialize sub experiments self._components = [ @@ -159,7 +157,9 @@ def _set_share_level_without_save(self, new_level: str) -> None: for comp in self._components: comp._set_share_level_without_save(new_level) - def _copy_metadata(self, new_instance: Optional["CompositeExperimentData"] = None) -> "CompositeExperimentData": + def _copy_metadata( + self, new_instance: Optional["CompositeExperimentData"] = None + ) -> "CompositeExperimentData": """Make a copy of the composite experiment metadata. Note: @@ -173,9 +173,12 @@ def _copy_metadata(self, new_instance: Optional["CompositeExperimentData"] = Non """ new_instance = super()._copy_metadata(new_instance) - for original_comp, new_comp in zip(self.component_experiment_data(), new_instance.component_experiment_data()): + for original_comp, new_comp in zip( + self.component_experiment_data(), new_instance.component_experiment_data() + ): original_comp._copy_metadata(new_comp) - new_instance.metadata["component_ids"] = [comp.experiment_id for comp in new_instance.component_experiment_data()] + new_instance.metadata["component_ids"] = [ + comp.experiment_id for comp in new_instance.component_experiment_data() + ] return new_instance - diff --git a/test/test_composite.py b/test/test_composite.py index b5aa4ec67f..eb0a9e672c 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -280,7 +280,7 @@ def setUp(self): self.rootdata = CompositeExperimentData( batch_exp, backend=self.backend, job_ids=self.job_ids ) - + self.rootdata.share_level = self.share_level def check_attributes(self, expdata): From a7938bb125de986da845221f659c59e79e8bdfbf Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 10 Oct 2021 16:19:30 +0300 Subject: [PATCH 37/55] lint --- test/test_composite.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_composite.py b/test/test_composite.py index eb0a9e672c..f1265dcf34 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -368,6 +368,9 @@ def test_composite_save_metadata(self): self.check_if_equal(loaded_data, self.rootdata, is_a_copy=False) def test_composite_copy_metadata(self): + """ + Test CompositeExperimentData._copy_metadata + """ new_instance = self.rootdata._copy_metadata() self.check_if_equal(new_instance, self.rootdata, is_a_copy=True) self.check_attributes(new_instance) From 20263ddb130862936f72e1c26b7566be51c9d48b Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 10 Oct 2021 16:36:46 +0300 Subject: [PATCH 38/55] removed job ids from composite tests --- test/test_composite.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/test_composite.py b/test/test_composite.py index f1265dcf34..8cfd30bb64 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -268,7 +268,6 @@ def setUp(self): super().setUp() self.backend = FakeMelbourne() - self.job_ids = [1, 2, 3, 4, 5] self.share_level = "hey" exp1 = FakeExperiment([0, 2]) @@ -278,7 +277,7 @@ def setUp(self): batch_exp = BatchExperiment([par_exp, exp3]) self.rootdata = CompositeExperimentData( - batch_exp, backend=self.backend, job_ids=self.job_ids + batch_exp, backend=self.backend ) self.rootdata.share_level = self.share_level @@ -288,7 +287,6 @@ def check_attributes(self, expdata): Recursively traverse the tree to verify attributes """ self.assertEqual(expdata.backend, self.backend) - self.assertEqual(expdata.job_ids, self.job_ids) self.assertEqual(expdata.share_level, self.share_level) if isinstance(expdata, CompositeExperimentData): @@ -306,7 +304,6 @@ def check_if_equal(self, expdata1, expdata2, is_a_copy): Recursively traverse the tree and check equality of expdata1 and expdata2 """ self.assertEqual(expdata1.backend.name(), expdata2.backend.name()) - self.assertEqual(expdata1.job_ids, expdata2.job_ids) self.assertEqual(expdata1.tags, expdata2.tags) self.assertEqual(expdata1.experiment_type, expdata2.experiment_type) self.assertEqual(expdata1.share_level, expdata2.share_level) From 6a072ea46b0250777fb9040830f643f8d995e411 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 10 Oct 2021 16:56:06 +0300 Subject: [PATCH 39/55] black --- test/test_composite.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/test_composite.py b/test/test_composite.py index 8cfd30bb64..f31dc21241 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -276,9 +276,7 @@ def setUp(self): exp3 = FakeExperiment(4) batch_exp = BatchExperiment([par_exp, exp3]) - self.rootdata = CompositeExperimentData( - batch_exp, backend=self.backend - ) + self.rootdata = CompositeExperimentData(batch_exp, backend=self.backend) self.rootdata.share_level = self.share_level From 6c1f5b95fb47bc06712023078d0ef2c83c9fdb43 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 11 Oct 2021 08:43:28 +0300 Subject: [PATCH 40/55] bug fix --- test/fake_experiment.py | 2 +- test/test_framework.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fake_experiment.py b/test/fake_experiment.py index fc3421f89a..be11cbb244 100644 --- a/test/fake_experiment.py +++ b/test/fake_experiment.py @@ -33,7 +33,7 @@ class FakeExperiment(BaseExperiment): def _default_experiment_options(cls) -> Options: return Options(dummyoption=None) - def __init__(self, qubits=0): + def __init__(self, qubits=1): """Initialise the fake experiment.""" super().__init__(qubits) diff --git a/test/test_framework.py b/test/test_framework.py index 51085ecbd4..99c220b7f1 100644 --- a/test/test_framework.py +++ b/test/test_framework.py @@ -41,7 +41,7 @@ def circuits(self, backend=None): qc.measure_all() return num_circuits * [qc] - exp = Experiment(0) + exp = Experiment(1) expdata = exp.run(backend) job_ids = expdata.job_ids From e141fe9dc3a784564c19f92cc0bef4314850a777 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 11 Oct 2021 09:06:49 +0300 Subject: [PATCH 41/55] lint --- qiskit_experiments/database_service/db_experiment_data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index d1eb1686be..904c58f40c 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -944,6 +944,7 @@ def _copy_metadata( and metadata but different ID. """ if new_instance is None: + #pylint disable=no-value-for-parameter new_instance = self.__class__() new_instance._type = self.experiment_type From d602aae334755e42073b72e5d1b0cb661232d47d Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 11 Oct 2021 09:10:20 +0300 Subject: [PATCH 42/55] black --- qiskit_experiments/database_service/db_experiment_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 904c58f40c..bf23192f8b 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -944,7 +944,7 @@ def _copy_metadata( and metadata but different ID. """ if new_instance is None: - #pylint disable=no-value-for-parameter + # pylint disable=no-value-for-parameter new_instance = self.__class__() new_instance._type = self.experiment_type From a4a4127a18f0533d55b5b99d89c9ffa296bafbd5 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 11 Oct 2021 09:15:01 +0300 Subject: [PATCH 43/55] typo --- qiskit_experiments/database_service/db_experiment_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index bf23192f8b..532a1c6629 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -944,7 +944,7 @@ def _copy_metadata( and metadata but different ID. """ if new_instance is None: - # pylint disable=no-value-for-parameter + # pylint: disable=no-value-for-parameter new_instance = self.__class__() new_instance._type = self.experiment_type From 13129c5303ddb40249c939972fdc6ddea242555f Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 12 Oct 2021 09:03:58 +0300 Subject: [PATCH 44/55] Update qiskit_experiments/database_service/db_experiment_data.py Co-authored-by: Christopher J. Wood --- qiskit_experiments/database_service/db_experiment_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 532a1c6629..d1b5ed4f40 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -1032,7 +1032,7 @@ def experiment_id(self) -> str: @property def parent_id(self) -> str: - """Return parent ID + """Return parent experiment ID Returns: Parent ID. From b8ebad8bd139f7772a85e9b801ca64d33770d37c Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 12 Oct 2021 09:09:20 +0300 Subject: [PATCH 45/55] updated release notes --- releasenotes/notes/save-load-composite-cb882d855dd669be.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml b/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml index 5204df21c8..e5cdf806c1 100644 --- a/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml +++ b/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml @@ -1,3 +1,5 @@ features: - | - Composite experiments: saving a composite experiment saves also all its descendants; similarly, setting the share level and service of a composite experiment sets the share level and service of all its descendants; loading a composite experiment is now enabled; composite experiments and their descnedants are tagged with the root experiment and the direct parent experiment. + Adds support for saving and loading :class:~qiskit_experiments.framework.ParallelExperiment and +:class:~qiskit_experiments.framework.BatchExperiment experiment data, including all their component experiment data and results, to the IBM experiments database service. When saving one of these experiments each component experiment will be saved under a unique experiment ID which references the original parent composite experiment via the :meth:~qiskit_Expeirments.framework.ExperimentData.parent_id. Note that changing the share level of the parent composite experiment will also change the share level of all component experiments. + From 5e8ba8bbd7dd5bfe3d0e215446b63d4faeb27f9a Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 12 Oct 2021 10:06:33 +0300 Subject: [PATCH 46/55] verbose as a class attribute --- .../database_service/database_service.py | 4 +++ .../database_service/db_experiment_data.py | 34 ++++++++++++++++--- .../composite/composite_experiment_data.py | 9 +++-- test/test_composite.py | 5 +++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/qiskit_experiments/database_service/database_service.py b/qiskit_experiments/database_service/database_service.py index a0a8c11d31..460ea97b3e 100644 --- a/qiskit_experiments/database_service/database_service.py +++ b/qiskit_experiments/database_service/database_service.py @@ -58,6 +58,7 @@ def create_experiment( job_ids: Optional[List[str]] = None, tags: Optional[List[str]] = None, notes: Optional[str] = None, + verbose: Optional[bool] = None, json_encoder: Type[json.JSONEncoder] = json.JSONEncoder, **kwargs: Any, ) -> str: @@ -75,6 +76,7 @@ def create_experiment( job_ids: IDs of experiment jobs. tags: Tags to be associated with the experiment. notes: Freeform notes about the experiment. + verbose: Whether to print messages to the standard output. json_encoder: Custom JSON encoder to use to encode the experiment. kwargs: Additional keywords supported by the service provider. @@ -93,6 +95,7 @@ def update_experiment( metadata: Optional[Dict] = None, job_ids: Optional[List[str]] = None, notes: Optional[str] = None, + verbose: Optional[bool] = None, tags: Optional[List[str]] = None, **kwargs: Any, ) -> None: @@ -103,6 +106,7 @@ def update_experiment( metadata: Experiment metadata. job_ids: IDs of experiment jobs. notes: Freeform notes about the experiment. + verbose: Whether to print messages to the standard output. tags: Tags to be associated with the experiment. kwargs: Additional keywords supported by the service provider. diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index d1b5ed4f40..c5e8995fe3 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -106,6 +106,7 @@ def __init__( metadata: Optional[Dict] = None, figure_names: Optional[List[str]] = None, notes: Optional[str] = None, + verbose: Optional[bool] = True, **kwargs, ): """Initializes the DbExperimentData instance. @@ -123,6 +124,7 @@ def __init__( metadata: Additional experiment metadata. figure_names: Name of figures associated with this experiment. notes: Freeform notes about the experiment. + verbose: Whether to print messages to the standard output. **kwargs: Additional experiment attributes. """ metadata = metadata or {} @@ -147,6 +149,7 @@ def __init__( self._tags = tags or [] self._share_level = share_level self._notes = notes or "" + self._verbose = verbose self._jobs = ThreadSafeOrderedDict(job_ids or []) self._job_futures = ThreadSafeList() @@ -714,6 +717,7 @@ def save_metadata(self) -> None: "job_ids": self.job_ids, "tags": self.tags, "notes": self.notes, + "verbose": self.verbose } new_data = {"experiment_type": self._type, "backend_name": self._backend.name()} if self.share_level: @@ -730,12 +734,9 @@ def save_metadata(self) -> None: json_encoder=self._json_encoder, ) - def save(self, verbose: bool = True) -> None: + def save(self) -> None: """Save the experiment data to a database service. - Args: - verbose: if True then the method may print messages to the standard output - .. note:: This saves the experiment metadata, all analysis results, and all figures. Depending on the number of figures and analysis results this @@ -782,7 +783,7 @@ def save(self, verbose: bool = True) -> None: self._service.delete_figure(experiment_id=self.experiment_id, figure_name=name) self._deleted_figures.remove(name) - if verbose: + if self.verbose: print( "You can view the experiment online at https://quantum-computing.ibm.com/experiments/" + self.experiment_id @@ -816,6 +817,7 @@ def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "DbExperimentDa metadata=metadata, figure_names=service_data.pop("figure_names"), notes=service_data.pop("notes"), + verbose=service_data.pop("verbose"), **service_data, ) # Retrieve analysis results @@ -954,6 +956,7 @@ def _copy_metadata( new_instance._share_level = self._share_level new_instance._metadata = copy.deepcopy(self._metadata) new_instance._notes = self._notes + new_instance._verbose = self._verbose, new_instance._auto_save = self._auto_save new_instance._service = self._service new_instance._extra_data = self._extra_data @@ -1186,6 +1189,26 @@ def auto_save(self, save_val: bool) -> None: # can be removed when we start tracking changes. res._auto_save = save_val + @property + def verbose(self) -> bool: + """Whether to print messages to the standard output. + + Returns: + Verbosity flag for this experiment. + """ + return self._verbose + + @verbose.setter + def verbose(self, verbose: bool) -> None: + """Set the verbosity flag for this experiment. + + Args: + service: Whether to print messages to the standard output. + """ + self._verbose = verbose + if self.auto_save: + self.save_metadata() + @property def source(self) -> Dict: """Return the class name and version.""" @@ -1208,6 +1231,7 @@ def __repr__(self): out += f", figure_names={self.figure_names}" if self.notes: out += f", notes={self.notes}" + out += ", verbose={self.verbose}" if self._extra_data: for key, val in self._extra_data.items(): out += f", {key}={repr(val)}" diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 750ef5990e..06e6ea604f 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -46,6 +46,9 @@ def __init__(self, experiment, backend=None, parent_id=None, job_ids=None): self.metadata["component_ids"] = [comp.experiment_id for comp in self._components] self.metadata["component_classes"] = [comp.__class__.__name__ for comp in self._components] + for comp in self._components: + comp.verbose = False + def __str__(self): line = 51 * "-" n_res = len(self._analysis_results) @@ -99,10 +102,10 @@ def _add_single_data(self, data): sub_data["counts"] = data["counts"] self._components[index]._add_single_data(sub_data) - def save(self, verbose=True) -> None: - super().save(verbose) + def save(self) -> None: + super().save() for comp in self._components: - comp.save(verbose=False) + comp.save() def save_metadata(self) -> None: super().save_metadata() diff --git a/test/test_composite.py b/test/test_composite.py index f31dc21241..be20b292cb 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -85,6 +85,7 @@ def create_experiment( job_ids: Optional[List[str]] = None, tags: Optional[List[str]] = None, notes: Optional[str] = None, + verbose: Optional[bool] = None, json_encoder: Type[json.JSONEncoder] = json.JSONEncoder, **kwargs: Any, ) -> str: @@ -102,6 +103,7 @@ def create_experiment( job_ids: IDs of experiment jobs. tags: Tags to be associated with the experiment. notes: Freeform notes about the experiment. + verbose: Whether to print messages to the standard output. json_encoder: Custom JSON encoder to use to encode the experiment. kwargs: Additional keywords supported by the service provider. @@ -117,6 +119,7 @@ def create_experiment( "job_ids": job_ids, "tags": tags, "notes": notes, + "verbose": verbose, "share_level": kwargs.get("share_level", None), "figure_names": kwargs.get("figure_names", None), } @@ -128,6 +131,7 @@ def update_experiment( metadata: Optional[Dict] = None, job_ids: Optional[List[str]] = None, notes: Optional[str] = None, + verbose: Optional[bool] = None, tags: Optional[List[str]] = None, **kwargs: Any, ) -> None: @@ -138,6 +142,7 @@ def update_experiment( metadata: Experiment metadata. job_ids: IDs of experiment jobs. notes: Freeform notes about the experiment. + verbose: Whether to print messages to the standard output. tags: Tags to be associated with the experiment. kwargs: Additional keywords supported by the service provider. """ From e347211a14adcfb34e819e71a06e9c18834643a3 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 12 Oct 2021 10:08:05 +0300 Subject: [PATCH 47/55] black --- qiskit_experiments/database_service/db_experiment_data.py | 4 ++-- .../framework/composite/composite_experiment_data.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index c5e8995fe3..c5af6ced9c 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -717,7 +717,7 @@ def save_metadata(self) -> None: "job_ids": self.job_ids, "tags": self.tags, "notes": self.notes, - "verbose": self.verbose + "verbose": self.verbose, } new_data = {"experiment_type": self._type, "backend_name": self._backend.name()} if self.share_level: @@ -956,7 +956,7 @@ def _copy_metadata( new_instance._share_level = self._share_level new_instance._metadata = copy.deepcopy(self._metadata) new_instance._notes = self._notes - new_instance._verbose = self._verbose, + new_instance._verbose = (self._verbose,) new_instance._auto_save = self._auto_save new_instance._service = self._service new_instance._extra_data = self._extra_data diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 06e6ea604f..01a5c6c76b 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -105,7 +105,7 @@ def _add_single_data(self, data): def save(self) -> None: super().save() for comp in self._components: - comp.save() + comp.save() def save_metadata(self) -> None: super().save_metadata() From adcd38d2bb9ee056dd9fb3bbcad5a15fe362291c Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 12 Oct 2021 11:04:37 +0300 Subject: [PATCH 48/55] rewrote share level setter --- .../database_service/db_experiment_data.py | 12 +----------- .../composite/composite_experiment_data.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index c5af6ced9c..0733739639 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -1095,20 +1095,10 @@ def share_level(self, new_level: str) -> None: specified. For example, IBM Quantum experiment service allows "public", "hub", "group", "project", and "private". """ - self._set_share_level_without_save(new_level) + self._share_level = new_level if self.auto_save: self.save_metadata() - def _set_share_level_without_save(self, new_level: str) -> None: - """Set the experiment share level. Doesn't save, not even when auto_save is set to True. - - Args: - new_level: New experiment share level. Valid share levels are provider- - specified. For example, IBM Quantum experiment service allows - "public", "hub", "group", "project", and "private". - """ - self._share_level = new_level - @property def notes(self) -> str: """Return experiment notes. diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 01a5c6c76b..c5f48bd56c 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -143,17 +143,23 @@ def _set_service(self, service: DatabaseServiceV1) -> None: for comp in self._components: comp._set_service(service) - def _set_share_level_without_save(self, new_level: str) -> None: - """Set the experiment share level. Doesn't save, not even when auto_save is set to True. + @ExperimentData.share_level.setter + def share_level(self, new_level: str) -> None: + """Set the experiment share level. Args: new_level: New experiment share level. Valid share levels are provider- specified. For example, IBM Quantum experiment service allows "public", "hub", "group", "project", and "private". """ - DbExperimentDataV1._set_share_level_without_save(self, new_level) + self._share_level = new_level for comp in self._components: - comp._set_share_level_without_save(new_level) + original_auto_save = comp.auto_save + comp.auto_save = False + comp.share_level = new_level + comp.auto_save = original_auto_save + if self.auto_save: + self.save_metadata() def _copy_metadata( self, new_instance: Optional["CompositeExperimentData"] = None From d540a5b1a7382aaebdb251e02a5fb450e397bae3 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 12 Oct 2021 11:26:01 +0300 Subject: [PATCH 49/55] lint --- qiskit_experiments/database_service/db_experiment_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 0733739639..87714ad1cc 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -1193,7 +1193,7 @@ def verbose(self, verbose: bool) -> None: """Set the verbosity flag for this experiment. Args: - service: Whether to print messages to the standard output. + verbose: Whether to print messages to the standard output. """ self._verbose = verbose if self.auto_save: From 90240b08d415a4f3e3636b2a95b6d30a7e42e32b Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 12 Oct 2021 11:54:37 +0300 Subject: [PATCH 50/55] sphinx --- releasenotes/notes/save-load-composite-cb882d855dd669be.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml b/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml index e5cdf806c1..eaeaf0c5b2 100644 --- a/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml +++ b/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml @@ -1,5 +1,4 @@ features: - | - Adds support for saving and loading :class:~qiskit_experiments.framework.ParallelExperiment and -:class:~qiskit_experiments.framework.BatchExperiment experiment data, including all their component experiment data and results, to the IBM experiments database service. When saving one of these experiments each component experiment will be saved under a unique experiment ID which references the original parent composite experiment via the :meth:~qiskit_Expeirments.framework.ExperimentData.parent_id. Note that changing the share level of the parent composite experiment will also change the share level of all component experiments. + Adds support for saving and loading :class:`~qiskit_experiments.framework.ParallelExperiment` and :class:`~qiskit_experiments.framework.BatchExperiment` experiment data, including all their component experiment data and results, to the IBM experiments database service. When saving one of these experiments each component experiment will be saved under a unique experiment ID which references the original parent composite experiment via the :meth:`~qiskit_Expeirments.framework.ExperimentData.parent_id`. Note that changing the share level of the parent composite experiment will also change the share level of all component experiments. From fd896ef9e3c4507bef78fb32d5ba52351a3afd9e Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 13 Oct 2021 09:11:43 +0300 Subject: [PATCH 51/55] changed inheritance structure of save_metadata --- .../database_service/db_experiment_data.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 87714ad1cc..145326b505 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -682,6 +682,18 @@ def analysis_results( def save_metadata(self) -> None: """Save this experiments metadata to a database service. + .. note:: + This method does not save analysis results nor figures. + Use :meth:`save` for general saving of all experiment data. + + See :meth:`qiskit.providers.experiment.DatabaseServiceV1.create_experiment` + for fields that are saved. + """ + self.save_experiment_metadata() + + def save_experiment_metadata(self) -> None: + """Save this experiments metadata to a database service. + .. note:: This method does not save analysis results nor figures. Use :meth:`save` for general saving of all experiment data. @@ -754,7 +766,7 @@ def save(self) -> None: ) return - DbExperimentDataV1.save_metadata(self) + self.save_experiment_metadata() for result in self._analysis_results.values(): result.save() From 3fbc4def0b6ee92ba50fbf7e71c909f216fe5c7b Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 19 Oct 2021 09:29:21 +0300 Subject: [PATCH 52/55] verbose as a class attribute --- .../database_service/database_service.py | 4 --- .../database_service/db_experiment_data.py | 28 +------------------ .../composite/composite_experiment_data.py | 6 ++-- test/test_composite.py | 5 ---- 4 files changed, 4 insertions(+), 39 deletions(-) diff --git a/qiskit_experiments/database_service/database_service.py b/qiskit_experiments/database_service/database_service.py index 460ea97b3e..a0a8c11d31 100644 --- a/qiskit_experiments/database_service/database_service.py +++ b/qiskit_experiments/database_service/database_service.py @@ -58,7 +58,6 @@ def create_experiment( job_ids: Optional[List[str]] = None, tags: Optional[List[str]] = None, notes: Optional[str] = None, - verbose: Optional[bool] = None, json_encoder: Type[json.JSONEncoder] = json.JSONEncoder, **kwargs: Any, ) -> str: @@ -76,7 +75,6 @@ def create_experiment( job_ids: IDs of experiment jobs. tags: Tags to be associated with the experiment. notes: Freeform notes about the experiment. - verbose: Whether to print messages to the standard output. json_encoder: Custom JSON encoder to use to encode the experiment. kwargs: Additional keywords supported by the service provider. @@ -95,7 +93,6 @@ def update_experiment( metadata: Optional[Dict] = None, job_ids: Optional[List[str]] = None, notes: Optional[str] = None, - verbose: Optional[bool] = None, tags: Optional[List[str]] = None, **kwargs: Any, ) -> None: @@ -106,7 +103,6 @@ def update_experiment( metadata: Experiment metadata. job_ids: IDs of experiment jobs. notes: Freeform notes about the experiment. - verbose: Whether to print messages to the standard output. tags: Tags to be associated with the experiment. kwargs: Additional keywords supported by the service provider. diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index 145326b505..f537dab683 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -87,6 +87,7 @@ class DbExperimentDataV1(DbExperimentData): """ version = 1 + verbose = True # Whether to print messages to the standard output. _metadata_version = 1 _executor = futures.ThreadPoolExecutor() """Threads used for asynchronous processing.""" @@ -106,7 +107,6 @@ def __init__( metadata: Optional[Dict] = None, figure_names: Optional[List[str]] = None, notes: Optional[str] = None, - verbose: Optional[bool] = True, **kwargs, ): """Initializes the DbExperimentData instance. @@ -124,7 +124,6 @@ def __init__( metadata: Additional experiment metadata. figure_names: Name of figures associated with this experiment. notes: Freeform notes about the experiment. - verbose: Whether to print messages to the standard output. **kwargs: Additional experiment attributes. """ metadata = metadata or {} @@ -149,7 +148,6 @@ def __init__( self._tags = tags or [] self._share_level = share_level self._notes = notes or "" - self._verbose = verbose self._jobs = ThreadSafeOrderedDict(job_ids or []) self._job_futures = ThreadSafeList() @@ -729,7 +727,6 @@ def save_experiment_metadata(self) -> None: "job_ids": self.job_ids, "tags": self.tags, "notes": self.notes, - "verbose": self.verbose, } new_data = {"experiment_type": self._type, "backend_name": self._backend.name()} if self.share_level: @@ -829,7 +826,6 @@ def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "DbExperimentDa metadata=metadata, figure_names=service_data.pop("figure_names"), notes=service_data.pop("notes"), - verbose=service_data.pop("verbose"), **service_data, ) # Retrieve analysis results @@ -968,7 +964,6 @@ def _copy_metadata( new_instance._share_level = self._share_level new_instance._metadata = copy.deepcopy(self._metadata) new_instance._notes = self._notes - new_instance._verbose = (self._verbose,) new_instance._auto_save = self._auto_save new_instance._service = self._service new_instance._extra_data = self._extra_data @@ -1191,26 +1186,6 @@ def auto_save(self, save_val: bool) -> None: # can be removed when we start tracking changes. res._auto_save = save_val - @property - def verbose(self) -> bool: - """Whether to print messages to the standard output. - - Returns: - Verbosity flag for this experiment. - """ - return self._verbose - - @verbose.setter - def verbose(self, verbose: bool) -> None: - """Set the verbosity flag for this experiment. - - Args: - verbose: Whether to print messages to the standard output. - """ - self._verbose = verbose - if self.auto_save: - self.save_metadata() - @property def source(self) -> Dict: """Return the class name and version.""" @@ -1233,7 +1208,6 @@ def __repr__(self): out += f", figure_names={self.figure_names}" if self.notes: out += f", notes={self.notes}" - out += ", verbose={self.verbose}" if self._extra_data: for key, val in self._extra_data.items(): out += f", {key}={repr(val)}" diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index c5f48bd56c..5da46abaa0 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -46,9 +46,6 @@ def __init__(self, experiment, backend=None, parent_id=None, job_ids=None): self.metadata["component_ids"] = [comp.experiment_id for comp in self._components] self.metadata["component_classes"] = [comp.__class__.__name__ for comp in self._components] - for comp in self._components: - comp.verbose = False - def __str__(self): line = 51 * "-" n_res = len(self._analysis_results) @@ -105,7 +102,10 @@ def _add_single_data(self, data): def save(self) -> None: super().save() for comp in self._components: + original_verbose = comp.verbose + comp.verbose = False comp.save() + comp.verbose = original_verbose def save_metadata(self) -> None: super().save_metadata() diff --git a/test/test_composite.py b/test/test_composite.py index be20b292cb..f31dc21241 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -85,7 +85,6 @@ def create_experiment( job_ids: Optional[List[str]] = None, tags: Optional[List[str]] = None, notes: Optional[str] = None, - verbose: Optional[bool] = None, json_encoder: Type[json.JSONEncoder] = json.JSONEncoder, **kwargs: Any, ) -> str: @@ -103,7 +102,6 @@ def create_experiment( job_ids: IDs of experiment jobs. tags: Tags to be associated with the experiment. notes: Freeform notes about the experiment. - verbose: Whether to print messages to the standard output. json_encoder: Custom JSON encoder to use to encode the experiment. kwargs: Additional keywords supported by the service provider. @@ -119,7 +117,6 @@ def create_experiment( "job_ids": job_ids, "tags": tags, "notes": notes, - "verbose": verbose, "share_level": kwargs.get("share_level", None), "figure_names": kwargs.get("figure_names", None), } @@ -131,7 +128,6 @@ def update_experiment( metadata: Optional[Dict] = None, job_ids: Optional[List[str]] = None, notes: Optional[str] = None, - verbose: Optional[bool] = None, tags: Optional[List[str]] = None, **kwargs: Any, ) -> None: @@ -142,7 +138,6 @@ def update_experiment( metadata: Experiment metadata. job_ids: IDs of experiment jobs. notes: Freeform notes about the experiment. - verbose: Whether to print messages to the standard output. tags: Tags to be associated with the experiment. kwargs: Additional keywords supported by the service provider. """ From 1ab63c8e418dc3ce8cceacc7d49a3a0338e256d9 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 19 Oct 2021 09:41:42 +0300 Subject: [PATCH 53/55] Update qiskit_experiments/framework/composite/composite_experiment_data.py Co-authored-by: Christopher J. Wood --- .../framework/composite/composite_experiment_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 5da46abaa0..7fe1064c40 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -139,7 +139,7 @@ def _set_service(self, service: DatabaseServiceV1) -> None: Raises: DbExperimentDataError: If an experiment service is already being used. """ - DbExperimentDataV1._set_service(self, service) + super()._set_service(service) for comp in self._components: comp._set_service(service) From a3c685b2c467f678743164d9b05c8931ba28f0ff Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 19 Oct 2021 17:37:02 +0300 Subject: [PATCH 54/55] save_experiment_metadata -> _save_experiment_metadata --- qiskit_experiments/database_service/db_experiment_data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/database_service/db_experiment_data.py b/qiskit_experiments/database_service/db_experiment_data.py index f537dab683..bc4204e96e 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -687,9 +687,9 @@ def save_metadata(self) -> None: See :meth:`qiskit.providers.experiment.DatabaseServiceV1.create_experiment` for fields that are saved. """ - self.save_experiment_metadata() + self._save_experiment_metadata() - def save_experiment_metadata(self) -> None: + def _save_experiment_metadata(self) -> None: """Save this experiments metadata to a database service. .. note:: @@ -763,7 +763,7 @@ def save(self) -> None: ) return - self.save_experiment_metadata() + self._save_experiment_metadata() for result in self._analysis_results.values(): result.save() From 6c909a191d6aec80dcf8f776444236072aece0f7 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 19 Oct 2021 17:37:21 +0300 Subject: [PATCH 55/55] line breaks in release notes --- .../notes/save-load-composite-cb882d855dd669be.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml b/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml index eaeaf0c5b2..a52bec5b50 100644 --- a/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml +++ b/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml @@ -1,4 +1,12 @@ features: - | - Adds support for saving and loading :class:`~qiskit_experiments.framework.ParallelExperiment` and :class:`~qiskit_experiments.framework.BatchExperiment` experiment data, including all their component experiment data and results, to the IBM experiments database service. When saving one of these experiments each component experiment will be saved under a unique experiment ID which references the original parent composite experiment via the :meth:`~qiskit_Expeirments.framework.ExperimentData.parent_id`. Note that changing the share level of the parent composite experiment will also change the share level of all component experiments. + Adds support for saving and loading :class:`~qiskit_experiments.framework.ParallelExperiment` + and :class:`~qiskit_experiments.framework.BatchExperiment` experiment data, + including all their component experiment data and results, to the IBM experiments database service. + + When saving one of these experiments, each component experiment will be saved under a unique experiment ID, + which references the original parent composite experiment, + via the :meth:`~qiskit_Expeirments.framework.ExperimentData.parent_id`. + + Note that changing the share level of the parent composite experiment will also change the share level of all component experiments.