diff --git a/qiskit_experiments/database_service/database_service.py b/qiskit_experiments/database_service/database_service.py index 58d58d91a7..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. @@ -78,7 +82,7 @@ def create_experiment( Experiment ID. Raises: - ExperimentEntryExists: If the experiment already exits. + ExperimentEntryExists: If the experiment already exists. """ pass @@ -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 963c5c6501..9e1749576f 100644 --- a/qiskit_experiments/database_service/db_experiment_data.py +++ b/qiskit_experiments/database_service/db_experiment_data.py @@ -101,6 +101,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.""" @@ -113,6 +114,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, @@ -127,6 +129,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 @@ -154,6 +157,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 @@ -719,6 +723,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. @@ -758,6 +774,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), @@ -788,7 +806,7 @@ def save(self) -> None: ) return - self.save_metadata() + self._save_experiment_metadata() for result in self._analysis_results.values(): result.save() @@ -817,10 +835,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 self.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": @@ -843,6 +862,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"), @@ -1103,6 +1123,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 @@ -1110,7 +1131,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 @@ -1133,6 +1154,7 @@ def _copy_metadata( timeout=orig_kwargs["timeout"], **extra_kwargs, ) + return new_instance @property @@ -1185,6 +1207,15 @@ def experiment_id(self) -> str: """ return self._id + @property + def parent_id(self) -> str: + """Return parent experiment ID + + Returns: + Parent ID. + """ + return self._parent_id + @property def job_ids(self) -> List[str]: """Return experiment job IDs. @@ -1275,6 +1306,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. @@ -1319,6 +1361,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: diff --git a/qiskit_experiments/framework/composite/composite_experiment_data.py b/qiskit_experiments/framework/composite/composite_experiment_data.py index 20e35f3752..7fe1064c40 100644 --- a/qiskit_experiments/framework/composite/composite_experiment_data.py +++ b/qiskit_experiments/framework/composite/composite_experiment_data.py @@ -17,40 +17,35 @@ 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): """Composite experiment data class""" - def __init__( - self, - experiment, - backend=None, - job_ids=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. - - Raises: - ExperimentError: If an input argument is invalid. """ - super().__init__( - experiment, - backend=backend, - job_ids=job_ids, - ) + super().__init__(experiment, backend=backend, parent_id=parent_id, job_ids=job_ids) # Initialize sub experiments self._components = [ - expr.__experiment_data__(expr, backend) for expr in experiment._experiments + expr.__experiment_data__(expr, backend=backend, parent_id=self.experiment_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] + def __str__(self): line = 51 * "-" n_res = len(self._analysis_results) @@ -103,3 +98,91 @@ def _add_single_data(self, data): else: sub_data["counts"] = data["counts"] self._components[index]._add_single_data(sub_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() + for comp in self._components: + comp.save_metadata() + + @classmethod + def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "CompositeExperimentData": + expdata = DbExperimentDataV1.load(experiment_id, service) + components = [] + for comp_id, comp_class in zip( + 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) + + expdata.__class__ = CompositeExperimentData + expdata._experiment = None + expdata._components = components + + return expdata + + 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. + """ + super()._set_service(service) + for comp in self._components: + comp._set_service(service) + + @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". + """ + self._share_level = new_level + for comp in self._components: + 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 + ) -> "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 406be2a6fb..0742e27cec 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -24,26 +24,21 @@ class ExperimentData(DbExperimentDataV1): """Qiskit Experiments Data container class""" - def __init__( - self, - experiment=None, - backend=None, - job_ids=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. + 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. - - Raises: - ExperimentError: If an input argument is invalid. """ 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 {}, ) @@ -80,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/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml b/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml new file mode 100644 index 0000000000..a52bec5b50 --- /dev/null +++ b/releasenotes/notes/save-load-composite-cb882d855dd669be.yaml @@ -0,0 +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. + diff --git a/test/fake_experiment.py b/test/fake_experiment.py index 5c6b1fa46f..be11cbb244 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=1): """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 c579eb79ab..f31dc21241 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -12,12 +12,26 @@ """Class to test composite experiments.""" +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 from qiskit.test import QiskitTestCase +from qiskit.test.mock import FakeMelbourne + +from qiskit_experiments.framework import ( + ParallelExperiment, + Options, + CompositeExperimentData, + BatchExperiment, +) +from qiskit_experiments.database_service import DatabaseServiceV1 +from qiskit_experiments.database_service.device_component import DeviceComponent -from qiskit_experiments.framework import ParallelExperiment, Options +# pylint: disable=missing-raises-doc class TestComposite(QiskitTestCase): @@ -29,11 +43,10 @@ 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) @@ -52,3 +65,307 @@ def test_parallel_options(self): self.assertEqual(par_exp.analysis_options, Options()) par_exp.run(FakeBackend()) + + +class DummyService(DatabaseServiceV1): + """ + Extremely simple database for testing + """ + + def __init__(self): + self.database = {} + + def create_experiment( + self, + experiment_type: str, + 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, + 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. + 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. + json_encoder: Custom JSON encoder to use to encode the experiment. + kwargs: Additional keywords supported by the service provider. + + Returns: + Experiment ID. + """ + + self.database[experiment_id] = { + "experiment_type": experiment_type, + "parent_id": parent_id, + "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. + """ + raise Exception("not implemented") + + 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(self.database[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, + parent_id: Optional[str] = None, + tags_operator: Optional[str] = "OR", + **filters: Any, + ) -> List[Dict]: + raise Exception("not implemented") + + def delete_experiment(self, experiment_id: str) -> None: + raise Exception("not implemented") + + 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: + raise Exception("not implemented") + + 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: + raise Exception("not implemented") + + def analysis_result( + self, result_id: str, json_decoder: Type[json.JSONDecoder] = json.JSONDecoder + ) -> Dict: + raise Exception("not implemented") + + 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]: + raise Exception("not implemented") + + def delete_analysis_result(self, result_id: str) -> None: + raise Exception("not implemented") + + def create_figure( + self, experiment_id: str, figure: Union[str, bytes], figure_name: Optional[str] + ) -> Tuple[str, int]: + raise Exception("not implemented") + + def update_figure( + self, experiment_id: str, figure: Union[str, bytes], figure_name: str + ) -> Tuple[str, int]: + raise Exception("not implemented") + + def figure( + self, experiment_id: str, figure_name: str, file_name: Optional[str] = None + ) -> Union[int, bytes]: + raise Exception("not implemented") + + def delete_figure( + self, + experiment_id: str, + figure_name: str, + ) -> None: + raise Exception("not implemented") + + @property + def preferences(self) -> Dict: + raise Exception("not implemented") + + +class TestCompositeExperimentData(QiskitTestCase): + """ + Test operations on objects of CompositeExperimentData + """ + + def setUp(self): + super().setUp() + + self.backend = FakeMelbourne() + self.share_level = "hey" + + exp1 = FakeExperiment([0, 2]) + exp2 = FakeExperiment([1, 3]) + par_exp = ParallelExperiment([exp1, exp2]) + exp3 = FakeExperiment(4) + batch_exp = BatchExperiment([par_exp, exp3]) + + self.rootdata = CompositeExperimentData(batch_exp, backend=self.backend) + + self.rootdata.share_level = self.share_level + + def check_attributes(self, expdata): + """ + Recursively traverse the tree to verify attributes + """ + self.assertEqual(expdata.backend, self.backend) + self.assertEqual(expdata.share_level, self.share_level) + + if isinstance(expdata, CompositeExperimentData): + 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, 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.tags, expdata2.tags) + self.assertEqual(expdata1.experiment_type, expdata2.experiment_type) + 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, 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): + """ + Verify that saving and loading restores the original composite experiment data object + """ + + self.rootdata.service = DummyService() + self.rootdata.save() + loaded_data = CompositeExperimentData.load( + self.rootdata.experiment_id, self.rootdata.service + ) + + self.check_if_equal(loaded_data, self.rootdata, is_a_copy=False) + + 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, 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) 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