Skip to content

Commit c4e9a52

Browse files
authored
Fix inconsistent handling of figure names (#1430)
`ExperimentData._clear_results()` cleared `ExperimentData._figures` but not the related `ExperimentData._db_data.figure_names` exposed as `ExperimentData.figure_names`. `BaseAnalysis.run()` calls `ExperimentData._clear_results()` so when a second analysis run produced a different set of figure names from the first the `ExperimentData` instance would be left a `figure_names` property with entries that did not match the underlying figures data. Here, this issue is addressed by also clearing `ExperimentData._db_data.figure_names`. This issue was first noticed due to occasional failures of the `test.database_service.test_db_experiment_data.TestDbExperimentData.test_copy_figure_artifacts` test which runs a fake experiment and then adds a figure and artifact to the experiment data. Then the tests copies the experiment data to test the behavior of `copy()`. Occasionally, the adding of the figure occurs before the background analysis thread runs. In this case, the figure name gets added to `figure_names` before the results get cleared by the analysis callback. Because of the bug described above, the old figure name stays around in the original experiment data object but does not copy to the new object because figure names are copied using the data in `ExperimentData._figures` and not `ExperimentData._db_data.figure_names`. A `block_for_results()` was added to the experiment because in principle the `copy()` could occur before the analysis clears results and then the two data objects would not match, though this case has not been observed (only adding the figure and then clearing it with analysis before copying so the copy is the one missing the figure name has been observed; not the copy having the name and not the original). Addtionally, `BaseAnalysis.run()` handled the `figure_names` option incorrectly when passes a keyword argument. Keyword arguments to passed to `run()` cause the analysis class to copy itself and add the options to the copy before running it. However, within the `run()` method, `self.options.figure_names` was used for assigning figure names rather than referencing the copy of the analysis class, so figure names could not be set using `figure_names` as a keyword argument. This issues was fixed by replacing `self` references with references to the copy. A regression test was added that fails for either of the above issues being present (figure names not matching after re-running analysis; figure names not settable from a `run()` keyword argument).
1 parent cf6222d commit c4e9a52

File tree

5 files changed

+44
-3
lines changed

5 files changed

+44
-3
lines changed

qiskit_experiments/framework/base_analysis.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def run_analysis(expdata: ExperimentData):
184184
if not result.experiment:
185185
result.experiment = expdata.experiment_type
186186
if not result.device_components:
187-
result.device_components = self._get_experiment_components(expdata)
187+
result.device_components = analysis._get_experiment_components(expdata)
188188
if not result.backend:
189189
result.backend = expdata.backend_name
190190
if not result.created_time:
@@ -204,7 +204,7 @@ def run_analysis(expdata: ExperimentData):
204204
if not result.experiment_id:
205205
result.experiment_id = expdata.experiment_id
206206
if not result.device_components:
207-
result.device_components = self._get_experiment_components(expdata)
207+
result.device_components = analysis._get_experiment_components(expdata)
208208
if not result.experiment:
209209
result.experiment = expdata.experiment_type
210210
expdata.add_artifacts(result)
@@ -227,7 +227,7 @@ def run_analysis(expdata: ExperimentData):
227227
name=f"{expdata.experiment_type}_{qubits_repr}_{short_id}.svg",
228228
)
229229
figure_to_add.append(figure)
230-
expdata.add_figures(figure_to_add, figure_names=self.options.figure_names)
230+
expdata.add_figures(figure_to_add, figure_names=analysis.options.figure_names)
231231

232232
experiment_data.add_analysis_callback(run_analysis)
233233

qiskit_experiments/framework/experiment_data.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ def _clear_results(self):
630630
self._deleted_figures.append(key)
631631
self._figures = ThreadSafeOrderedDict()
632632
self._artifacts = ThreadSafeOrderedDict()
633+
self._db_data.figure_names.clear()
633634

634635
@property
635636
def service(self) -> Optional[IBMExperimentService]:
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
fixes:
3+
- |
4+
:class:`.ExperimentData` was updated so that running analysis a second time
5+
with ``replace_results=True`` does not result in the ``figure_names``
6+
property having incorrect data (both old and new figure names if the names
7+
changed). See `#1430
8+
<https://github.com/Qiskit-Extensions/qiskit-experiments/pull/1430>`__.
9+
- |
10+
:class:`.BaseAnalysis` was updated to respect ``figure_names`` as a keyword
11+
argument to the ``run()`` method. Previously, this argument was ignored and
12+
``figure_names`` could only be set as an analysis option prior to calling
13+
``run()``. See `#1430
14+
<https://github.com/Qiskit-Extensions/qiskit-experiments/pull/1430>`__.

test/database_service/test_db_experiment_data.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,7 @@ def test_copy_metadata(self):
10671067
def test_copy_figure_artifacts(self):
10681068
"""Test copy expdata figures and artifacts."""
10691069
exp_data = FakeExperiment(experiment_type="qiskit_test").run(backend=FakeBackend())
1070+
self.assertExperimentDone(exp_data)
10701071
exp_data.add_figures(str.encode("hello world"))
10711072
exp_data.add_artifacts(ArtifactData(name="test", data="foo"))
10721073
copied = exp_data.copy(copy_results=True)

test/framework/test_framework.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from qiskit_experiments.exceptions import AnalysisError
2929
from qiskit_experiments.framework import (
3030
ExperimentData,
31+
FigureData,
3132
BaseExperiment,
3233
BaseAnalysis,
3334
AnalysisResultData,
@@ -163,6 +164,30 @@ def test_analysis_replace_results_true(self):
163164
self.assertEqualExtended(expdata1.analysis_results(), expdata2.analysis_results())
164165
self.assertEqual(result_ids, list(expdata2._deleted_analysis_results))
165166

167+
def test_analysis_replace_results_true_new_figure(self):
168+
"""Test running analysis with replace_results=True keeps figure data consistent"""
169+
analysis = FakeAnalysis()
170+
analysis.options.add_figures = True
171+
analysis.options.figure_names = ["old_figure_name.svg"]
172+
173+
expdata = ExperimentData()
174+
expdata.add_data(self.fake_job_data())
175+
analysis.run(expdata, seed=54321)
176+
self.assertExperimentDone(expdata)
177+
178+
# Assure all figure names map to valid figures
179+
self.assertEqual(expdata.figure_names, ["old_figure_name.svg"])
180+
self.assertIsInstance(expdata.figure("old_figure_name"), FigureData)
181+
182+
analysis.run(
183+
expdata, replace_results=True, seed=12345, figure_names=["new_figure_name.svg"]
184+
)
185+
self.assertExperimentDone(expdata)
186+
187+
# Assure figure names have changed but are still valid
188+
self.assertEqual(expdata.figure_names, ["new_figure_name.svg"])
189+
self.assertIsInstance(expdata.figure("new_figure_name"), FigureData)
190+
166191
def test_analysis_replace_results_false(self):
167192
"""Test running analysis with replace_results=False"""
168193
analysis = FakeAnalysis()

0 commit comments

Comments
 (0)