Skip to content
Closed
Show file tree
Hide file tree
Changes from 13 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
7 changes: 3 additions & 4 deletions qiskit_experiments/analysis/curve_fitting.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
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.analysis.data_processing import filter_data


Expand All @@ -31,7 +30,7 @@ def curve_fit(
sigma: Optional[np.ndarray] = None,
bounds: Optional[Union[Dict[str, Tuple[float, float]], Tuple[np.ndarray, np.ndarray]]] = None,
**kwargs,
) -> AnalysisResult:
) -> Dict:
r"""Perform a non-linear least squares to fit

This solves the optimization problem
Expand Down Expand Up @@ -143,7 +142,7 @@ def fit_func(x, *params):
"xrange": xdata_range,
}

return AnalysisResult(result)
return result


def multi_curve_fit(
Expand All @@ -156,7 +155,7 @@ def multi_curve_fit(
weights: Optional[np.ndarray] = None,
bounds: Optional[Union[Dict[str, Tuple[float, float]], Tuple[np.ndarray, np.ndarray]]] = None,
**kwargs,
) -> AnalysisResult:
) -> Dict:
r"""Perform a linearized multi-objective non-linear least squares fit.

This solves the optimization problem
Expand Down
7 changes: 3 additions & 4 deletions qiskit_experiments/analysis/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
"""
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.matplotlib import pyplot, requires_matplotlib

# pylint: disable = unused-import
Expand All @@ -25,7 +24,7 @@
@requires_matplotlib
def plot_curve_fit(
func: Callable,
result: AnalysisResult,
result: Dict,
confidence_interval: bool = True,
ax=None,
num_fit_points: int = 100,
Expand All @@ -39,7 +38,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
36 changes: 15 additions & 21 deletions qiskit_experiments/base_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
from typing import List, Tuple

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

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

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


class BaseAnalysis(ABC):
Expand Down Expand Up @@ -66,11 +68,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,38 +83,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
# pylint: disable=broad-except
try:
analysis_results, figures = self._run_analysis(experiment_data, **analysis_options)
analysis_results["success"] = True
except AnalysisError as ex:
analysis_results = AnalysisResult(success=False, error_message=ex)
figures = None
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 @@ -123,7 +117,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
29 changes: 19 additions & 10 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[AnalysisResult, List["matplotlib.figure.Figure"]]:
) -> Tuple[AnalysisResultV1, List["matplotlib.figure.Figure"]]:
"""
Calculate T1

Expand Down Expand Up @@ -127,8 +129,7 @@ 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(
{
result_data = {
"value": fit_result["popt"][1],
"stderr": fit_result["popt_err"][1],
"unit": "s",
Expand All @@ -138,11 +139,10 @@ def fit_fun(x, a, tau, c):
fit_result["popt"], fit_result["popt_err"], fit_result["reduced_chisq"]
),
}
)

analysis_result["fit"]["circuit_unit"] = unit
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
37 changes: 24 additions & 13 deletions qiskit_experiments/characterization/t2star_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
from qiskit.circuit import QuantumCircuit
from qiskit.utils import apply_prefix
from qiskit.providers.options import Options
from qiskit.providers.experiment import AnalysisResultV1, ResultQuality
from qiskit.providers.experiment.device_component import Qubit
from qiskit_experiments.base_experiment import BaseExperiment
from qiskit_experiments.base_analysis import BaseAnalysis, AnalysisResult
from qiskit_experiments.base_analysis import BaseAnalysis
from qiskit_experiments.analysis.curve_fitting import curve_fit, process_curve_data
from qiskit_experiments.analysis.data_processing import level2_probability
from qiskit_experiments.analysis import plotting
from ..experiment_data import ExperimentData


# pylint: disable = invalid-name
class T2StarAnalysis(BaseAnalysis):
"""T2Star Experiment result analysis class."""
Expand All @@ -45,7 +48,7 @@ def _run_analysis(
plot: bool = True,
ax: Optional["AxesSubplot"] = None,
**kwargs,
) -> Tuple[AnalysisResult, List["matplotlib.figure.Figure"]]:
) -> Tuple[AnalysisResultV1, List["matplotlib.figure.Figure"]]:
r"""Calculate T2Star experiment.

The probability of measuring `+` is assumed to be of the form
Expand Down Expand Up @@ -82,12 +85,13 @@ def _format_plot(ax, unit):
ax.set_ylabel("Probability to measure |0>", fontsize=12)

# implementation of _run_analysis
unit = experiment_data._data[0]["metadata"]["unit"]
conversion_factor = experiment_data._data[0]["metadata"].get("dt_factor", None)
data = experiment_data.data()
unit = data[0]["metadata"]["unit"]
conversion_factor = data[0]["metadata"].get("dt_factor", None)
if conversion_factor is None:
conversion_factor = 1 if unit == "s" else apply_prefix(1, unit)
xdata, ydata, sigma = process_curve_data(
experiment_data._data, lambda datum: level2_probability(datum, "0")
data, lambda datum: level2_probability(datum, "0")
)

si_xdata = xdata * conversion_factor
Expand All @@ -110,8 +114,7 @@ def _format_plot(ax, unit):
figures = None

# Output unit is 'sec', regardless of the unit used in the input
analysis_result = AnalysisResult(
{
result_data = {
"t2star_value": fit_result["popt"][1],
"frequency_value": fit_result["popt"][2],
"stderr": fit_result["popt_err"][1],
Expand All @@ -122,11 +125,19 @@ def _format_plot(ax, unit):
fit_result["popt"], fit_result["popt_err"], fit_result["reduced_chisq"]
),
}
)

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

analysis_result = AnalysisResultV1(
result_data=result_data,
result_type="T2Star",
device_components=[Qubit(data[0]["metadata"]["qubit"])],
experiment_id=experiment_data.experiment_id,
quality=result_data["quality"],
)

return analysis_result, figures

def _t2star_default_params(
Expand Down Expand Up @@ -179,9 +190,9 @@ def _fit_quality(fit_out, fit_err, reduced_chisq):
and (fit_err[1] is None or fit_err[1] < 0.1 * fit_out[1])
and (fit_err[2] is None or fit_err[2] < 0.1 * fit_out[2])
):
return "computer_good"
return ResultQuality.GOOD
else:
return "computer_bad"
return ResultQuality.BAD


class T2StarExperiment(BaseExperiment):
Expand Down Expand Up @@ -233,7 +244,7 @@ def circuits(self, backend: Optional[Backend] = None) -> List[QuantumCircuit]:
"""
if self._unit == "dt":
try:
dt_factor = getattr(backend._configuration, "dt")
dt_factor = getattr(backend.configuration(), "dt")
except AttributeError as no_dt:
raise AttributeError("Dt parameter is missing in backend configuration") from no_dt

Expand Down
Loading