From d526f897fe0feb1272a0a9e892d21c36dbc83cc2 Mon Sep 17 00:00:00 2001 From: musasina Date: Sat, 14 Oct 2023 11:59:36 +0300 Subject: [PATCH 01/26] Refactoring _run_analysis in composite_analysis #1268 --- .../framework/composite/composite_analysis.py | 60 +++------- .../framework/experiment_data.py | 112 ++++++++++++++++++ 2 files changed, 128 insertions(+), 44 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_analysis.py b/qiskit_experiments/framework/composite/composite_analysis.py index 85e8baf0a0..bc06ece2cd 100644 --- a/qiskit_experiments/framework/composite/composite_analysis.py +++ b/qiskit_experiments/framework/composite/composite_analysis.py @@ -124,13 +124,27 @@ def run( def _run_analysis(self, experiment_data: ExperimentData): # Return list of experiment data containers for each component experiment # containing the marginalized data from the composite experiment - component_expdata = self._component_experiment_data(experiment_data) + component_expdata = [] + if not self._flatten_results: + # Retrieve child data for component experiments for updating + component_index = experiment_data.metadata.get("component_child_index", []) + if not component_index: + raise AnalysisError("Unable to extract component child experiment data") + component_expdata = [experiment_data.child_data(i) for i in component_index] + else: + # Initialize temporary ExperimentData containers for + # each component experiment to analysis on. These will + # not be saved but results and figures will be collected + # from them + component_expdata = self._initialize_component_experiment_data(experiment_data) + + experiment_data._add_data(experiment_data.child_data(),experiment_data.data()) # Run the component analysis on each component data for i, sub_expdata in enumerate(component_expdata): # Since copy for replace result is handled at the parent level # we always run with replace result on component analysis - self._analyses[i].run(sub_expdata, replace_results=True) + sub_expdata._result_data = self._analyses[i].run(sub_expdata, replace_results=True)._result_data # Analysis is running in parallel so we add loop to wait # for all component analysis to finish before returning @@ -147,48 +161,6 @@ def _run_analysis(self, experiment_data: ExperimentData): return analysis_results, figures return [], [] - def _component_experiment_data(self, experiment_data: ExperimentData) -> List[ExperimentData]: - """Return a list of marginalized experiment data for component experiments. - - Args: - experiment_data: a composite experiment data container. - - Returns: - The list of analysis-ready marginalized experiment data for each - component experiment. - - Raises: - AnalysisError: If the component experiment data cannot be extracted. - """ - if not self._flatten_results: - # Retrieve child data for component experiments for updating - component_index = experiment_data.metadata.get("component_child_index", []) - if not component_index: - raise AnalysisError("Unable to extract component child experiment data") - component_expdata = [experiment_data.child_data(i) for i in component_index] - else: - # Initialize temporary ExperimentData containers for - # each component experiment to analysis on. These will - # not be saved but results and figures will be collected - # from them - component_expdata = self._initialize_component_experiment_data(experiment_data) - - # Compute marginalize data for each component experiment - marginalized_data = self._marginalized_component_data(experiment_data.data()) - - # Add the marginalized component data and component job metadata - # to each component child experiment. Note that this will clear - # any currently stored data in the experiment. Since copying of - # child data is handled by the `replace_results` kwarg of the - # parent container it is safe to always clear and replace the - # results of child containers in this step - for sub_expdata, sub_data in zip(component_expdata, marginalized_data): - # Clear any previously stored data and add marginalized data - sub_expdata._result_data.clear() - sub_expdata.add_data(sub_data) - - return component_expdata - def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[Dict]]: """Return marginalized data for component experiments. diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 20fab44cc8..76b836bebc 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -38,6 +38,8 @@ from matplotlib import pyplot from matplotlib.figure import Figure as MatplotlibFigure from qiskit.result import Result +from qiskit.result import marginal_distribution +from qiskit.result.postprocess import format_counts_memory from qiskit.providers.jobstatus import JobStatus, JOB_FINAL_STATES from qiskit.exceptions import QiskitError from qiskit.providers import Job, Backend, Provider @@ -795,6 +797,116 @@ def add_data( self._add_result_data(datum) else: raise TypeError(f"Invalid data type {type(datum)}.") + + def _add_data( + self, + component_expdata: List[ExperimentData], + data: Union[Result, List[Result], Dict, List[Dict]], + ) -> None: + """Add experiment data. + + Args: + data: Experiment data to add. Several types are accepted for convenience: + + * Result: Add data from this ``Result`` object. + * List[Result]: Add data from the ``Result`` objects. + * Dict: Add this data. + * List[Dict]: Add this list of data. + + Raises: + TypeError: If the input data type is invalid. + """ + #TODO: Continue from here + + if not isinstance(data, list): + data = [data] + + # self._marginalized_component_data() + # Directly add non-job data + marginalized_data = self._marginalized_component_data(data) + + with self._result_data.lock: + for sub_expdata, sub_data in zip(component_expdata, marginalized_data): + # Clear any previously stored data and add marginalized data + sub_expdata._result_data.clear() + sub_expdata.add_data(sub_data) + + def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[Dict]]: + """Return marginalized data for component experiments. + + Args: + composite_data: a list of composite experiment circuit data. + + Returns: + A List of lists of marginalized circuit data for each component + experiment in the composite experiment. + """ + # Marginalize data + marginalized_data = {} + for datum in composite_data: + metadata = datum.get("metadata", {}) + + # Add marginalized data to sub experiments + if "composite_clbits" in metadata: + composite_clbits = metadata["composite_clbits"] + else: + composite_clbits = None + + # Pre-process the memory if any to avoid redundant calls to format_counts_memory + f_memory = self._format_memory(datum, composite_clbits) + + for i, index in enumerate(metadata["composite_index"]): + if index not in marginalized_data: + # Initialize data list for marginalized + marginalized_data[index] = [] + sub_data = {"metadata": metadata["composite_metadata"][i]} + if "counts" in datum: + if composite_clbits is not None: + sub_data["counts"] = marginal_distribution( + counts=datum["counts"], + indices=composite_clbits[i], + ) + else: + sub_data["counts"] = datum["counts"] + if "memory" in datum: + if composite_clbits is not None: + # level 2 + if f_memory is not None: + idx = slice( + -1 - composite_clbits[i][-1], -composite_clbits[i][0] or None + ) + sub_data["memory"] = [shot[idx] for shot in f_memory] + # level 1 + else: + mem = np.array(datum["memory"]) + + # Averaged level 1 data + if len(mem.shape) == 2: + sub_data["memory"] = mem[composite_clbits[i]].tolist() + # Single-shot level 1 data + if len(mem.shape) == 3: + sub_data["memory"] = mem[:, composite_clbits[i]].tolist() + else: + sub_data["memory"] = datum["memory"] + marginalized_data[index].append(sub_data) + + # Sort by index + return [marginalized_data[i] for i in sorted(marginalized_data.keys())] + + @staticmethod + def _format_memory(datum: Dict, composite_clbits: List): + """A helper method to convert level 2 memory (if it exists) to bit-string format.""" + f_memory = None + if ( + "memory" in datum + and composite_clbits is not None + and isinstance(datum["memory"][0], str) + ): + num_cbits = 1 + max(cbit for cbit_list in composite_clbits for cbit in cbit_list) + header = {"memory_slots": num_cbits} + f_memory = list(format_counts_memory(shot, header) for shot in datum["memory"]) + + return f_memory def add_jobs( self, From 09a3c99967be07b74416f4f2708bfb7b09e41553 Mon Sep 17 00:00:00 2001 From: musasina Date: Tue, 17 Oct 2023 15:44:06 +0300 Subject: [PATCH 02/26] Updated according to @nkanazawa1989 's suggestion #1268 --- .../framework/experiment_data.py | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 76b836bebc..9666f2660d 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -40,6 +40,7 @@ from qiskit.result import Result from qiskit.result import marginal_distribution from qiskit.result.postprocess import format_counts_memory +from qiskit.result.utils import marginal_memory from qiskit.providers.jobstatus import JobStatus, JOB_FINAL_STATES from qiskit.exceptions import QiskitError from qiskit.providers import Job, Backend, Provider @@ -794,7 +795,10 @@ def add_data( if isinstance(datum, dict): self._result_data.append(datum) elif isinstance(datum, Result): - self._add_result_data(datum) + if datum["metadata"]: + self._set_child_data(datum["metadata"]._metadata()) + else: + self._add_result_data(datum) else: raise TypeError(f"Invalid data type {type(datum)}.") @@ -853,7 +857,13 @@ def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[ composite_clbits = None # Pre-process the memory if any to avoid redundant calls to format_counts_memory - f_memory = self._format_memory(datum, composite_clbits) + f_memory = None + if ( + "memory" in datum + and composite_clbits is not None + and isinstance(datum["memory"][0], str) + ): + f_memory = marginal_memory(datum["memory"], composite_clbits) for i, index in enumerate(metadata["composite_index"]): if index not in marginalized_data: @@ -892,22 +902,7 @@ def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[ # Sort by index return [marginalized_data[i] for i in sorted(marginalized_data.keys())] - - @staticmethod - def _format_memory(datum: Dict, composite_clbits: List): - """A helper method to convert level 2 memory (if it exists) to bit-string format.""" - f_memory = None - if ( - "memory" in datum - and composite_clbits is not None - and isinstance(datum["memory"][0], str) - ): - num_cbits = 1 + max(cbit for cbit_list in composite_clbits for cbit in cbit_list) - header = {"memory_slots": num_cbits} - f_memory = list(format_counts_memory(shot, header) for shot in datum["memory"]) - - return f_memory - + def add_jobs( self, jobs: Union[Job, List[Job]], From 24b210ced8a269724a00bb757037b86bc6d4c6fd Mon Sep 17 00:00:00 2001 From: musasina Date: Tue, 24 Oct 2023 13:33:53 +0300 Subject: [PATCH 03/26] Updated _add_data #1268 --- qiskit_experiments/framework/experiment_data.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 9666f2660d..94a11f176c 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -833,8 +833,19 @@ def _add_data( for sub_expdata, sub_data in zip(component_expdata, marginalized_data): # Clear any previously stored data and add marginalized data sub_expdata._result_data.clear() + for datum in sub_data: + self.__reacher_composite_metadata(datum) sub_expdata.add_data(sub_data) + def __reacher_composite_metadata(self,data : Dict)->List: + if data.get("composite_metadata"): + for datum in data.get("composite_metadata"): + self.__reacher_composite_metadata(datum) + self._add_data(datum.child_data(),datum["composite_metadata"]) + else: + data["composite_metadata"] = [ExperimentData()] + return + def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[Dict]]: """Return marginalized data for component experiments. From cbf544a4832e30423e4f46d4fbabbf9df303fc34 Mon Sep 17 00:00:00 2001 From: musasina Date: Tue, 24 Oct 2023 13:42:24 +0300 Subject: [PATCH 04/26] Updated add_data for early initialization #1268 --- .../framework/experiment_data.py | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 94a11f176c..788b753f6d 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -770,6 +770,48 @@ def add_data( ) -> None: """Add experiment data. + Args: + data: Experiment data to add. Several types are accepted for convenience: + + * Result: Add data from this ``Result`` object. + * List[Result]: Add data from the ``Result`` objects. + * Dict: Add this data. + * List[Dict]: Add this list of data. + + Raises: + TypeError: If the input data type is invalid. + """ + if any(not future.done() for future in self._analysis_futures.values()): + LOG.warning( + "Not all analysis has finished running. Adding new data may " + "create unexpected analysis results." + ) + if not isinstance(data, list): + data = [data] + + # Directly add non-job data + with self._result_data.lock: + for datum in data: + if isinstance(datum, dict): + if datum.get("composite_metadata"): + self._add_data(datum.child_data(),datum["composite_metadata"]) + self._result_data.append(datum) + else: + datum["composite_metadata"] = [ExperimentData()] + elif isinstance(datum, Result): + if datum["metadata"]: + self._set_child_data(datum["metadata"]._metadata()) + else: + self._add_result_data(datum) + else: + raise TypeError(f"Invalid data type {type(datum)}.") + + def __add_data( + self, + data: Union[Result, List[Result], Dict, List[Dict]], + ) -> None: + """Add experiment data. + Args: data: Experiment data to add. Several types are accepted for convenience: @@ -835,7 +877,7 @@ def _add_data( sub_expdata._result_data.clear() for datum in sub_data: self.__reacher_composite_metadata(datum) - sub_expdata.add_data(sub_data) + sub_expdata.__add_data(sub_data) def __reacher_composite_metadata(self,data : Dict)->List: if data.get("composite_metadata"): From e0924af37efaba7579f0b8f6d774e31ec2269223 Mon Sep 17 00:00:00 2001 From: musasina Date: Fri, 27 Oct 2023 14:54:21 +0300 Subject: [PATCH 05/26] passed test to revert changes --- qiskit_experiments/framework/experiment_data.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 788b753f6d..e8908f4b47 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -790,14 +790,12 @@ def add_data( data = [data] # Directly add non-job data + with self._result_data.lock: for datum in data: if isinstance(datum, dict): - if datum.get("composite_metadata"): - self._add_data(datum.child_data(),datum["composite_metadata"]) + if datum["metadata"].get("composite_metadata"): self._result_data.append(datum) - else: - datum["composite_metadata"] = [ExperimentData()] elif isinstance(datum, Result): if datum["metadata"]: self._set_child_data(datum["metadata"]._metadata()) From 145ae4525431ee798d2d76bb018f9460b6f4e7c7 Mon Sep 17 00:00:00 2001 From: musasina Date: Sun, 29 Oct 2023 12:34:39 +0300 Subject: [PATCH 06/26] commit before pull --- .../framework/experiment_data.py | 43 +++++++++++-------- test/framework/test_composite.py | 2 +- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index e8908f4b47..972eea5219 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -781,6 +781,7 @@ def add_data( Raises: TypeError: If the input data type is invalid. """ + if any(not future.done() for future in self._analysis_futures.values()): LOG.warning( "Not all analysis has finished running. Adding new data may " @@ -788,19 +789,19 @@ def add_data( ) if not isinstance(data, list): data = [data] - + print(data) # Directly add non-job data - with self._result_data.lock: for datum in data: if isinstance(datum, dict): if datum["metadata"].get("composite_metadata"): - self._result_data.append(datum) + tmp_exp_data = ExperimentData() + marginalized_data = self._marginalized_component_data([datum]) + for inner_datum in marginalized_data: + tmp_exp_data.__add_data(inner_datum) + self._set_child_data([tmp_exp_data]) elif isinstance(datum, Result): - if datum["metadata"]: - self._set_child_data(datum["metadata"]._metadata()) - else: - self._add_result_data(datum) + self.__add_data(datum) else: raise TypeError(f"Invalid data type {type(datum)}.") @@ -873,19 +874,8 @@ def _add_data( for sub_expdata, sub_data in zip(component_expdata, marginalized_data): # Clear any previously stored data and add marginalized data sub_expdata._result_data.clear() - for datum in sub_data: - self.__reacher_composite_metadata(datum) sub_expdata.__add_data(sub_data) - def __reacher_composite_metadata(self,data : Dict)->List: - if data.get("composite_metadata"): - for datum in data.get("composite_metadata"): - self.__reacher_composite_metadata(datum) - self._add_data(datum.child_data(),datum["composite_metadata"]) - else: - data["composite_metadata"] = [ExperimentData()] - return - def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[Dict]]: """Return marginalized data for component experiments. @@ -914,7 +904,7 @@ def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[ and composite_clbits is not None and isinstance(datum["memory"][0], str) ): - f_memory = marginal_memory(datum["memory"], composite_clbits) + f_memory = self._format_memory(datum, composite_clbits) for i, index in enumerate(metadata["composite_index"]): if index not in marginalized_data: @@ -954,6 +944,21 @@ def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[ # Sort by index return [marginalized_data[i] for i in sorted(marginalized_data.keys())] + @staticmethod + def _format_memory(datum: Dict, composite_clbits: List): + """A helper method to convert level 2 memory (if it exists) to bit-string format.""" + f_memory = None + if ( + "memory" in datum + and composite_clbits is not None + and isinstance(datum["memory"][0], str) + ): + num_cbits = 1 + max(cbit for cbit_list in composite_clbits for cbit in cbit_list) + header = {"memory_slots": num_cbits} + f_memory = list(format_counts_memory(shot, header) for shot in datum["memory"]) + + return f_memory + def add_jobs( self, jobs: Union[Job, List[Job]], diff --git a/test/framework/test_composite.py b/test/framework/test_composite.py index fc0d126b2f..8419a105f8 100644 --- a/test/framework/test_composite.py +++ b/test/framework/test_composite.py @@ -651,10 +651,10 @@ def test_composite_count_memory_marginalization(self, memory): } test_data.add_data(datum) - sub_data = CompositeAnalysis([], flatten_results=False)._marginalized_component_data( test_data.data() ) + # print(sub_data) expected = [ [ { From 5c59e08790ec0d97e9f2dc4110444f950d36adfa Mon Sep 17 00:00:00 2001 From: musasina Date: Sun, 29 Oct 2023 12:58:49 +0300 Subject: [PATCH 07/26] Updated add_data method #1268 --- qiskit_experiments/framework/experiment_data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 972eea5219..950160b92d 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -789,7 +789,7 @@ def add_data( ) if not isinstance(data, list): data = [data] - print(data) + # Directly add non-job data with self._result_data.lock: for datum in data: @@ -800,6 +800,8 @@ def add_data( for inner_datum in marginalized_data: tmp_exp_data.__add_data(inner_datum) self._set_child_data([tmp_exp_data]) + else: + self._result_data.append(datum) elif isinstance(datum, Result): self.__add_data(datum) else: From cc0b89a7eea9a23112cf0956f3430a79019439f2 Mon Sep 17 00:00:00 2001 From: musasina Date: Sat, 4 Nov 2023 16:16:33 +0300 Subject: [PATCH 08/26] Updated add_data method #1268 --- .../framework/experiment_data.py | 29 +++++++++++++++---- test/framework/test_composite.py | 3 +- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 950160b92d..a5c1c491b4 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -792,20 +792,39 @@ def add_data( # Directly add non-job data with self._result_data.lock: + tmp_exp_data = ExperimentData() + composite_flag = False + experiment_seperator = {} for datum in data: if isinstance(datum, dict): - if datum["metadata"].get("composite_metadata"): - tmp_exp_data = ExperimentData() + if "composite_metadata" in datum["metadata"]: + composite_flag = True marginalized_data = self._marginalized_component_data([datum]) for inner_datum in marginalized_data: - tmp_exp_data.__add_data(inner_datum) - self._set_child_data([tmp_exp_data]) + #print(inner_datum) + if "experiment_type" in inner_datum[0]["metadata"]: + if inner_datum[0]["metadata"]["experiment_type"] in experiment_seperator: + experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]].add_data(inner_datum[0]) + else: + experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]] = ExperimentData() + experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]].add_data(inner_datum[0]) else: self._result_data.append(datum) elif isinstance(datum, Result): - self.__add_data(datum) + if datum["metadata"]: + self._set_child_data(datum["metadata"]._metadata()) + else: + self._add_result_data(datum) else: raise TypeError(f"Invalid data type {type(datum)}.") + if composite_flag: + tmp_exp_data._set_child_data(list(experiment_seperator.values())) + self._set_child_data([tmp_exp_data]) + for exp_data in self._child_data.values(): + for sub_exp_data in exp_data.child_data(): + print(sub_exp_data.data()) + print(self.data()) + def __add_data( self, diff --git a/test/framework/test_composite.py b/test/framework/test_composite.py index 8419a105f8..c806345d96 100644 --- a/test/framework/test_composite.py +++ b/test/framework/test_composite.py @@ -654,7 +654,7 @@ def test_composite_count_memory_marginalization(self, memory): sub_data = CompositeAnalysis([], flatten_results=False)._marginalized_component_data( test_data.data() ) - # print(sub_data) + #print([exp_data.data() for exp_data in test_data.child_data()]) expected = [ [ { @@ -671,7 +671,6 @@ def test_composite_count_memory_marginalization(self, memory): } ], ] - self.assertListEqual(sub_data, expected) def test_composite_single_kerneled_memory_marginalization(self): From 2dbba8ac6a58dcbd47e6da17bbe586c2eb69bd77 Mon Sep 17 00:00:00 2001 From: musasina Date: Wed, 15 Nov 2023 06:14:22 +0300 Subject: [PATCH 09/26] Passed test new start --- .../framework/composite/composite_analysis.py | 3 ++- qiskit_experiments/framework/experiment_data.py | 10 +++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_analysis.py b/qiskit_experiments/framework/composite/composite_analysis.py index bc06ece2cd..d316fd7156 100644 --- a/qiskit_experiments/framework/composite/composite_analysis.py +++ b/qiskit_experiments/framework/composite/composite_analysis.py @@ -138,7 +138,8 @@ def _run_analysis(self, experiment_data: ExperimentData): # from them component_expdata = self._initialize_component_experiment_data(experiment_data) - experiment_data._add_data(experiment_data.child_data(),experiment_data.data()) + + experiment_data._add_data(component_expdata,experiment_data.data()) # Run the component analysis on each component data for i, sub_expdata in enumerate(component_expdata): diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index a5c1c491b4..fad9d16439 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -808,8 +808,9 @@ def add_data( else: experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]] = ExperimentData() experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]].add_data(inner_datum[0]) - else: - self._result_data.append(datum) + + self._result_data.append(datum) + elif isinstance(datum, Result): if datum["metadata"]: self._set_child_data(datum["metadata"]._metadata()) @@ -820,11 +821,6 @@ def add_data( if composite_flag: tmp_exp_data._set_child_data(list(experiment_seperator.values())) self._set_child_data([tmp_exp_data]) - for exp_data in self._child_data.values(): - for sub_exp_data in exp_data.child_data(): - print(sub_exp_data.data()) - print(self.data()) - def __add_data( self, From e7f46c3af1918d2638d1dbd36a983804036754bf Mon Sep 17 00:00:00 2001 From: musasina Date: Wed, 15 Nov 2023 06:40:54 +0300 Subject: [PATCH 10/26] Updated add_data tests passed #1268 --- qiskit_experiments/framework/experiment_data.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index fad9d16439..96cc470096 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -808,8 +808,8 @@ def add_data( else: experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]] = ExperimentData() experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]].add_data(inner_datum[0]) - - self._result_data.append(datum) + + self._result_data.append(datum) elif isinstance(datum, Result): if datum["metadata"]: @@ -821,6 +821,8 @@ def add_data( if composite_flag: tmp_exp_data._set_child_data(list(experiment_seperator.values())) self._set_child_data([tmp_exp_data]) + + return tmp_exp_data def __add_data( self, From 9eb2dba0b2f591fc55eafac41309afa668a890e3 Mon Sep 17 00:00:00 2001 From: musasina Date: Wed, 15 Nov 2023 06:58:38 +0300 Subject: [PATCH 11/26] Updated add_data tests passed #1268 --- .../framework/experiment_data.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 3ea2c97d0d..59ed376e9c 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -808,8 +808,20 @@ def add_data( else: experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]] = ExperimentData() experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]].add_data(inner_datum[0]) - - self._result_data.append(datum) + + elif "composite_metadata" in datum: + composite_flag = True + marginalized_data = self._marginalized_component_data([datum]) + for inner_datum in marginalized_data: + #print(inner_datum) + if "experiment_type" in inner_datum[0]["metadata"]: + if inner_datum[0]["metadata"]["experiment_type"] in experiment_seperator: + experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]].add_data(inner_datum[0]) + else: + experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]] = ExperimentData() + experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]].add_data(inner_datum[0]) + + self._result_data.append(datum) elif isinstance(datum, Result): if datum["metadata"]: @@ -821,8 +833,6 @@ def add_data( if composite_flag: tmp_exp_data._set_child_data(list(experiment_seperator.values())) self._set_child_data([tmp_exp_data]) - - return tmp_exp_data def __add_data( self, @@ -887,6 +897,7 @@ def _add_data( # self._marginalized_component_data() # Directly add non-job data + marginalized_data = self._marginalized_component_data(data) with self._result_data.lock: From 0bd3a186d2542ddec6997d3fa270441e0bd5318e Mon Sep 17 00:00:00 2001 From: musasina Date: Sun, 10 Dec 2023 19:10:52 +0300 Subject: [PATCH 12/26] Updated add_data and deprecated _add_data #1268 --- .../framework/composite/composite_analysis.py | 4 +- .../framework/experiment_data.py | 81 +++++-------------- 2 files changed, 24 insertions(+), 61 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_analysis.py b/qiskit_experiments/framework/composite/composite_analysis.py index e818154f65..69819c192a 100644 --- a/qiskit_experiments/framework/composite/composite_analysis.py +++ b/qiskit_experiments/framework/composite/composite_analysis.py @@ -157,13 +157,13 @@ def _run_analysis(self, experiment_data: ExperimentData): component_expdata = self._initialize_component_experiment_data(experiment_data) - experiment_data._add_data(component_expdata,experiment_data.data()) + experiment_data.add_data(experiment_data.data()) # Run the component analysis on each component data for i, sub_expdata in enumerate(component_expdata): # Since copy for replace result is handled at the parent level # we always run with replace result on component analysis - sub_expdata._result_data = self._analyses[i].run(sub_expdata, replace_results=True)._result_data + self._analyses[i].run(sub_expdata, replace_results=True) # Analysis is running in parallel so we add loop to wait # for all component analysis to finish before returning diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 59ed376e9c..3506b3530e 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -22,7 +22,7 @@ from concurrent import futures from threading import Event from functools import wraps, singledispatch -from collections import deque +from collections import deque, defaultdict import contextlib import copy import uuid @@ -789,39 +789,25 @@ def add_data( ) if not isinstance(data, list): data = [data] - + if data != [] and isinstance(data[0],dict): + marginalized_data = self._marginalized_component_data(data) # Directly add non-job data with self._result_data.lock: tmp_exp_data = ExperimentData() composite_flag = False - experiment_seperator = {} + experiment_seperator = defaultdict(lambda : ExperimentData()) for datum in data: if isinstance(datum, dict): - if "composite_metadata" in datum["metadata"]: + if "metadata" in datum and "composite_metadata" in datum["metadata"]: composite_flag = True - marginalized_data = self._marginalized_component_data([datum]) - for inner_datum in marginalized_data: - #print(inner_datum) - if "experiment_type" in inner_datum[0]["metadata"]: - if inner_datum[0]["metadata"]["experiment_type"] in experiment_seperator: - experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]].add_data(inner_datum[0]) - else: - experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]] = ExperimentData() - experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]].add_data(inner_datum[0]) - + experiment_seperator[datum["metadata"]["experiment_type"]].add_data(datum["metadata"]["composite_metadata"]) + elif "composite_metadata" in datum: composite_flag = True - marginalized_data = self._marginalized_component_data([datum]) - for inner_datum in marginalized_data: - #print(inner_datum) - if "experiment_type" in inner_datum[0]["metadata"]: - if inner_datum[0]["metadata"]["experiment_type"] in experiment_seperator: - experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]].add_data(inner_datum[0]) - else: - experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]] = ExperimentData() - experiment_seperator[inner_datum[0]["metadata"]["experiment_type"]].add_data(inner_datum[0]) + experiment_seperator[datum["experiment_type"]].add_data(datum["composite_metadata"]) - self._result_data.append(datum) + if datum not in self._result_data: + self._result_data.append(datum) elif isinstance(datum, Result): if datum["metadata"]: @@ -830,9 +816,17 @@ def add_data( self._add_result_data(datum) else: raise TypeError(f"Invalid data type {type(datum)}.") + if composite_flag: + for sub_expdata, sub_data in zip(self.child_data(), marginalized_data): + # Clear any previously stored data and add marginalized data + sub_expdata._result_data.clear() + sub_expdata.__add_data(sub_data) tmp_exp_data._set_child_data(list(experiment_seperator.values())) - self._set_child_data([tmp_exp_data]) + if self._child_data.values() != []: + self.add_child_data(tmp_exp_data) + else: + self._set_child_data([tmp_exp_data]) def __add_data( self, @@ -871,40 +865,6 @@ def __add_data( self._add_result_data(datum) else: raise TypeError(f"Invalid data type {type(datum)}.") - - def _add_data( - self, - component_expdata: List[ExperimentData], - data: Union[Result, List[Result], Dict, List[Dict]], - ) -> None: - """Add experiment data. - - Args: - data: Experiment data to add. Several types are accepted for convenience: - - * Result: Add data from this ``Result`` object. - * List[Result]: Add data from the ``Result`` objects. - * Dict: Add this data. - * List[Dict]: Add this list of data. - - Raises: - TypeError: If the input data type is invalid. - """ - #TODO: Continue from here - - if not isinstance(data, list): - data = [data] - - # self._marginalized_component_data() - # Directly add non-job data - - marginalized_data = self._marginalized_component_data(data) - - with self._result_data.lock: - for sub_expdata, sub_data in zip(component_expdata, marginalized_data): - # Clear any previously stored data and add marginalized data - sub_expdata._result_data.clear() - sub_expdata.__add_data(sub_data) def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[Dict]]: """Return marginalized data for component experiments. @@ -936,6 +896,9 @@ def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[ ): f_memory = self._format_memory(datum, composite_clbits) + if "composite_index" not in metadata: + continue + for i, index in enumerate(metadata["composite_index"]): if index not in marginalized_data: # Initialize data list for marginalized From 5e4b9d2d38b7d3d0cbd56dc2aff25e0fb10127a9 Mon Sep 17 00:00:00 2001 From: musasina Date: Sun, 10 Dec 2023 19:51:51 +0300 Subject: [PATCH 13/26] Updated add_data and _add_result_data, deprecated _add_data #1268 --- qiskit_experiments/framework/experiment_data.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 3506b3530e..3a69d79427 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -1208,6 +1208,7 @@ def _add_result_data(self, result: Result, job_id: Optional[str] = None) -> None self.job_ids.append(job_id) with self._result_data.lock: # Lock data while adding all result data + results = [] for i, _ in enumerate(result.results): data = result.data(i) data["job_id"] = job_id @@ -1221,7 +1222,9 @@ def _add_result_data(self, result: Result, job_id: Optional[str] = None) -> None data["meas_level"] = expr_result.meas_level if hasattr(expr_result, "meas_return"): data["meas_return"] = expr_result.meas_return - self._result_data.append(data) + results.append(data) + + self.add_data(results) def _retrieve_data(self): """Retrieve job data if missing experiment data.""" From f752a4f19b36b9a9ab10fe0ed5a1d482ba3d01b6 Mon Sep 17 00:00:00 2001 From: musasina Date: Sun, 10 Dec 2023 22:42:13 +0300 Subject: [PATCH 14/26] Updated add_data and _add_result_data, deprecated _add_data #1268 --- qiskit_experiments/framework/experiment_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 3a69d79427..307954cf3f 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -789,8 +789,7 @@ def add_data( ) if not isinstance(data, list): data = [data] - if data != [] and isinstance(data[0],dict): - marginalized_data = self._marginalized_component_data(data) + # Directly add non-job data with self._result_data.lock: tmp_exp_data = ExperimentData() @@ -818,6 +817,7 @@ def add_data( raise TypeError(f"Invalid data type {type(datum)}.") if composite_flag: + marginalized_data = self._marginalized_component_data(data) for sub_expdata, sub_data in zip(self.child_data(), marginalized_data): # Clear any previously stored data and add marginalized data sub_expdata._result_data.clear() From dd257a2813120edc9ae4f8e4ec9ddbfe124000cf Mon Sep 17 00:00:00 2001 From: musasina Date: Sun, 17 Dec 2023 20:46:21 +0300 Subject: [PATCH 15/26] Updated add_data #1268 --- qiskit_experiments/framework/experiment_data.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 307954cf3f..ff82855aeb 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -800,11 +800,18 @@ def add_data( if "metadata" in datum and "composite_metadata" in datum["metadata"]: composite_flag = True experiment_seperator[datum["metadata"]["experiment_type"]].add_data(datum["metadata"]["composite_metadata"]) - + marginalized_datum = self._marginalized_component_data([datum]) + for inner_datum in marginalized_datum: + for inner_inner_datum in inner_datum: + experiment_seperator[datum["metadata"]["experiment_type"]].__add_data([inner_inner_datum]) elif "composite_metadata" in datum: composite_flag = True experiment_seperator[datum["experiment_type"]].add_data(datum["composite_metadata"]) - + marginalized_datum = self._marginalized_component_data([datum]) + for inner_datum in marginalized_datum: + for inner_inner_datum in inner_datum: + experiment_seperator[datum["experiment_type"]].__add_data([inner_inner_datum]) + if datum not in self._result_data: self._result_data.append(datum) @@ -817,8 +824,12 @@ def add_data( raise TypeError(f"Invalid data type {type(datum)}.") if composite_flag: + + component_index = self.metadata.get("component_child_index", []) + component_expdata = [self.child_data(i) for i in component_index] marginalized_data = self._marginalized_component_data(data) - for sub_expdata, sub_data in zip(self.child_data(), marginalized_data): + + for sub_expdata, sub_data in zip(component_expdata, marginalized_data): # Clear any previously stored data and add marginalized data sub_expdata._result_data.clear() sub_expdata.__add_data(sub_data) From c79e888e57fb7843e6ef793433825cbccca6dfc1 Mon Sep 17 00:00:00 2001 From: musasina Date: Mon, 18 Dec 2023 22:40:24 +0300 Subject: [PATCH 16/26] Updated add_data, _run_analysis, composite_test #1268 --- .../framework/composite/composite_analysis.py | 6 +- .../framework/experiment_data.py | 62 ++----------------- test/framework/test_composite.py | 6 +- 3 files changed, 13 insertions(+), 61 deletions(-) diff --git a/qiskit_experiments/framework/composite/composite_analysis.py b/qiskit_experiments/framework/composite/composite_analysis.py index 69819c192a..dc56e96dbf 100644 --- a/qiskit_experiments/framework/composite/composite_analysis.py +++ b/qiskit_experiments/framework/composite/composite_analysis.py @@ -156,8 +156,12 @@ def _run_analysis(self, experiment_data: ExperimentData): # from them component_expdata = self._initialize_component_experiment_data(experiment_data) + marginalized_data = self._marginalized_component_data(experiment_data.data()) - experiment_data.add_data(experiment_data.data()) + for sub_expdata, sub_data in zip(component_expdata, marginalized_data): + # Clear any previously stored data and add marginalized data + sub_expdata._result_data.clear() + sub_expdata.add_data(sub_data) # Run the component analysis on each component data for i, sub_expdata in enumerate(component_expdata): diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index ff82855aeb..87cb53e0e8 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -803,79 +803,27 @@ def add_data( marginalized_datum = self._marginalized_component_data([datum]) for inner_datum in marginalized_datum: for inner_inner_datum in inner_datum: - experiment_seperator[datum["metadata"]["experiment_type"]].__add_data([inner_inner_datum]) + experiment_seperator[datum["metadata"]["experiment_type"]].add_data([inner_inner_datum]) elif "composite_metadata" in datum: composite_flag = True experiment_seperator[datum["experiment_type"]].add_data(datum["composite_metadata"]) marginalized_datum = self._marginalized_component_data([datum]) for inner_datum in marginalized_datum: for inner_inner_datum in inner_datum: - experiment_seperator[datum["experiment_type"]].__add_data([inner_inner_datum]) + experiment_seperator[datum["experiment_type"]].add_data([inner_inner_datum]) if datum not in self._result_data: self._result_data.append(datum) elif isinstance(datum, Result): - if datum["metadata"]: - self._set_child_data(datum["metadata"]._metadata()) - else: - self._add_result_data(datum) + self._add_result_data(datum) else: raise TypeError(f"Invalid data type {type(datum)}.") if composite_flag: - - component_index = self.metadata.get("component_child_index", []) - component_expdata = [self.child_data(i) for i in component_index] - marginalized_data = self._marginalized_component_data(data) - - for sub_expdata, sub_data in zip(component_expdata, marginalized_data): - # Clear any previously stored data and add marginalized data - sub_expdata._result_data.clear() - sub_expdata.__add_data(sub_data) - tmp_exp_data._set_child_data(list(experiment_seperator.values())) - if self._child_data.values() != []: - self.add_child_data(tmp_exp_data) - else: - self._set_child_data([tmp_exp_data]) - - def __add_data( - self, - data: Union[Result, List[Result], Dict, List[Dict]], - ) -> None: - """Add experiment data. - - Args: - data: Experiment data to add. Several types are accepted for convenience: - - * Result: Add data from this ``Result`` object. - * List[Result]: Add data from the ``Result`` objects. - * Dict: Add this data. - * List[Dict]: Add this list of data. - - Raises: - TypeError: If the input data type is invalid. - """ - if any(not future.done() for future in self._analysis_futures.values()): - LOG.warning( - "Not all analysis has finished running. Adding new data may " - "create unexpected analysis results." - ) - if not isinstance(data, list): - data = [data] - # Directly add non-job data - with self._result_data.lock: - for datum in data: - if isinstance(datum, dict): - self._result_data.append(datum) - elif isinstance(datum, Result): - if datum["metadata"]: - self._set_child_data(datum["metadata"]._metadata()) - else: - self._add_result_data(datum) - else: - raise TypeError(f"Invalid data type {type(datum)}.") + tmp_exp_data._set_child_data(list(experiment_seperator.values())) + self.add_child_data(tmp_exp_data) def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[Dict]]: """Return marginalized data for component experiments. diff --git a/test/framework/test_composite.py b/test/framework/test_composite.py index cdbea84f3b..93fbc48e37 100644 --- a/test/framework/test_composite.py +++ b/test/framework/test_composite.py @@ -920,10 +920,10 @@ def test_batch_transpile_options_integrated(self): expdata = self.batch2.run(backend, noise_model=noise_model, shots=1000) self.assertExperimentDone(expdata) - + self.assertEqual(expdata.child_data(0).analysis_results(0).value, 8) - self.assertEqual(expdata.child_data(1).child_data(0).analysis_results(0).value, 16) - self.assertEqual(expdata.child_data(1).child_data(1).analysis_results(0).value, 4) + self.assertEqual(expdata.child_data(1).child_data(1).analysis_results(0).value, 16) + self.assertEqual(expdata.child_data(1).child_data(2).analysis_results(0).value, 4) def test_separate_jobs(self): """Test the separate_job experiment option""" From 8f2127864dceef5a269d84f501d1b837ef136c9a Mon Sep 17 00:00:00 2001 From: musasina Date: Tue, 19 Dec 2023 14:08:38 +0300 Subject: [PATCH 17/26] commit before second approach --- qiskit_experiments/framework/experiment_data.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 87cb53e0e8..5e1a7a6f44 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -799,18 +799,18 @@ def add_data( if isinstance(datum, dict): if "metadata" in datum and "composite_metadata" in datum["metadata"]: composite_flag = True - experiment_seperator[datum["metadata"]["experiment_type"]].add_data(datum["metadata"]["composite_metadata"]) + experiment_seperator[datum["metadata"]["composite_index"]].add_data(datum["metadata"]["composite_metadata"]) marginalized_datum = self._marginalized_component_data([datum]) for inner_datum in marginalized_datum: for inner_inner_datum in inner_datum: - experiment_seperator[datum["metadata"]["experiment_type"]].add_data([inner_inner_datum]) + experiment_seperator[datum["metadata"]["composite_index"]].add_data([inner_inner_datum]) elif "composite_metadata" in datum: composite_flag = True - experiment_seperator[datum["experiment_type"]].add_data(datum["composite_metadata"]) + experiment_seperator[datum["composite_index"]].add_data(datum["composite_metadata"]) marginalized_datum = self._marginalized_component_data([datum]) for inner_datum in marginalized_datum: for inner_inner_datum in inner_datum: - experiment_seperator[datum["experiment_type"]].add_data([inner_inner_datum]) + experiment_seperator[datum["composite_index"]].add_data([inner_inner_datum]) if datum not in self._result_data: self._result_data.append(datum) From 73db5bdee5ed119f2a0e294a634794bc31744281 Mon Sep 17 00:00:00 2001 From: musasina Date: Tue, 19 Dec 2023 14:44:03 +0300 Subject: [PATCH 18/26] Tests passed , Finished second approach add_data #1268 --- .../framework/experiment_data.py | 66 ++++++++++++++++++- test/framework/test_composite.py | 4 +- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 5e1a7a6f44..e3ec64baf7 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -764,7 +764,7 @@ def source(self) -> Dict: # Data addition and deletion - def add_data( + def _add_data( self, data: Union[Result, List[Result], Dict, List[Dict]], ) -> None: @@ -825,6 +825,70 @@ def add_data( tmp_exp_data._set_child_data(list(experiment_seperator.values())) self.add_child_data(tmp_exp_data) + def add_data( + self, + data: Union[Result, List[Result], Dict, List[Dict]], + ) -> None: + """Add experiment data. + + Args: + data: Experiment data to add. Several types are accepted for convenience: + + * Result: Add data from this ``Result`` object. + * List[Result]: Add data from the ``Result`` objects. + * Dict: Add this data. + * List[Dict]: Add this list of data. + + Raises: + TypeError: If the input data type is invalid. + """ + + if any(not future.done() for future in self._analysis_futures.values()): + LOG.warning( + "Not all analysis has finished running. Adding new data may " + "create unexpected analysis results." + ) + if not isinstance(data, list): + data = [data] + + # Directly add non-job data + with self._result_data.lock: + + for datum in data: + if isinstance(datum, dict): + if "metadata" in datum and "composite_metadata" in datum["metadata"]: + marginalized_datum = self._marginalized_component_data([datum]) + try: + composite_index = datum["metadata"]["composite_index"] + composite_expdata = [self.child_data(i) for i in composite_index] + for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): + sub_expdata.add_data(sub_data) + except IndexError or RuntimeError: + new_child = ExperimentData() + for inner_datum in marginalized_datum: + new_child.add_data(inner_datum) + + elif "composite_metadata" in datum: + + marginalized_datum = self._marginalized_component_data([datum]) + try: + composite_index = datum["composite_index"] + composite_expdata = [self.child_data(i) for i in composite_index] + for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): + sub_expdata.add_data(sub_data) + except IndexError or RuntimeError: + new_child = ExperimentData() + for inner_datum in marginalized_datum: + new_child.add_data(inner_datum) + + self._result_data.append(datum) + + elif isinstance(datum, Result): + self._add_result_data(datum) + else: + raise TypeError(f"Invalid data type {type(datum)}.") + + def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[Dict]]: """Return marginalized data for component experiments. diff --git a/test/framework/test_composite.py b/test/framework/test_composite.py index 93fbc48e37..63e70643b2 100644 --- a/test/framework/test_composite.py +++ b/test/framework/test_composite.py @@ -922,8 +922,8 @@ def test_batch_transpile_options_integrated(self): self.assertExperimentDone(expdata) self.assertEqual(expdata.child_data(0).analysis_results(0).value, 8) - self.assertEqual(expdata.child_data(1).child_data(1).analysis_results(0).value, 16) - self.assertEqual(expdata.child_data(1).child_data(2).analysis_results(0).value, 4) + self.assertEqual(expdata.child_data(1).child_data(0).analysis_results(0).value, 16) + self.assertEqual(expdata.child_data(1).child_data(1).analysis_results(0).value, 4) def test_separate_jobs(self): """Test the separate_job experiment option""" From 1ed676e0119190b68223f394714f210a9ea60ec3 Mon Sep 17 00:00:00 2001 From: musasina Date: Wed, 20 Dec 2023 13:59:00 +0300 Subject: [PATCH 19/26] Updated add_data #1268 --- qiskit_experiments/framework/experiment_data.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index e3ec64baf7..99afbd47a4 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -64,6 +64,7 @@ from qiskit_experiments.framework.analysis_result_data import AnalysisResultData from qiskit_experiments.framework.analysis_result_table import AnalysisResultTable from qiskit_experiments.framework import BackendData +from qiskit_experiments.exceptions import AnalysisError from qiskit_experiments.database_service.exceptions import ( ExperimentDataError, ExperimentEntryNotFound, @@ -857,26 +858,31 @@ def add_data( for datum in data: if isinstance(datum, dict): if "metadata" in datum and "composite_metadata" in datum["metadata"]: + for inner_composite_datum in datum["metadata"]["composite_metadata"]: + if "composite_index" in inner_composite_datum: + self.add_data(inner_composite_datum) marginalized_datum = self._marginalized_component_data([datum]) try: composite_index = datum["metadata"]["composite_index"] composite_expdata = [self.child_data(i) for i in composite_index] for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): sub_expdata.add_data(sub_data) - except IndexError or RuntimeError: + except IndexError or RuntimeError or AnalysisError: new_child = ExperimentData() for inner_datum in marginalized_datum: new_child.add_data(inner_datum) elif "composite_metadata" in datum: - + for inner_composite_datum in datum["composite_metadata"]: + if "composite_index" in inner_composite_datum: + self.add_data(inner_composite_datum) marginalized_datum = self._marginalized_component_data([datum]) try: composite_index = datum["composite_index"] composite_expdata = [self.child_data(i) for i in composite_index] for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): sub_expdata.add_data(sub_data) - except IndexError or RuntimeError: + except IndexError or RuntimeError or AnalysisError: new_child = ExperimentData() for inner_datum in marginalized_datum: new_child.add_data(inner_datum) From 745669fd2c42c2cc34b4cc0653960da59dd11569 Mon Sep 17 00:00:00 2001 From: musasina Date: Wed, 20 Dec 2023 14:26:18 +0300 Subject: [PATCH 20/26] Tests passed second approach, Updated add_data #1268 --- .../framework/experiment_data.py | 69 +------------------ 1 file changed, 2 insertions(+), 67 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 99afbd47a4..13d131b371 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -765,67 +765,6 @@ def source(self) -> Dict: # Data addition and deletion - def _add_data( - self, - data: Union[Result, List[Result], Dict, List[Dict]], - ) -> None: - """Add experiment data. - - Args: - data: Experiment data to add. Several types are accepted for convenience: - - * Result: Add data from this ``Result`` object. - * List[Result]: Add data from the ``Result`` objects. - * Dict: Add this data. - * List[Dict]: Add this list of data. - - Raises: - TypeError: If the input data type is invalid. - """ - - if any(not future.done() for future in self._analysis_futures.values()): - LOG.warning( - "Not all analysis has finished running. Adding new data may " - "create unexpected analysis results." - ) - if not isinstance(data, list): - data = [data] - - # Directly add non-job data - with self._result_data.lock: - tmp_exp_data = ExperimentData() - composite_flag = False - experiment_seperator = defaultdict(lambda : ExperimentData()) - for datum in data: - if isinstance(datum, dict): - if "metadata" in datum and "composite_metadata" in datum["metadata"]: - composite_flag = True - experiment_seperator[datum["metadata"]["composite_index"]].add_data(datum["metadata"]["composite_metadata"]) - marginalized_datum = self._marginalized_component_data([datum]) - for inner_datum in marginalized_datum: - for inner_inner_datum in inner_datum: - experiment_seperator[datum["metadata"]["composite_index"]].add_data([inner_inner_datum]) - elif "composite_metadata" in datum: - composite_flag = True - experiment_seperator[datum["composite_index"]].add_data(datum["composite_metadata"]) - marginalized_datum = self._marginalized_component_data([datum]) - for inner_datum in marginalized_datum: - for inner_inner_datum in inner_datum: - experiment_seperator[datum["composite_index"]].add_data([inner_inner_datum]) - - if datum not in self._result_data: - self._result_data.append(datum) - - elif isinstance(datum, Result): - self._add_result_data(datum) - else: - raise TypeError(f"Invalid data type {type(datum)}.") - - if composite_flag: - - tmp_exp_data._set_child_data(list(experiment_seperator.values())) - self.add_child_data(tmp_exp_data) - def add_data( self, data: Union[Result, List[Result], Dict, List[Dict]], @@ -858,9 +797,7 @@ def add_data( for datum in data: if isinstance(datum, dict): if "metadata" in datum and "composite_metadata" in datum["metadata"]: - for inner_composite_datum in datum["metadata"]["composite_metadata"]: - if "composite_index" in inner_composite_datum: - self.add_data(inner_composite_datum) + marginalized_datum = self._marginalized_component_data([datum]) try: composite_index = datum["metadata"]["composite_index"] @@ -873,9 +810,7 @@ def add_data( new_child.add_data(inner_datum) elif "composite_metadata" in datum: - for inner_composite_datum in datum["composite_metadata"]: - if "composite_index" in inner_composite_datum: - self.add_data(inner_composite_datum) + marginalized_datum = self._marginalized_component_data([datum]) try: composite_index = datum["composite_index"] From fd6df6cbf14874b464c48840c3010838178c87d7 Mon Sep 17 00:00:00 2001 From: musasina Date: Wed, 20 Dec 2023 14:45:29 +0300 Subject: [PATCH 21/26] Test passed, recursive approach started #1268 --- qiskit_experiments/framework/experiment_data.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 13d131b371..0d1b0c45c2 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -804,13 +804,16 @@ def add_data( composite_expdata = [self.child_data(i) for i in composite_index] for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): sub_expdata.add_data(sub_data) + for inner_datum in datum["metadata"]["composite_metadata"]: + if "composite_index" in inner_datum: + for sub_expdata in composite_expdata: + sub_expdata.add_data(inner_datum) except IndexError or RuntimeError or AnalysisError: new_child = ExperimentData() for inner_datum in marginalized_datum: new_child.add_data(inner_datum) elif "composite_metadata" in datum: - marginalized_datum = self._marginalized_component_data([datum]) try: composite_index = datum["composite_index"] From 0565a1b12b7c3e911202c44ae8463938add017fc Mon Sep 17 00:00:00 2001 From: musasina Date: Thu, 21 Dec 2023 11:39:54 +0300 Subject: [PATCH 22/26] Tests passed , Updated recursive approach, Updated add_data #1268 --- qiskit_experiments/framework/experiment_data.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 0d1b0c45c2..18081fe560 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -807,25 +807,32 @@ def add_data( for inner_datum in datum["metadata"]["composite_metadata"]: if "composite_index" in inner_datum: for sub_expdata in composite_expdata: - sub_expdata.add_data(inner_datum) + self.add_data(inner_datum) except IndexError or RuntimeError or AnalysisError: new_child = ExperimentData() for inner_datum in marginalized_datum: new_child.add_data(inner_datum) + + self._result_data.append(datum) elif "composite_metadata" in datum: + marginalized_datum = self._marginalized_component_data([datum]) try: composite_index = datum["composite_index"] composite_expdata = [self.child_data(i) for i in composite_index] for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): sub_expdata.add_data(sub_data) + for inner_datum in datum["composite_metadata"]: + if "composite_index" in inner_datum: + for sub_expdata in composite_expdata: + self.add_data(inner_datum) except IndexError or RuntimeError or AnalysisError: new_child = ExperimentData() for inner_datum in marginalized_datum: new_child.add_data(inner_datum) - - self._result_data.append(datum) + else: + self._result_data.append(datum) elif isinstance(datum, Result): self._add_result_data(datum) From 814c22afe48ad3e7f9732420c1ec55b873c6024e Mon Sep 17 00:00:00 2001 From: msina_ertugrul <102359522+Musa-Sina-Ertugrul@users.noreply.github.com> Date: Thu, 28 Dec 2023 14:02:19 +0300 Subject: [PATCH 23/26] Update qiskit_experiments/framework/experiment_data.py Co-authored-by: Naoki Kanazawa --- qiskit_experiments/framework/experiment_data.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index a297ba7191..3cae242119 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -802,6 +802,8 @@ def add_data( marginalized_datum = self._marginalized_component_data([datum]) try: composite_index = datum["metadata"]["composite_index"] + while max(composite_index) > len(self._child_data): + self.add_child_data(ExperimentData()) composite_expdata = [self.child_data(i) for i in composite_index] for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): sub_expdata.add_data(sub_data) From 229692fc030d5c60902567800705acc1292dd6f0 Mon Sep 17 00:00:00 2001 From: musasina Date: Thu, 28 Dec 2023 14:04:00 +0300 Subject: [PATCH 24/26] Started on new suggestions, suggestion 1 finished #1268 --- qiskit_experiments/framework/experiment_data.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 3cae242119..2d868b5d5b 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -769,6 +769,7 @@ def source(self) -> Dict: def add_data( self, data: Union[Result, List[Result], Dict, List[Dict]], + **kwargs ) -> None: """Add experiment data. @@ -810,7 +811,7 @@ def add_data( for inner_datum in datum["metadata"]["composite_metadata"]: if "composite_index" in inner_datum: for sub_expdata in composite_expdata: - self.add_data(inner_datum) + self.add_data(inner_datum,inner_comoposite_flag=False) except IndexError or RuntimeError or AnalysisError: new_child = ExperimentData() for inner_datum in marginalized_datum: @@ -829,13 +830,17 @@ def add_data( for inner_datum in datum["composite_metadata"]: if "composite_index" in inner_datum: for sub_expdata in composite_expdata: - self.add_data(inner_datum) + self.add_data(inner_datum,inner_comoposite_flag=False) except IndexError or RuntimeError or AnalysisError: new_child = ExperimentData() for inner_datum in marginalized_datum: new_child.add_data(inner_datum) else: - self._result_data.append(datum) + try: + if kwargs["inner_comoposite_flag"]: + self._result_data.append(datum) + except KeyError: + self._result_data.append(datum) elif isinstance(datum, Result): self._add_result_data(datum) From 288d18f2b8355c03e053ecc2861d348e4b1332a9 Mon Sep 17 00:00:00 2001 From: musasina Date: Thu, 28 Dec 2023 18:04:55 +0300 Subject: [PATCH 25/26] Waiting respond, not bootstrapped exp_data appear suddenly after running analysis --- .../framework/experiment_data.py | 56 +++++++++---------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 2d868b5d5b..74cb28cdb8 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -794,47 +794,41 @@ def add_data( data = [data] # Directly add non-job data - with self._result_data.lock: + with self._result_data.lock and self._child_data.lock: for datum in data: if isinstance(datum, dict): if "metadata" in datum and "composite_metadata" in datum["metadata"]: marginalized_datum = self._marginalized_component_data([datum]) - try: - composite_index = datum["metadata"]["composite_index"] - while max(composite_index) > len(self._child_data): - self.add_child_data(ExperimentData()) - composite_expdata = [self.child_data(i) for i in composite_index] - for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): - sub_expdata.add_data(sub_data) - for inner_datum in datum["metadata"]["composite_metadata"]: - if "composite_index" in inner_datum: - for sub_expdata in composite_expdata: - self.add_data(inner_datum,inner_comoposite_flag=False) - except IndexError or RuntimeError or AnalysisError: - new_child = ExperimentData() - for inner_datum in marginalized_datum: - new_child.add_data(inner_datum) - + composite_index = datum["metadata"]["composite_index"] + max_index = max(composite_index) + while max_index > len(self._child_data) -1: + self.add_child_data(ExperimentData()) + composite_expdata = [self.child_data(i) for i in composite_index] + for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): + sub_expdata.add_data(sub_data) + for inner_datum in datum["metadata"]["composite_metadata"]: + if "composite_index" in inner_datum: + for sub_expdata in composite_expdata: + self.add_data(inner_datum,inner_comoposite_flag=False) + self._result_data.append(datum) - elif "composite_metadata" in datum: + elif "composite_metadata" in datum and "metadata" not in datum: marginalized_datum = self._marginalized_component_data([datum]) - try: - composite_index = datum["composite_index"] - composite_expdata = [self.child_data(i) for i in composite_index] - for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): - sub_expdata.add_data(sub_data) - for inner_datum in datum["composite_metadata"]: - if "composite_index" in inner_datum: - for sub_expdata in composite_expdata: - self.add_data(inner_datum,inner_comoposite_flag=False) - except IndexError or RuntimeError or AnalysisError: - new_child = ExperimentData() - for inner_datum in marginalized_datum: - new_child.add_data(inner_datum) + composite_index = datum["composite_index"] + max_index = max(composite_index) + while max(composite_index) > len(self._child_data) -1: + self.add_child_data(ExperimentData()) + composite_expdata = [self.child_data(i) for i in composite_index] + for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): + sub_expdata.add_data(sub_data) + for inner_datum in datum["composite_metadata"]: + if "composite_index" in inner_datum: + for sub_expdata in composite_expdata: + self.add_data(inner_datum,inner_comoposite_flag=False) else: try: if kwargs["inner_comoposite_flag"]: From a3abf4d286fce73fe38c8c377b5a34134761a579 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Fri, 5 Jan 2024 14:41:24 +0900 Subject: [PATCH 26/26] Fix marginalize problems --- qiskit_experiments/framework/base_analysis.py | 2 +- .../framework/composite/composite_analysis.py | 209 ++------------ .../framework/experiment_data.py | 265 ++++++++---------- 3 files changed, 135 insertions(+), 341 deletions(-) diff --git a/qiskit_experiments/framework/base_analysis.py b/qiskit_experiments/framework/base_analysis.py index acced5544d..7ead1cf574 100644 --- a/qiskit_experiments/framework/base_analysis.py +++ b/qiskit_experiments/framework/base_analysis.py @@ -167,7 +167,7 @@ def run_analysis(expdata: ExperimentData): # Clearing previous analysis data experiment_data._clear_results() - if not expdata.data(): + if not expdata.data() and not expdata.child_data(): warnings.warn("ExperimentData object data is empty.\n") # Making new analysis diff --git a/qiskit_experiments/framework/composite/composite_analysis.py b/qiskit_experiments/framework/composite/composite_analysis.py index dc56e96dbf..24175d8a7c 100644 --- a/qiskit_experiments/framework/composite/composite_analysis.py +++ b/qiskit_experiments/framework/composite/composite_analysis.py @@ -120,216 +120,39 @@ def copy(self): ret._analyses = [analysis.copy() for analysis in ret._analyses] return ret - def run( - self, - experiment_data: ExperimentData, - replace_results: bool = False, - **options, - ) -> ExperimentData: - # Make a new copy of experiment data if not updating results - if not replace_results and _requires_copy(experiment_data): - experiment_data = experiment_data.copy() - - if not self._flatten_results: - # Initialize child components if they are not initialized - # This only needs to be done if results are not being flattened - self._add_child_data(experiment_data) - - # Run analysis with replace_results = True since we have already - # created the copy if it was required - return super().run(experiment_data, replace_results=True, **options) - def _run_analysis(self, experiment_data: ExperimentData): - # Return list of experiment data containers for each component experiment - # containing the marginalized data from the composite experiment - component_expdata = [] - if not self._flatten_results: - # Retrieve child data for component experiments for updating - component_index = experiment_data.metadata.get("component_child_index", []) - if not component_index: - raise AnalysisError("Unable to extract component child experiment data") - component_expdata = [experiment_data.child_data(i) for i in component_index] - else: - # Initialize temporary ExperimentData containers for - # each component experiment to analysis on. These will - # not be saved but results and figures will be collected - # from them - component_expdata = self._initialize_component_experiment_data(experiment_data) - - marginalized_data = self._marginalized_component_data(experiment_data.data()) - - for sub_expdata, sub_data in zip(component_expdata, marginalized_data): - # Clear any previously stored data and add marginalized data - sub_expdata._result_data.clear() - sub_expdata.add_data(sub_data) + child_data = experiment_data.child_data() + + if len(self._analyses) != len(child_data): + # Child data is automatically created when composite result data is added. + # Validate that child data size matches with number of analysis entries. + raise RuntimeError( + "Number of sub-analysis and child data don't match: " + f"{len(self._analyses)} != {len(child_data)}. " + "Please check if the composite experiment and analysis are properly instantiated." + ) - # Run the component analysis on each component data - for i, sub_expdata in enumerate(component_expdata): + for sub_analysis, sub_data in zip(self._analyses, child_data): # Since copy for replace result is handled at the parent level # we always run with replace result on component analysis - self._analyses[i].run(sub_expdata, replace_results=True) + sub_analysis.run(sub_data, replace_results=True) # Analysis is running in parallel so we add loop to wait # for all component analysis to finish before returning # the parent experiment analysis results - for sub_expdata in component_expdata: - sub_expdata.block_for_results() + for sub_data in child_data: + sub_data.block_for_results() + # Optionally flatten results from all component experiments # for adding to the main experiment data container if self._flatten_results: - analysis_results, figures = self._combine_results(component_expdata) + analysis_results, figures = self._combine_results(child_data) for res in analysis_results: # Override experiment ID because entries are flattened res.experiment_id = experiment_data.experiment_id return analysis_results, figures return [], [] - def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[Dict]]: - """Return marginalized data for component experiments. - - Args: - composite_data: a list of composite experiment circuit data. - - Returns: - A List of lists of marginalized circuit data for each component - experiment in the composite experiment. - """ - # Marginalize data - marginalized_data = {} - for datum in composite_data: - metadata = datum.get("metadata", {}) - - # Add marginalized data to sub experiments - if "composite_clbits" in metadata: - composite_clbits = metadata["composite_clbits"] - else: - composite_clbits = None - - # Pre-process the memory if any to avoid redundant calls to format_counts_memory - f_memory = self._format_memory(datum, composite_clbits) - - for i, index in enumerate(metadata["composite_index"]): - if index not in marginalized_data: - # Initialize data list for marginalized - marginalized_data[index] = [] - sub_data = {"metadata": metadata["composite_metadata"][i]} - if "counts" in datum: - if composite_clbits is not None: - sub_data["counts"] = marginal_distribution( - counts=datum["counts"], - indices=composite_clbits[i], - ) - else: - sub_data["counts"] = datum["counts"] - if "memory" in datum: - if composite_clbits is not None: - # level 2 - if f_memory is not None: - idx = slice( - -1 - composite_clbits[i][-1], -composite_clbits[i][0] or None - ) - sub_data["memory"] = [shot[idx] for shot in f_memory] - # level 1 - else: - mem = np.array(datum["memory"]) - - # Averaged level 1 data - if len(mem.shape) == 2: - sub_data["memory"] = mem[composite_clbits[i]].tolist() - # Single-shot level 1 data - if len(mem.shape) == 3: - sub_data["memory"] = mem[:, composite_clbits[i]].tolist() - else: - sub_data["memory"] = datum["memory"] - marginalized_data[index].append(sub_data) - - # Sort by index - return [marginalized_data[i] for i in sorted(marginalized_data.keys())] - - @staticmethod - def _format_memory(datum: Dict, composite_clbits: List): - """A helper method to convert level 2 memory (if it exists) to bit-string format.""" - f_memory = None - if ( - "memory" in datum - and composite_clbits is not None - and isinstance(datum["memory"][0], str) - ): - num_cbits = 1 + max(cbit for cbit_list in composite_clbits for cbit in cbit_list) - header = {"memory_slots": num_cbits} - f_memory = list(format_counts_memory(shot, header) for shot in datum["memory"]) - - return f_memory - - def _add_child_data(self, experiment_data: ExperimentData): - """Save empty component experiment data as child data. - - This will initialize empty ExperimentData objects for each component - experiment and add them as child data to the main composite experiment - ExperimentData container container for saving. - - Args: - experiment_data: a composite experiment experiment data container. - """ - component_index = experiment_data.metadata.get("component_child_index", []) - if component_index: - # Child components are already initialized - return - - # Initialize the component experiment data containers and add them - # as child data to the current experiment data - child_components = self._initialize_component_experiment_data(experiment_data) - start_index = len(experiment_data.child_data()) - for i, subdata in enumerate(child_components): - experiment_data.add_child_data(subdata) - component_index.append(start_index + i) - - # Store the indices of the added child data in metadata - experiment_data.metadata["component_child_index"] = component_index - - def _initialize_component_experiment_data( - self, experiment_data: ExperimentData - ) -> List[ExperimentData]: - """Initialize empty experiment data containers for component experiments. - - Args: - experiment_data: a composite experiment experiment data container. - - Returns: - The list of experiment data containers for each component experiment - containing the component metadata, and tags, share level, and - auto save settings of the composite experiment. - """ - # Extract component experiment types and metadata so they can be - # added to the component experiment data containers - metadata = experiment_data.metadata - num_components = len(self._analyses) - experiment_types = metadata.get("component_types", [None] * num_components) - component_metadata = metadata.get("component_metadata", [{}] * num_components) - - # Create component experiments and set the backend and - # metadata for the components - component_expdata = [] - for i, _ in enumerate(self._analyses): - subdata = ExperimentData(backend=experiment_data.backend) - subdata.experiment_type = experiment_types[i] - subdata.metadata.update(component_metadata[i]) - - if self._flatten_results: - # Explicitly set auto_save to false so the temporary - # data can't accidentally be saved - subdata.auto_save = False - else: - # Copy tags, share_level and auto_save from the parent - # experiment data if results are not being flattened. - subdata.tags = experiment_data.tags - subdata.share_level = experiment_data.share_level - subdata.auto_save = experiment_data.auto_save - - component_expdata.append(subdata) - - return component_expdata - def _set_flatten_results(self): """Recursively set flatten_results to True for all composite components.""" self._flatten_results = True diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 74cb28cdb8..41d072c041 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -17,12 +17,12 @@ import logging import dataclasses import re -from typing import Dict, Optional, List, Union, Any, Callable, Tuple, TYPE_CHECKING +from typing import Dict, Optional, List, Union, Any, Callable, Tuple, Iterator, TYPE_CHECKING from datetime import datetime, timezone from concurrent import futures from threading import Event -from functools import wraps, singledispatch -from collections import deque, defaultdict +from functools import wraps, singledispatch, partial +from collections import deque import contextlib import copy import uuid @@ -40,7 +40,6 @@ from qiskit.result import Result from qiskit.result import marginal_distribution from qiskit.result.postprocess import format_counts_memory -from qiskit.result.utils import marginal_memory from qiskit.providers.jobstatus import JobStatus, JOB_FINAL_STATES from qiskit.exceptions import QiskitError from qiskit.providers import Job, Backend, Provider @@ -769,7 +768,6 @@ def source(self) -> Dict: def add_data( self, data: Union[Result, List[Result], Dict, List[Dict]], - **kwargs ) -> None: """Add experiment data. @@ -794,139 +792,116 @@ def add_data( data = [data] # Directly add non-job data - with self._result_data.lock and self._child_data.lock: - - for datum in data: - if isinstance(datum, dict): - if "metadata" in datum and "composite_metadata" in datum["metadata"]: - - marginalized_datum = self._marginalized_component_data([datum]) - composite_index = datum["metadata"]["composite_index"] - max_index = max(composite_index) - while max_index > len(self._child_data) -1: - self.add_child_data(ExperimentData()) - composite_expdata = [self.child_data(i) for i in composite_index] - for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): - sub_expdata.add_data(sub_data) - for inner_datum in datum["metadata"]["composite_metadata"]: - if "composite_index" in inner_datum: - for sub_expdata in composite_expdata: - self.add_data(inner_datum,inner_comoposite_flag=False) - - self._result_data.append(datum) - - elif "composite_metadata" in datum and "metadata" not in datum: - - marginalized_datum = self._marginalized_component_data([datum]) - composite_index = datum["composite_index"] - max_index = max(composite_index) - while max(composite_index) > len(self._child_data) -1: - self.add_child_data(ExperimentData()) - composite_expdata = [self.child_data(i) for i in composite_index] - for sub_expdata, sub_data in zip(composite_expdata, marginalized_datum): - sub_expdata.add_data(sub_data) - for inner_datum in datum["composite_metadata"]: - if "composite_index" in inner_datum: - for sub_expdata in composite_expdata: - self.add_data(inner_datum,inner_comoposite_flag=False) - else: - try: - if kwargs["inner_comoposite_flag"]: - self._result_data.append(datum) - except KeyError: - self._result_data.append(datum) - - elif isinstance(datum, Result): - self._add_result_data(datum) - else: - raise TypeError(f"Invalid data type {type(datum)}.") + for datum in data: + if isinstance(datum, dict): + self._add_canonical_dict_data(datum) + elif isinstance(datum, Result): + self._add_result_data(datum) + else: + raise TypeError(f"Invalid data type {type(datum)}.") + def _add_canonical_dict_data(self, data: dict): + """A common subroutine to store result dictionary in canonical format. - def _marginalized_component_data(self, composite_data: List[Dict]) -> List[List[Dict]]: + Args: + data: A single formatted entry of experiment results. + ExperimentData expects this data dictionary to include keys such as + metadata, counts, memory and so forth. + """ + if "metadata" in data and "composite_metadata" in data["metadata"]: + composite_index = data["metadata"]["composite_index"] + max_index = max(composite_index) + with self._child_data.lock: + while (new_idx := len(self._child_data)) <= max_index: + child_data = ExperimentData() + # Add automatically generated component experiment metadata + try: + component_metadata = self.metadata["component_metadata"][new_idx].copy() + child_data.metadata.update(component_metadata) + except (KeyError, IndexError): + pass + try: + component_type = self.metadata["component_types"][new_idx] + child_data.experiment_type = component_type + except (KeyError, IndexError): + pass + self.add_child_data(child_data) + for idx, sub_data in self._decompose_component_data(data): + self.child_data(idx).add_data(sub_data) + else: + with self._result_data.lock: + self._result_data.append(data) + + @staticmethod + def _decompose_component_data( + composite_data: dict, + ) -> Iterator[tuple[int, dict]]: """Return marginalized data for component experiments. Args: - composite_data: a list of composite experiment circuit data. + composite_data: a composite experiment result dictionary. - Returns: - A List of lists of marginalized circuit data for each component - experiment in the composite experiment. + Yields: + Tuple of composite index and result dictionary for each component experiment. """ - # Marginalize data - marginalized_data = {} - for datum in composite_data: - metadata = datum.get("metadata", {}) + metadata = composite_data.get("metadata", {}) - # Add marginalized data to sub experiments - if "composite_clbits" in metadata: - composite_clbits = metadata["composite_clbits"] + tmp_sub_data = { + k: v for k, v in composite_data.items() if k not in ("metadata", "counts", "memory") + } + composite_clbits = metadata.get("composite_clbits", None) + + if composite_clbits is not None and "memory" in composite_data: + # TODO use qiskit.result.utils.marginal_memory function implemented in Rust. + # This function expects a complex data-type ndarray for IQ data, + # while Qiskit Experiments stores IQ data in list format, i.e. [Re, Im]. + # This format is tied to the data processor module and we cannot easily switch. + # We need to overhaul the data processor and related unit tests first. + memory = composite_data["memory"] + if isinstance(memory[0], str): + n_clbits = max(sum(composite_clbits, [])) + 1 + formatter = partial(format_counts_memory, header={"memory_slots": n_clbits}) + formatted_mem = list(map(formatter, memory)) else: - composite_clbits = None - - # Pre-process the memory if any to avoid redundant calls to format_counts_memory - f_memory = None - if ( - "memory" in datum - and composite_clbits is not None - and isinstance(datum["memory"][0], str) - ): - f_memory = self._format_memory(datum, composite_clbits) - - if "composite_index" not in metadata: - continue - - for i, index in enumerate(metadata["composite_index"]): - if index not in marginalized_data: - # Initialize data list for marginalized - marginalized_data[index] = [] - sub_data = {"metadata": metadata["composite_metadata"][i]} - if "counts" in datum: - if composite_clbits is not None: - sub_data["counts"] = marginal_distribution( - counts=datum["counts"], - indices=composite_clbits[i], - ) - else: - sub_data["counts"] = datum["counts"] - if "memory" in datum: - if composite_clbits is not None: - # level 2 - if f_memory is not None: - idx = slice( - -1 - composite_clbits[i][-1], -composite_clbits[i][0] or None - ) - sub_data["memory"] = [shot[idx] for shot in f_memory] - # level 1 - else: - mem = np.array(datum["memory"]) - - # Averaged level 1 data - if len(mem.shape) == 2: - sub_data["memory"] = mem[composite_clbits[i]].tolist() - # Single-shot level 1 data - if len(mem.shape) == 3: - sub_data["memory"] = mem[:, composite_clbits[i]].tolist() - else: - sub_data["memory"] = datum["memory"] - marginalized_data[index].append(sub_data) - - # Sort by index - return [marginalized_data[i] for i in sorted(marginalized_data.keys())] - - @staticmethod - def _format_memory(datum: Dict, composite_clbits: List): - """A helper method to convert level 2 memory (if it exists) to bit-string format.""" - f_memory = None - if ( - "memory" in datum - and composite_clbits is not None - and isinstance(datum["memory"][0], str) - ): - num_cbits = 1 + max(cbit for cbit_list in composite_clbits for cbit in cbit_list) - header = {"memory_slots": num_cbits} - f_memory = list(format_counts_memory(shot, header) for shot in datum["memory"]) + formatted_mem = np.array(memory, dtype=float) + else: + formatted_mem = None - return f_memory + for i, exp_idx in enumerate(metadata["composite_index"]): + sub_data = tmp_sub_data.copy() + try: + sub_data["metadata"] = metadata["composite_metadata"][i] + except (KeyError, IndexError): + sub_data["metadata"] = {} + if "counts" in composite_data: + if composite_clbits is not None: + sub_data["counts"] = marginal_distribution( + counts=composite_data["counts"], + indices=composite_clbits[i], + ) + else: + sub_data["counts"] = composite_data["counts"] + if "memory" in composite_data: + if isinstance(formatted_mem, list): + # level 2 + idx = slice(-1 - composite_clbits[i][-1], -composite_clbits[i][0] or None) + sub_data["memory"] = [shot[idx] for shot in formatted_mem] + elif isinstance(formatted_mem, np.ndarray): + # level 1 + if len(formatted_mem.shape) == 2: + # Averaged + sub_data["memory"] = formatted_mem[composite_clbits[i]].tolist() + elif len(formatted_mem.shape) == 3: + # Single shot + sub_data["memory"] = formatted_mem[:, composite_clbits[i]].tolist() + else: + raise ValueError( + f"Invalid memory shape of {formatted_mem.shape}. " + "This data cannot be marginalized." + ) + else: + sub_data["memory"] = composite_data["memory"] + yield exp_idx, sub_data def add_jobs( self, @@ -1182,25 +1157,21 @@ def _add_result_data(self, result: Result, job_id: Optional[str] = None) -> None if job_id not in self._jobs: self._jobs[job_id] = None self.job_ids.append(job_id) - with self._result_data.lock: - # Lock data while adding all result data - results = [] - for i, _ in enumerate(result.results): - data = result.data(i) - data["job_id"] = job_id - if "counts" in data: - # Format to Counts object rather than hex dict - data["counts"] = result.get_counts(i) - expr_result = result.results[i] - if hasattr(expr_result, "header") and hasattr(expr_result.header, "metadata"): - data["metadata"] = expr_result.header.metadata - data["shots"] = expr_result.shots - data["meas_level"] = expr_result.meas_level - if hasattr(expr_result, "meas_return"): - data["meas_return"] = expr_result.meas_return - results.append(data) - - self.add_data(results) + + for i, _ in enumerate(result.results): + data = result.data(i) + data["job_id"] = job_id + if "counts" in data: + # Format to Counts object rather than hex dict + data["counts"] = result.get_counts(i) + expr_result = result.results[i] + if hasattr(expr_result, "header") and hasattr(expr_result.header, "metadata"): + data["metadata"] = expr_result.header.metadata + data["shots"] = expr_result.shots + data["meas_level"] = expr_result.meas_level + if hasattr(expr_result, "meas_return"): + data["meas_return"] = expr_result.meas_return + self._add_canonical_dict_data(data) def _retrieve_data(self): """Retrieve job data if missing experiment data."""