Skip to content
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

Rework experiment options #39

Merged
merged 11 commits into from
May 26, 2021
2 changes: 1 addition & 1 deletion qiskit_experiments/analysis/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def plot_curve_fit(
):
"""Generate plot of a curve fit analysis result.

Wraps ``matplotlib.pyplot.plot``.
Wraps :func:`matplotlib.pyplot.plot`.

Args:
func: the fit function for curve_fit.
Expand Down
56 changes: 41 additions & 15 deletions qiskit_experiments/base_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,46 @@
from abc import ABC, abstractmethod
from typing import List, Tuple

from qiskit.providers.options import Options
from qiskit.exceptions import QiskitError

from .experiment_data import ExperimentData, AnalysisResult
from qiskit_experiments.experiment_data import ExperimentData, AnalysisResult

# pylint: disable = unused-import
from qiskit_experiments.matplotlib import pyplot


class BaseAnalysis(ABC):
"""Base Analysis class for analyzing Experiment data."""
"""Base Analysis class for analyzing Experiment data.

The data produced by experiments (i.e. subclasses of BaseExperiment)
are analyzed with subclasses of BaseExperiment. The analysis is
typically run after the data has been gathered by the experiment.
For example, an analysis may perform some data processing of the
measured data and a fit to a function to extract a parameter.

When designing Analysis subclasses default values for any kwarg
analysis options of the `run` method should be set by overriding
the `_default_options` class method. When calling `run` these
default values will be combined with all other option kwargs in the
run method and passed to the `_run_analysis` function.
"""

# Expected experiment data container for analysis
__experiment_data__ = ExperimentData

@classmethod
def _default_options(cls) -> Options:
return Options()

def run(
self,
experiment_data: ExperimentData,
save: bool = True,
return_figures: bool = False,
**options,
):
"""Run analysis and update stored ExperimentData with analysis result.
"""Run analysis and update ExperimentData with analysis result.

Args:
experiment_data: the experiment data to analyze.
Expand All @@ -43,14 +64,13 @@ def run(
return_figures: if true return a pair of
``(analysis_results, figures)``,
otherwise return only analysis_results.
options: kwarg options for analysis function.
options: additional analysis options. See class documentation for
supported options.

Returns:
AnalysisResult: the output of the analysis that produces a
single result.
List[AnalysisResult]: the output for analysis that produces
multiple results.
tuple: If ``return_figures=True`` the output is a pair
Tuple: If ``return_figures=True`` the output is a pair
``(analysis_results, figures)`` where ``analysis_results``
may be a single or list of :class:`AnalysisResult` objects, and
``figures`` may be None, a single figure, or a list of figures.
Expand All @@ -63,10 +83,15 @@ def run(
f"Invalid experiment data type, expected {self.__experiment_data__.__name__}"
f" but received {type(experiment_data).__name__}"
)
# Get analysis options
analysis_options = self._default_options()
analysis_options.update_options(**options)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The options contains both immutable and runtime options? This overrides class default setting in constructor and runtime. For me it looks too flexible.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was matching the current way options are handled for simulators. When a class is initialized it initializes a copy of the default options specified in the class design, and then everything in options is mutable. For analysis options it's currently coded so you can pass option values at construction Analysis(**options) which is equivalent to setting them after default construction, Analysis().set_options(**options). These are equivalent. The Analysis.run method can also be called with options, which will override any stored values in analysis.options just for that execution, without changing any of the stored options.

However thinking about this more, since analysis options are also stored in experiment maybe this is overkill and we could remove the set_options ahd just have the default options and run(**options) to override them to keep the analysis clas more like a function without side effects (like it was before). I'll try make some changes to simplify this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, thank you :)

analysis_options = analysis_options.__dict__

# Run analysis
# pylint: disable=broad-except
try:
analysis_results, figures = self._run_analysis(experiment_data, **options)
analysis_results, figures = self._run_analysis(experiment_data, **analysis_options)
analysis_results["success"] = True
except Exception as ex:
analysis_results = AnalysisResult(success=False, error_message=ex)
Expand All @@ -88,18 +113,19 @@ def run(

@abstractmethod
def _run_analysis(
self, data: ExperimentData, **options
) -> Tuple[List[AnalysisResult], List["matplotlib.figure.Figure"]]:
self, experiment_data: ExperimentData, **options
) -> Tuple[List[AnalysisResult], List["pyplot.Figure"]]:
"""Run analysis on circuit data.

Args:
experiment_data: the experiment data to analyze.
options: kwarg options for analysis function.
options: additional options for analysis. By default the fields and
values in :meth:`options` are used and any provided values
can override these.

Returns:
tuple: A pair ``(analysis_results, figures)`` where
``analysis_results`` may be a single or list of
AnalysisResult objects, and ``figures`` is a list of any
figures for the experiment.
A pair ``(analysis_results, figures)`` where ``analysis_results``
may be a single or list of AnalysisResult objects, and ``figures``
is a list of any figures for the experiment.
"""
pass
Loading