-
Notifications
You must be signed in to change notification settings - Fork 136
Add child data support to ExperimentData #451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
2ef6591
37a3c24
970ca48
86a665f
8c8a5f3
c5debcf
92fba8c
ecf0a2a
9175928
fa935a1
3ec7f53
484fb3e
0ee07fa
e4a4d4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -13,19 +13,39 @@ | |||||
| Composite Experiment Analysis class. | ||||||
| """ | ||||||
|
|
||||||
| from qiskit.exceptions import QiskitError | ||||||
| from qiskit_experiments.framework import BaseAnalysis | ||||||
| from .composite_experiment_data import CompositeExperimentData | ||||||
| from typing import List, Dict | ||||||
| from qiskit.result import marginal_counts | ||||||
| from qiskit_experiments.framework import BaseAnalysis, ExperimentData | ||||||
|
|
||||||
|
|
||||||
| class CompositeAnalysis(BaseAnalysis): | ||||||
| """Analysis class for CompositeExperiment""" | ||||||
| """Run analysis for composite experiments. | ||||||
|
|
||||||
| __experiment_data__ = CompositeExperimentData | ||||||
| Composite experiments consist of several component experiments | ||||||
| run together in a single execution, the results of which are returned | ||||||
| as a single list of circuit result data in the :class:`ExperimentData` | ||||||
| container. Analysis of this composite circuit data involves constructing | ||||||
| a child experiment data container for each component experiment containing | ||||||
| the marginalized circuit result data for that experiment. Each component | ||||||
| child data is then analyzed using the analysis class from the corresponding | ||||||
| component experiment. | ||||||
|
|
||||||
| .. note:: | ||||||
|
|
||||||
| The child :class:`ExperimentData` for each component experiment is | ||||||
| constructed and added to the parent experiment data the first time | ||||||
| :meth:`run` is called on the composite :class:`ExperimentData`. | ||||||
|
|
||||||
| On sub-sequent called to :meth:`run` if `replace_results=True`` | ||||||
| in a addition to replace the analysis results and figures of each | ||||||
| component child experiment any previously stored child experiment | ||||||
| circuit data will be cleared and replaced with the marginalized data | ||||||
| reconstructed from the parent composite experiment data. | ||||||
| """ | ||||||
|
|
||||||
| # pylint: disable = arguments-differ | ||||||
| def _run_analysis(self, experiment_data: CompositeExperimentData, **options): | ||||||
| """Run analysis on circuit data. | ||||||
| def _run_analysis(self, experiment_data: ExperimentData, **options): | ||||||
| """Run analysis on composite experiment circuit data. | ||||||
|
|
||||||
| Args: | ||||||
| experiment_data: the experiment data to analyze. | ||||||
|
|
@@ -40,15 +60,94 @@ def _run_analysis(self, experiment_data: CompositeExperimentData, **options): | |||||
| QiskitError: if analysis is attempted on non-composite | ||||||
| experiment data. | ||||||
| """ | ||||||
| if not isinstance(experiment_data, CompositeExperimentData): | ||||||
| raise QiskitError("CompositeAnalysis must be run on CompositeExperimentData.") | ||||||
| # Extract job metadata for the component experiments so it can be added | ||||||
| # to the child experiment data incase it is required by the child experiments | ||||||
| # analysis classes | ||||||
| composite_exp = experiment_data.experiment | ||||||
| component_exps = composite_exp.component_experiment() | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the current name of the method in the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is suggestion from @wshanks #460 (comment)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could be changed, would just be an API change.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would also be fine with just having the plural version and allowing it to take None, int or slice to return all, single, or subset like we do with |
||||||
| if "component_job_metadata" in experiment_data.metadata: | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand these lines. What does the existence of
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand correctly the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The important point we need to understand here is children is not generated by job execution, but this is generated on the fly by analysis. This is because experiment data child is not limited only to the composite, but also, for example, we can create pseudo RDB of experiment data. i.e. my_all_result_for_writing_prx_paper = ExperimentData()
my_all_result_for_writing_prx_paper.add_child_data(my_data_of_calibration)
my_all_result_for_writing_prx_paper.add_child_data(my_data_of_rb)
my_all_result_for_writing_prx_paper.add_child_data(my_data_of_quantum_volume)
etc...then we can write
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is to deal with how regular ExperimentData stores job metadata, which is used by some analysis (curve analysis in particular i think). If its not there certain experiments analysis wont run (note I think this should be changed, but it's beyond the scope of this PR). Since each component experiment might need this for its respective analysis to work this is just storing the list of these component job metadata in the parent experiment so it can be added later along with the marginalized data. Previously this was all done when calling The |
||||||
| component_metadata = experiment_data.metadata["component_job_metadata"][-1] | ||||||
| else: | ||||||
| component_metadata = [{}] * composite_exp.num_experiments | ||||||
|
|
||||||
| # Initialize component data for updating and get the experiment IDs for | ||||||
| # the component child experiments in case there are other child experiments | ||||||
| # in the experiment data | ||||||
| component_ids = self._initialize_components(composite_exp, experiment_data) | ||||||
|
|
||||||
| # Compute marginalize data for each component experiment | ||||||
| marginalized_data = self._marginalize_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 i, (sub_data, sub_exp) in enumerate(zip(marginalized_data, component_exps)): | ||||||
| sub_exp_data = experiment_data.child_data(component_ids[i]) | ||||||
|
|
||||||
| comp_exp = experiment_data.experiment | ||||||
| # Clear any previously stored data and add marginalized data | ||||||
| sub_exp_data._data.clear() | ||||||
| sub_exp_data.add_data(sub_data) | ||||||
|
|
||||||
| for i in range(comp_exp.num_experiments): | ||||||
| # Run analysis for sub-experiments and add sub-experiment metadata | ||||||
| exp = comp_exp.component_experiment(i) | ||||||
| expdata = experiment_data.component_experiment_data(i) | ||||||
| exp.run_analysis(expdata, **options) | ||||||
| # Add component job metadata | ||||||
| sub_exp_data.metadata["job_metadata"] = [component_metadata[i]] | ||||||
|
|
||||||
| # Run analysis | ||||||
| # Since copy for replace result is handled at the parent level | ||||||
| # we always run with replace result on component analysis | ||||||
| sub_exp.run_analysis(sub_exp_data, replace_results=True) | ||||||
|
|
||||||
| return [], [] | ||||||
|
|
||||||
| def _initialize_components(self, experiment, experiment_data): | ||||||
| """Initialize child data components and return list of child experiment IDs""" | ||||||
| # Check if component child experiment data containers have already | ||||||
| # been created. If so the list of indices for their positions in the | ||||||
| # ordered dict should exist. Index is used to extract the experiment | ||||||
| # IDs for each child experiment which can change when re-running analysis | ||||||
| # if replace_results=False, so that we update the correct child data | ||||||
| # for each component experiment | ||||||
| component_index = experiment_data.metadata.get("component_child_index", []) | ||||||
| if not component_index: | ||||||
| # If the experiment Construct component data and update indices | ||||||
| start_index = len(experiment_data.child_data()) | ||||||
| component_index = [] | ||||||
| for i, sub_exp in enumerate(experiment.component_experiment()): | ||||||
| sub_data = sub_exp._initialize_experiment_data() | ||||||
| experiment_data.add_child_data(sub_data) | ||||||
| component_index.append(start_index + i) | ||||||
| experiment_data.metadata["component_child_index"] = component_index | ||||||
|
|
||||||
| # Child components exist so we can get their ID for accessing them | ||||||
| child_ids = experiment_data._child_data.keys() | ||||||
| component_ids = [child_ids[idx] for idx in component_index] | ||||||
| return component_ids | ||||||
|
|
||||||
| def _marginalize_data(self, composite_data: List[Dict]) -> List[Dict]: | ||||||
| """Return marginalized data for component experiments""" | ||||||
| # 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 | ||||||
| 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_counts(datum["counts"], composite_clbits[i]) | ||||||
| else: | ||||||
| sub_data["counts"] = datum["counts"] | ||||||
| marginalized_data[index].append(sub_data) | ||||||
|
|
||||||
| # Sort by index | ||||||
| return [marginalized_data[i] for i in sorted(marginalized_data.keys())] | ||||||
Uh oh!
There was an error while loading. Please reload this page.