Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions qiskit_experiments/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
:toctree: ../stubs/

ExperimentData
AnalysisResult


Experiment Base Classes
Expand All @@ -49,7 +48,7 @@
from .version import __version__

# Base Classes
from .experiment_data import ExperimentData, AnalysisResult
from .experiment_data import ExperimentData
from .base_analysis import BaseAnalysis
from .base_experiment import BaseExperiment

Expand Down
29 changes: 20 additions & 9 deletions qiskit_experiments/analysis/curve_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

import numpy as np
from qiskit.providers.options import Options
from qiskit.providers.experiment import AnalysisResultV1
from qiskit.providers.experiment.device_component import Qubit

from qiskit_experiments.analysis import plotting
from qiskit_experiments.analysis.curve_fitting import multi_curve_fit, CurveAnalysisResult
Expand Down Expand Up @@ -728,8 +730,8 @@ def _run_analysis(
Raises:
AnalysisError: if the analysis fails.
"""
analysis_result = CurveAnalysisResult()
analysis_result["analysis_type"] = self.__class__.__name__
result_data = CurveAnalysisResult()
result_data["analysis_type"] = self.__class__.__name__
figures = list()

# pop arguments that are not given to fitter
Expand Down Expand Up @@ -788,7 +790,7 @@ def _run_analysis(
sigma=_sigma,
**fit_options,
)
analysis_result.update(**fit_result)
result_data.update(**fit_result)
else:
# Multiple initial guesses
fit_options_candidates = [
Expand All @@ -807,24 +809,24 @@ def _run_analysis(
]
# Sort by chi squared value
fit_results = sorted(fit_results, key=lambda r: r["reduced_chisq"])
analysis_result.update(**fit_results[0])
result_data.update(**fit_results[0])

except AnalysisError as ex:
analysis_result["error_message"] = str(ex)
analysis_result["success"] = False
result_data["error_message"] = str(ex)
result_data["success"] = False

else:
#
# 4. Post-process analysis data
#
analysis_result = self._post_processing(analysis_result=analysis_result)
result_data = self._post_processing(analysis_result=result_data)

finally:
#
# 5. Create figures
#
if self._get_option("plot"):
figures.extend(self._create_figures(analysis_results=analysis_result))
figures.extend(self._create_figures(analysis_results=result_data))

#
# 6. Optionally store raw data points
Expand All @@ -844,6 +846,15 @@ def _run_analysis(
"ydata": sub_ydata,
"sigma": sub_sigma,
}
analysis_result["raw_data"] = raw_data_dict
result_data["raw_data"] = raw_data_dict

analysis_result = AnalysisResultV1(
result_data=result_data,
result_type="RB",
device_components=[
Qubit(qubit) for qubit in experiment_data.data(0)["metadata"]["qubits"]
],
experiment_id=experiment_data.experiment_id,
)

return [analysis_result], figures
4 changes: 2 additions & 2 deletions qiskit_experiments/analysis/curve_fitting.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
import numpy as np
import scipy.optimize as opt
from qiskit_experiments.exceptions import AnalysisError
from qiskit_experiments.base_analysis import AnalysisResult
from qiskit_experiments.base_analysis import ResultDict
from qiskit_experiments.analysis.data_processing import filter_data


class CurveAnalysisResult(AnalysisResult):
class CurveAnalysisResult(ResultDict):
"""Analysis data container for curve fit analysis.

Class Attributes:
Expand Down
8 changes: 4 additions & 4 deletions qiskit_experiments/analysis/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
"""
Plotting functions for experiment analysis
"""
from typing import Callable, Optional
from typing import Callable, Optional, Dict
import numpy as np

from qiskit_experiments.base_analysis import AnalysisResult
from qiskit_experiments.base_analysis import ResultDict
from qiskit_experiments.matplotlib import pyplot, requires_matplotlib

# pylint: disable = unused-import
Expand All @@ -25,7 +25,7 @@
@requires_matplotlib
def plot_curve_fit(
func: Callable,
result: AnalysisResult,
result: ResultDict,
confidence_interval: bool = True,
ax=None,
num_fit_points: int = 100,
Expand All @@ -39,7 +39,7 @@ def plot_curve_fit(

Args:
func: the fit function for curve_fit.
result: an AnalysisResult from curve_fit.
result: a dictionary from curve_fit.
confidence_interval: if True plot the confidence interval from popt_err.
ax (matplotlib.axes.Axes): Optional, a matplotlib axes to add the plot to.
num_fit_points: the number of points to plot for xrange.
Expand Down
34 changes: 13 additions & 21 deletions qiskit_experiments/base_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@

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

from qiskit_experiments.exceptions import AnalysisError
from qiskit_experiments.experiment_data import ExperimentData, AnalysisResult
from qiskit_experiments.experiment_data import ExperimentData, ResultDict


class BaseAnalysis(ABC):
Expand Down Expand Up @@ -66,11 +67,11 @@ def run(
supported options.

Returns:
List[AnalysisResult]: the output for analysis that produces
multiple results.
List[AnalysisResultV1]: the output for analysis that produces
multiple results.
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
may be a single or list of :class:`AnalysisResultV1` objects, and
``figures`` may be None, a single figure, or a list of figures.

Raises:
Expand All @@ -81,39 +82,30 @@ 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)
analysis_options = analysis_options.__dict__

# Run analysis
try:
analysis_results, figures = self._run_analysis(experiment_data, **analysis_options)
for res in analysis_results:
if "success" not in res:
res["success"] = True
except AnalysisError as ex:
analysis_results = [AnalysisResult(success=False, error_message=ex)]
figures = None
# pylint: disable=broad-except
analysis_results, figures = self._run_analysis(experiment_data, **analysis_options)

# Save to experiment data
if save:
if isinstance(analysis_results, AnalysisResult):
experiment_data.add_analysis_result(analysis_results)
else:
for res in analysis_results:
experiment_data.add_analysis_result(res)
experiment_data.add_analysis_results(analysis_results)
if figures:
for fig in figures:
experiment_data.add_figure(fig)
experiment_data.add_figures(figures)

if return_figures:
return analysis_results, figures
return analysis_results

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

Args:
Expand All @@ -124,7 +116,7 @@ def _run_analysis(

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

Raises:
Expand Down
15 changes: 10 additions & 5 deletions qiskit_experiments/base_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ def run(
else:
job = backend.run(circuits, **run_opts)

# Add Job to ExperimentData
experiment_data.add_data(job)

# Queue analysis of data for when job is finished
# Add Job to ExperimentData and add analysis for post processing.
run_analysis = None
if analysis and self.__analysis_class__ is not None:
self.run_analysis(experiment_data)
run_analysis = self.run_analysis

experiment_data.add_data(job, post_processing_callback=run_analysis)

# Return the ExperimentData future
return experiment_data
Expand Down Expand Up @@ -162,6 +162,11 @@ def physical_qubits(self) -> Tuple[int]:
"""Return the physical qubits for this experiment."""
return self._physical_qubits

@property
def experiment_type(self) -> str:
"""Return experiment type."""
return self._type

@classmethod
def analysis(cls):
"""Return the default Analysis class for the experiment."""
Expand Down
49 changes: 29 additions & 20 deletions qiskit_experiments/characterization/t1_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@
from qiskit.utils import apply_prefix
from qiskit.providers.options import Options

from qiskit.providers.experiment import AnalysisResultV1
from qiskit.providers.experiment.device_component import Qubit

from qiskit_experiments.base_experiment import BaseExperiment
from qiskit_experiments.base_analysis import BaseAnalysis
from qiskit_experiments.analysis.curve_fitting import process_curve_data, curve_fit
from qiskit_experiments.analysis.data_processing import level2_probability
from qiskit_experiments.analysis import plotting
from qiskit_experiments import AnalysisResult


class T1Analysis(BaseAnalysis):
Expand Down Expand Up @@ -69,7 +71,7 @@ def _run_analysis(
offset_bounds=None,
plot=True,
ax=None,
) -> Tuple[List[AnalysisResult], List["matplotlib.figure.Figure"]]:
) -> Tuple[List[AnalysisResultV1], List["matplotlib.figure.Figure"]]:
"""
Calculate T1

Expand Down Expand Up @@ -127,22 +129,20 @@ def fit_fun(x, a, tau, c):
bounds = {"a": amplitude_bounds, "tau": t1_bounds, "c": offset_bounds}
fit_result = curve_fit(fit_fun, xdata, ydata, init, sigma=sigma, bounds=bounds)

analysis_result = AnalysisResult(
{
"value": fit_result["popt"][1],
"stderr": fit_result["popt_err"][1],
"unit": "s",
"label": "T1",
"fit": fit_result,
"quality": self._fit_quality(
fit_result["popt"], fit_result["popt_err"], fit_result["reduced_chisq"]
),
}
)

analysis_result["fit"]["circuit_unit"] = unit
result_data = {
"value": fit_result["popt"][1],
"stderr": fit_result["popt_err"][1],
"unit": "s",
"label": "T1",
"fit": fit_result,
"quality": self._fit_quality(
fit_result["popt"], fit_result["popt_err"], fit_result["reduced_chisq"]
),
}

result_data["fit"]["circuit_unit"] = unit
if unit == "dt":
analysis_result["fit"]["dt"] = conversion_factor
result_data["fit"]["dt"] = conversion_factor

# Generate fit plot
if plot and plotting.HAS_MATPLOTLIB:
Expand All @@ -153,7 +153,16 @@ def fit_fun(x, a, tau, c):
else:
figures = None

return [analysis_result], figures
res_v1 = AnalysisResultV1(
result_data=result_data,
result_type="T1",
device_components=[Qubit(data[0]["metadata"]["qubit"])],
experiment_id=experiment_data.experiment_id,
quality=result_data["quality"],
verified=True,
)

return [res_v1], figures

@staticmethod
def _fit_quality(fit_out, fit_err, reduced_chisq):
Expand All @@ -166,9 +175,9 @@ def _fit_quality(fit_out, fit_err, reduced_chisq):
and (fit_err[1] is None or fit_err[1] < fit_out[1])
and (fit_err[2] is None or fit_err[2] < 0.1)
):
return "computer_good"
return "good"
else:
return "computer_bad"
return "bad"

@classmethod
def _format_plot(cls, ax, analysis_result, qubit=None, add_label=True):
Expand Down
Loading