Skip to content
Merged
13 changes: 8 additions & 5 deletions qiskit_experiments/database_service/db_experiment_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import contextlib
from collections import deque
from datetime import datetime
import numpy as np

from matplotlib import pyplot
from qiskit.providers import Job, BaseJob, Backend, BaseBackend, Provider
Expand Down Expand Up @@ -1218,7 +1219,7 @@ def tags(self, new_tags: List[str]) -> None:
raise DbExperimentDataError(
f"The `tags` field of {type(self).__name__} must be a list."
)
self._tags = new_tags
self._tags = np.unique(new_tags).tolist()
if self.auto_save:
self.save_metadata()

Expand Down Expand Up @@ -1297,7 +1298,7 @@ def figure_names(self) -> List[str]:

@property
def share_level(self) -> str:
"""Return the share level fo this experiment.
"""Return the share level for this experiment

Returns:
Experiment share level.
Expand All @@ -1306,7 +1307,8 @@ def share_level(self) -> str:

@share_level.setter
def share_level(self, new_level: str) -> None:
"""Set the experiment share level.
"""Set the experiment share level,
only to this experiment and not to its descendants.

Args:
new_level: New experiment share level. Valid share levels are provider-
Expand Down Expand Up @@ -1348,7 +1350,7 @@ def service(self) -> Optional[DatabaseServiceV1]:

@service.setter
def service(self, service: DatabaseServiceV1) -> None:
"""Set the service to be used for storing experiment data.
"""Set the service to be used for storing experiment data

Args:
service: Service to be used.
Expand All @@ -1359,7 +1361,8 @@ def service(self, service: DatabaseServiceV1) -> None:
self._set_service(service)

def _set_service(self, service: DatabaseServiceV1) -> None:
"""Set the service to be used for storing experiment data.
"""Set the service to be used for storing experiment data,
to this experiment only and not to its descendants

Args:
service: Service to be used.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class CompositeAnalysis(BaseAnalysis):
composite :class:`ExperimentData`.

When calling :meth:`run` on experiment data already containing
initalized component experiment child data, any previously stored
initialized component experiment child data, any previously stored
circuit data will be cleared and replaced with the marginalized data
reconstructed from the parent composite experiment data.
"""
Expand Down
26 changes: 24 additions & 2 deletions qiskit_experiments/framework/experiment_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ def _set_child_data(self, child_data: List[ExperimentData]):
self.add_child_data(data)

def _set_service(self, service: DatabaseService) -> None:
"""Set the service to be used for storing experiment data.
"""Set the service to be used for storing experiment data,
to this experiment itself and its descendants.

Args:
service: Service to be used.
Expand All @@ -197,7 +198,8 @@ def _set_service(self, service: DatabaseService) -> None:

@DbExperimentData.share_level.setter
def share_level(self, new_level: str) -> None:
"""Set the experiment share level.
"""Set the experiment share level,
to this experiment itself and its descendants.

Args:
new_level: New experiment share level. Valid share levels are provider-
Expand Down Expand Up @@ -227,6 +229,26 @@ def block_for_results(self, timeout: Optional[float] = None) -> ExperimentData:
_, timeout = combined_timeout(subdata.block_for_results, timeout)
return self

def add_tags_recursive(self, tags2add: List[str]) -> None:
"""Add tags to this experiment itself and its descendants

Args:
tags2add - the tags that will be added to the existing tags
"""
self.tags += tags2add
for data in self._child_data.values():
data.add_tags_recursive(tags2add)

def remove_tags_recursive(self, tags2remove: List[str]) -> None:
"""Remove tags from this experiment itself and its descendants

Args:
tags2remove - the tags that will be removed from the existing tags
"""
self.tags = [x for x in self.tags if x not in tags2remove]
for data in self._child_data.values():
data.remove_tags_recursive(tags2remove)

def __repr__(self):
out = (
f"<ExperimentData[{self.experiment_type}]"
Expand Down
4 changes: 4 additions & 0 deletions releasenotes/notes/add-remove-tags-9fa798ccad0f5c6a.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
features:
- |
New methods `add_tags_recursive` and `remove_tags_recursive` for adding and removing tags of an experiment data object and recursively all its descendants (if it is composite).
28 changes: 28 additions & 0 deletions test/test_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,34 @@ def test_analysis_replace_results_false(self):
for sub1, sub2 in zip(data1.child_data(), data2.child_data()):
self.assertNotEqual(sub1.experiment_id, sub2.experiment_id)

def test_composite_tags(self):
"""
Test the tags setter, add_tags_recursive, remove_tags_recursive
"""
exp1 = FakeExperiment([0, 2])
exp2 = FakeExperiment([1, 3])
par_exp = BatchExperiment([exp1, exp2])
expdata = par_exp.run(FakeBackend()).block_for_results()
data1 = expdata.child_data(0)
data2 = expdata.child_data(1)

expdata.tags = ["a", "c", "a"]
data1.tags = ["b"]
print(expdata.tags)
self.assertEqual(sorted(expdata.tags), ["a", "c"])
self.assertEqual(sorted(data1.tags), ["b"])
self.assertEqual(sorted(data2.tags), [])

expdata.add_tags_recursive(["d", "c"])
self.assertEqual(sorted(expdata.tags), ["a", "c", "d"])
self.assertEqual(sorted(data1.tags), ["b", "c", "d"])
self.assertEqual(sorted(data2.tags), ["c", "d"])

expdata.remove_tags_recursive(["a", "b"])
self.assertEqual(sorted(expdata.tags), ["c", "d"])
self.assertEqual(sorted(data1.tags), ["c", "d"])
self.assertEqual(sorted(data2.tags), ["c", "d"])

def test_composite_subexp_data(self):
"""
Verify that sub-experiment data of parallel and batch
Expand Down