From 3c26036416fccf486df76c04d0dc2cc37c020b77 Mon Sep 17 00:00:00 2001
From: Helena Zhang <helena.zhang@ibm.com>
Date: Wed, 27 Mar 2024 17:09:45 -0400
Subject: [PATCH] update copy behavior

---
 .../framework/analysis_result_table.py           | 16 +++++++++++++---
 qiskit_experiments/framework/experiment_data.py  |  5 ++++-
 ...x-copy-analysis-results-e7a8381a7b720f47.yaml |  5 +++++
 test/database_service/test_db_experiment_data.py | 15 +++++++++++++++
 4 files changed, 37 insertions(+), 4 deletions(-)
 create mode 100644 releasenotes/notes/fix-copy-analysis-results-e7a8381a7b720f47.yaml

diff --git a/qiskit_experiments/framework/analysis_result_table.py b/qiskit_experiments/framework/analysis_result_table.py
index 2c84a08e2a..059923d7a1 100644
--- a/qiskit_experiments/framework/analysis_result_table.py
+++ b/qiskit_experiments/framework/analysis_result_table.py
@@ -204,16 +204,26 @@ def clear(self):
         with self._lock:
             self._data = pd.DataFrame(columns=self.DEFAULT_COLUMNS)
 
-    def copy(self):
+    def copy(self, new_ids: bool = True):
         """Create new thread-safe instance with the same data.
 
-        .. note::
-            This returns a new object with shallow copied data frame.
+        Args:
+            new_ids: Whether to generate new IDs for copied entries. Defaults to True.
+
+        Returns:
+            A new shallow copied DataFrame object.
         """
         with self._lock:
             # Hold the lock so that no data can be added
             new_instance = self.__class__()
             new_instance._data = self._data.copy(deep=False)
+            if new_ids:
+                new_instance._data["result_id"] = None
+                for idx, _ in new_instance._data.iterrows():
+                    new_instance._data.at[idx, "result_id"] = new_instance._create_unique_hash()
+                new_instance._data.index = [
+                    result_id[:8] for result_id in new_instance._data["result_id"]
+                ]
         return new_instance
 
     def _create_unique_hash(self) -> str:
diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py
index 68e4a52b0c..f73fbc1e25 100644
--- a/qiskit_experiments/framework/experiment_data.py
+++ b/qiskit_experiments/framework/experiment_data.py
@@ -2397,7 +2397,10 @@ def copy(self, copy_results: bool = True) -> "ExperimentData":
         # Copy results and figures.
         # This requires analysis callbacks to finish
         self._wait_for_futures(self._analysis_futures.values(), name="analysis")
-        new_instance._analysis_results = self._analysis_results.copy()
+        copied_results = self._analysis_results.copy()
+        # Analysis results should have experiment ID of the copied experiment
+        copied_results._data["experiment_id"] = new_instance.experiment_id
+        new_instance._analysis_results = copied_results
         with self._figures.lock:
             new_instance._figures = ThreadSafeOrderedDict()
             new_instance.add_figures(self._figures.values())
diff --git a/releasenotes/notes/fix-copy-analysis-results-e7a8381a7b720f47.yaml b/releasenotes/notes/fix-copy-analysis-results-e7a8381a7b720f47.yaml
new file mode 100644
index 0000000000..8f9a42ec45
--- /dev/null
+++ b/releasenotes/notes/fix-copy-analysis-results-e7a8381a7b720f47.yaml
@@ -0,0 +1,5 @@
+---
+fixes:
+  - |
+    Fixed a bug where a copied :class:`.ExperimentData` instance's analysis results didn't get
+    new IDs and experiement ID.
diff --git a/test/database_service/test_db_experiment_data.py b/test/database_service/test_db_experiment_data.py
index 0382d2b72a..15c69f0023 100644
--- a/test/database_service/test_db_experiment_data.py
+++ b/test/database_service/test_db_experiment_data.py
@@ -15,6 +15,8 @@
 """Test ExperimentData."""
 from test.base import QiskitExperimentsTestCase
 from test.fake_experiment import FakeExperiment
+from test.extended_equality import is_equivalent
+
 import os
 from unittest import mock
 import copy
@@ -1058,12 +1060,25 @@ def test_additional_attr(self):
     def test_copy_metadata(self):
         """Test copy metadata."""
         exp_data = FakeExperiment(experiment_type="qiskit_test").run(backend=FakeBackend())
+        self.assertExperimentDone(exp_data)
         exp_data.add_data(self._get_job_result(1))
         copied = exp_data.copy(copy_results=False)
         self.assertEqual(exp_data.data(), copied.data())
         self.assertFalse(copied.analysis_results())
         self.assertEqual(exp_data.provider, copied.provider)
 
+    def test_copy_analysis_results(self):
+        """Test copy analysis results."""
+        exp_data = FakeExperiment(experiment_type="qiskit_test").run(backend=FakeBackend())
+        self.assertExperimentDone(exp_data)
+        exp_data.add_data(self._get_job_result(1))
+        copied = exp_data.copy(copy_results=True)
+        for res in copied.analysis_results():
+            self.assertEqual(res.experiment_id, copied.experiment_id)
+        # copied analysis results should be identical to the original except for experiment ID
+        copied._analysis_results._data["experiment_id"] = exp_data.experiment_id
+        self.assertTrue(is_equivalent(exp_data.analysis_results(), copied.analysis_results()))
+
     def test_copy_figure_artifacts(self):
         """Test copy expdata figures and artifacts."""
         exp_data = FakeExperiment(experiment_type="qiskit_test").run(backend=FakeBackend())