From e926e73a0edd06241cfff300f480a031d5330078 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Tue, 8 Feb 2022 13:36:15 -0500 Subject: [PATCH] Fix CompositeExperiment.copy Fix CompositeExperiment.copy to preserve any analysis object references between the original component experiment analysis classes and the composite analysis component analysis classes. --- .../framework/base_experiment.py | 2 +- .../framework/composite/composite_analysis.py | 6 ++++ .../composite/composite_experiment.py | 9 ++++++ .../fix-composite-copy-a869e9773f6a4d48.yaml | 10 +++++++ test/test_composite.py | 29 +++++++++++++++++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fix-composite-copy-a869e9773f6a4d48.yaml diff --git a/qiskit_experiments/framework/base_experiment.py b/qiskit_experiments/framework/base_experiment.py index 2587e365af..dfa10fd0c3 100644 --- a/qiskit_experiments/framework/base_experiment.py +++ b/qiskit_experiments/framework/base_experiment.py @@ -279,7 +279,7 @@ def run( # Optionally run analysis if analysis and experiment.analysis: - return self.analysis.run(experiment_data) + return experiment.analysis.run(experiment_data) else: return experiment_data diff --git a/qiskit_experiments/framework/composite/composite_analysis.py b/qiskit_experiments/framework/composite/composite_analysis.py index f69eb4d15d..a0c7bdf7a5 100644 --- a/qiskit_experiments/framework/composite/composite_analysis.py +++ b/qiskit_experiments/framework/composite/composite_analysis.py @@ -60,6 +60,12 @@ def component_analysis(self, index=None) -> Union[BaseAnalysis, List[BaseAnalysi return self._analyses return self._analyses[index] + def copy(self): + ret = super().copy() + # Recursively copy analysis + ret._analyses = [analysis.copy() for analysis in ret._analyses] + return ret + def _run_analysis(self, experiment_data: ExperimentData): # Return list of experiment data containers for each component experiment # containing the marginalied data from the composite experiment diff --git a/qiskit_experiments/framework/composite/composite_experiment.py b/qiskit_experiments/framework/composite/composite_experiment.py index 955ef9bedd..c9dc7ec339 100644 --- a/qiskit_experiments/framework/composite/composite_experiment.py +++ b/qiskit_experiments/framework/composite/composite_experiment.py @@ -87,6 +87,15 @@ def copy(self) -> "BaseExperiment": ret = super().copy() # Recursively call copy of component experiments ret._experiments = [exp.copy() for exp in self._experiments] + + # Check if the analysis in CompositeAnalysis was a reference to the + # original component experiment analyses and if so update the copies + # to preserve this relationship + if isinstance(self.analysis, CompositeAnalysis): + for i, orig_exp in enumerate(self._experiments): + if orig_exp.analysis is self.analysis._analyses[i]: + # Update copies analysis with reference to experiment analysis + ret.analysis._analyses[i] = ret._experiments[i].analysis return ret def _set_backend(self, backend): diff --git a/releasenotes/notes/fix-composite-copy-a869e9773f6a4d48.yaml b/releasenotes/notes/fix-composite-copy-a869e9773f6a4d48.yaml new file mode 100644 index 0000000000..66d844e5c9 --- /dev/null +++ b/releasenotes/notes/fix-composite-copy-a869e9773f6a4d48.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fix :meth:`.ParallelExperiment.copy` and :meth:`.BatchExperiment.copy` + so that the copies preserves any references between the original + component experiments analysis classes and the :class:`.CompositeAnalysis` + classes component analysis classes. + - | + Fixes :meth:`.CompositeAnalysis.copy` to recursively make a copy of the + component analysis classes. diff --git a/test/test_composite.py b/test/test_composite.py index 8df60b2c03..881f62a36e 100644 --- a/test/test_composite.py +++ b/test/test_composite.py @@ -187,6 +187,35 @@ def test_composite_copy(self): self.check_attributes(new_instance) self.assertEqual(new_instance.parent_id, None) + def test_composite_copy_analysis_ref(self): + """Test copy of composite expeirment preserves component analysis refs""" + + class Analysis(FakeAnalysis): + """Fake analysis class with options""" + + @classmethod + def _default_options(cls): + opts = super()._default_options() + opts.option1 = None + opts.option2 = None + return opts + + exp1 = FakeExperiment([0]) + exp1.analysis = Analysis() + exp2 = FakeExperiment([1]) + exp2.analysis = Analysis() + + # Generate a copy + par_exp = ParallelExperiment([exp1, exp2]).copy() + comp_exp0 = par_exp.component_experiment(0) + comp_exp1 = par_exp.component_experiment(1) + comp_an0 = par_exp.analysis.component_analysis(0) + comp_an1 = par_exp.analysis.component_analysis(1) + + # Check reference of analysis is preserved + self.assertTrue(comp_exp0.analysis is comp_an0) + self.assertTrue(comp_exp1.analysis is comp_an1) + def test_nested_composite(self): """ Test nested parallel experiments.