diff --git a/qiskit_experiments/__init__.py b/qiskit_experiments/__init__.py index 9f1c6fa3c2..25b2b57b7d 100644 --- a/qiskit_experiments/__init__.py +++ b/qiskit_experiments/__init__.py @@ -30,7 +30,6 @@ :toctree: ../stubs/ ExperimentData - AnalysisResult Experiment Base Classes @@ -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 diff --git a/qiskit_experiments/analysis/curve_fitting.py b/qiskit_experiments/analysis/curve_fitting.py index 867fd1f238..54f07b4d68 100644 --- a/qiskit_experiments/analysis/curve_fitting.py +++ b/qiskit_experiments/analysis/curve_fitting.py @@ -19,7 +19,7 @@ import numpy as np import scipy.optimize as opt from qiskit.exceptions import QiskitError -from qiskit_experiments.experiment_data import AnalysisResult + from qiskit_experiments.analysis.data_processing import filter_data @@ -31,7 +31,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 @@ -139,7 +139,7 @@ def fit_func(x, *params): "xrange": xdata_range, } - return AnalysisResult(result) + return result def multi_curve_fit( @@ -152,7 +152,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 diff --git a/qiskit_experiments/analysis/plotting.py b/qiskit_experiments/analysis/plotting.py index a4cabe3441..c6347156f3 100644 --- a/qiskit_experiments/analysis/plotting.py +++ b/qiskit_experiments/analysis/plotting.py @@ -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.experiment_data import AnalysisResult from qiskit_experiments.matplotlib import pyplot, requires_matplotlib # pylint: disable = unused-import @@ -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, @@ -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. diff --git a/qiskit_experiments/base_analysis.py b/qiskit_experiments/base_analysis.py index ed3001a1db..0163d7087a 100644 --- a/qiskit_experiments/base_analysis.py +++ b/qiskit_experiments/base_analysis.py @@ -18,7 +18,6 @@ from qiskit.providers.options import Options from qiskit.exceptions import QiskitError - from qiskit.providers.experiment import AnalysisResultV1 from .experiment_data import ExperimentData @@ -93,24 +92,14 @@ def run( # Run analysis # pylint: disable=broad-except - try: - 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) - figures = None + analysis_results, figures = self._run_analysis(experiment_data, **analysis_options) # Save to experiment data if save: - if isinstance(analysis_results, AnalysisResultV1): - 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 diff --git a/qiskit_experiments/base_experiment.py b/qiskit_experiments/base_experiment.py index 016b48fde4..969dc04b14 100644 --- a/qiskit_experiments/base_experiment.py +++ b/qiskit_experiments/base_experiment.py @@ -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.__analysis_class__().run + + experiment_data.add_data(job, post_processing_callback=run_analysis) # Return the ExperimentData future return experiment_data @@ -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.""" diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index 4836dfe880..93d1396cf9 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -29,7 +29,6 @@ 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): @@ -127,8 +126,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", @@ -138,11 +136,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: @@ -154,11 +151,11 @@ def fit_fun(x, a, tau, c): figures = None res_v1 = AnalysisResultV1( - analysis_result, - "T1", - [Qubit(data[0]["metadata"]["qubit"])], - experiment_data.experiment_id, - quality=analysis_result["quality"], + 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, ) diff --git a/qiskit_experiments/characterization/t2star_experiment.py b/qiskit_experiments/characterization/t2star_experiment.py index 8218196dd2..7a146fff97 100644 --- a/qiskit_experiments/characterization/t2star_experiment.py +++ b/qiskit_experiments/characterization/t2star_experiment.py @@ -21,12 +21,15 @@ 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 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, AnalysisResult +from ..experiment_data import ExperimentData + # pylint: disable = invalid-name class T2StarAnalysis(BaseAnalysis): @@ -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 @@ -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 @@ -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], @@ -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( @@ -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): @@ -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 diff --git a/qiskit_experiments/composite/composite_analysis.py b/qiskit_experiments/composite/composite_analysis.py index f67ac7481a..cf7679a354 100644 --- a/qiskit_experiments/composite/composite_analysis.py +++ b/qiskit_experiments/composite/composite_analysis.py @@ -14,7 +14,10 @@ """ from qiskit.exceptions import QiskitError -from qiskit_experiments.base_analysis import BaseAnalysis, AnalysisResultV1 +from qiskit.providers.experiment import AnalysisResultV1 +from qiskit.providers.experiment.device_component import Qubit + +from qiskit_experiments.base_analysis import BaseAnalysis from .composite_experiment_data import CompositeExperimentData @@ -70,11 +73,14 @@ def _run_analysis(self, experiment_data: CompositeExperimentData, **options): sub_ids.append(expdata.experiment_id) sub_qubits.append(expdata.experiment.physical_qubits) - analysis_result = AnalysisResult( - { + analysis_result = AnalysisResultV1( + result_data={ "experiment_types": sub_types, "experiment_ids": sub_ids, "experiment_qubits": sub_qubits, - } + }, + result_type="composite", + device_components=[Qubit(qidx) for qidx in sub_qubits], + experiment_id=experiment_data.experiment_id ) return analysis_result, None diff --git a/qiskit_experiments/composite/composite_experiment.py b/qiskit_experiments/composite/composite_experiment.py index 70b1583b2d..4fbae14da6 100644 --- a/qiskit_experiments/composite/composite_experiment.py +++ b/qiskit_experiments/composite/composite_experiment.py @@ -48,8 +48,17 @@ def num_experiments(self): """Return the number of sub experiments""" return self._num_experiments - def component_experiment(self, index): - """Return the component Experiment object""" + def component_experiment(self, index=None): + """Return the component Experiment object. + + Args: + index: Experiment index, or ``None`` if all experiments are to be returned. + + Returns: + The component experiment(s). + """ + if index is None: + return self._experiments return self._experiments[index] def component_analysis(self, index, **analysis_options): diff --git a/qiskit_experiments/composite/composite_experiment_data.py b/qiskit_experiments/composite/composite_experiment_data.py index 2e3f493097..80766c7afc 100644 --- a/qiskit_experiments/composite/composite_experiment_data.py +++ b/qiskit_experiments/composite/composite_experiment_data.py @@ -26,7 +26,6 @@ def __init__( self, experiment, backend=None, - job_ids=None, ): """Initialize experiment data. @@ -34,7 +33,6 @@ def __init__( experiment (CompositeExperiment): experiment object that generated the data. backend (Backend): Optional, Backend the experiment runs on. It can either be a :class:`~qiskit.providers.Backend` instance or just backend name. - job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. Raises: ExperimentError: If an input argument is invalid. @@ -43,11 +41,11 @@ def __init__( super().__init__( experiment, backend=backend, - job_ids=job_ids, ) # Initialize sub experiments - self._components = [expr.__experiment_data__(expr) for expr in experiment._experiments] + self._components = [expr.__experiment_data__(expr) + for expr in experiment.component_experiment()] def __str__(self): line = 51 * "-" @@ -57,14 +55,16 @@ def __str__(self): ret += f"\nExperiment: {self.experiment_type}" ret += f"\nExperiment ID: {self.experiment_id}" ret += f"\nStatus: {status}" + if status == "ERROR": + ret += "\n " + ret += "\n ".join(self._errors) ret += f"\nComponent Experiments: {len(self._components)}" ret += f"\nCircuits: {len(self._data)}" ret += f"\nAnalysis Results: {n_res}" ret += "\n" + line if n_res: - ret += "\nLast Analysis Result" - for key, value in self._analysis_results[-1].items(): - ret += f"\n- {key}: {value}" + ret += "\nLast Analysis Result:" + ret += f"\n{str(self._analysis_results.values()[-1])}" return ret def component_experiment_data( diff --git a/qiskit_experiments/experiment_data.py b/qiskit_experiments/experiment_data.py index b5dfd484e7..bf904c40f9 100644 --- a/qiskit_experiments/experiment_data.py +++ b/qiskit_experiments/experiment_data.py @@ -13,29 +13,13 @@ Experiment Data class """ import logging -from typing import Optional, Union, List, Dict, Tuple -import os -import uuid -from collections import OrderedDict -from qiskit.result import Result from qiskit.providers import Backend -from qiskit.exceptions import QiskitError -from qiskit.providers import Job, BaseJob -from qiskit.providers.exceptions import JobError - from qiskit.providers.experiment import ExperimentDataV1 -from qiskit_experiments.matplotlib import pyplot, HAS_MATPLOTLIB - - LOG = logging.getLogger(__name__) -class AnalysisResult(dict): - """Placeholder class""" - - class ExperimentData(ExperimentDataV1): """Qiskit Experiments Data container class""" @@ -43,22 +27,20 @@ def __init__( self, experiment=None, backend=None, - job_ids=None, ): """Initialize experiment data. Args: experiment (BaseExperiment): Optional, experiment object that generated the data. backend (Backend): Optional, Backend the experiment runs on. - job_ids (list[str]): Optional, IDs of jobs submitted for the experiment. Raises: ExperimentError: If an input argument is invalid. """ self._experiment = experiment - super().__init__(experiment._type if experiment else None, - backend) + super().__init__(experiment_type=experiment.experiment_type if experiment else "unknown", + backend=backend) @property def experiment(self): diff --git a/qiskit_experiments/matplotlib.py b/qiskit_experiments/matplotlib.py index b3c8dccac9..005a7aa97b 100644 --- a/qiskit_experiments/matplotlib.py +++ b/qiskit_experiments/matplotlib.py @@ -34,6 +34,18 @@ def wrapped(*args, **kwargs): f"{func} requires matplotlib to generate curve fit plot." ' Run "pip install matplotlib" before.' ) - return func(*args, **kwargs) + # Analysis/plotting is done in a separate thread (so it doesn't block the + # main thread), but matplotlib doesn't support GUI mode in a child thread. + # The code below switches to a non-GUI backend "Agg" when creating the + # plot. An alternative is to run this in a separate process, but then + # we'd need to deal with pickling issues. + from matplotlib import pyplot + saved_backend = pyplot.get_backend() + pyplot.switch_backend('Agg') + try: + ret_val = func(*args, **kwargs) + finally: + pyplot.switch_backend(saved_backend) + return ret_val return wrapped diff --git a/qiskit_experiments/randomized_benchmarking/interleaved_rb_analysis.py b/qiskit_experiments/randomized_benchmarking/interleaved_rb_analysis.py index 65c2ae58ff..e73f169bad 100644 --- a/qiskit_experiments/randomized_benchmarking/interleaved_rb_analysis.py +++ b/qiskit_experiments/randomized_benchmarking/interleaved_rb_analysis.py @@ -14,6 +14,10 @@ """ from typing import Optional, List import numpy as np + +from qiskit.providers.experiment import AnalysisResultV1 +from qiskit.providers.experiment.device_component import Qubit + from qiskit_experiments.analysis.curve_fitting import ( process_multi_curve_data, multi_curve_fit, @@ -58,6 +62,7 @@ def _run_analysis( def data_processor(datum): return level2_probability(datum, num_qubits * "0") + data = experiment_data.data() # Raw data for each sample series_raw, x_raw, y_raw, sigma_raw = process_multi_curve_data(data, data_processor) @@ -74,7 +79,7 @@ def fit_fun_interleaved(x, a, alpha, alpha_c, b): p0 = self._p0_multi(series, xdata, ydata, num_qubits) bounds = {"a": [0, 1], "alpha": [0, 1], "alpha_c": [0, 1], "b": [0, 1]} - analysis_result = multi_curve_fit( + result_data = multi_curve_fit( [fit_fun_standard, fit_fun_interleaved], series, xdata, @@ -87,14 +92,14 @@ def fit_fun_interleaved(x, a, alpha, alpha_c, b): # Add EPC data nrb = 2 ** num_qubits scale = (nrb - 1) / nrb - _, alpha, alpha_c, _ = analysis_result["popt"] - _, _, alpha_c_err, _ = analysis_result["popt_err"] + _, alpha, alpha_c, _ = result_data["popt"] + _, _, alpha_c_err, _ = result_data["popt_err"] # Calculate epc_est (=r_c^est) - Eq. (4): epc_est = scale * (1 - alpha_c) epc_est_err = scale * alpha_c_err - analysis_result["EPC"] = epc_est - analysis_result["EPC_err"] = epc_est_err + result_data["EPC"] = epc_est + result_data["EPC_err"] = epc_est_err # Calculate the systematic error bounds - Eq. (5): systematic_err_1 = scale * (abs(alpha - alpha_c) + (1 - alpha)) @@ -105,24 +110,31 @@ def fit_fun_interleaved(x, a, alpha, alpha_c, b): systematic_err = min(systematic_err_1, systematic_err_2) systematic_err_l = epc_est - systematic_err systematic_err_r = epc_est + systematic_err - analysis_result["EPC_systematic_err"] = systematic_err - analysis_result["EPC_systematic_bounds"] = [max(systematic_err_l, 0), systematic_err_r] + result_data["EPC_systematic_err"] = systematic_err + result_data["EPC_systematic_bounds"] = [max(systematic_err_l, 0), systematic_err_r] if plot and plotting.HAS_MATPLOTLIB: - ax = plotting.plot_curve_fit(fit_fun_standard, analysis_result, ax=ax, color="blue") + ax = plotting.plot_curve_fit(fit_fun_standard, result_data, ax=ax, color="blue") ax = plotting.plot_curve_fit( fit_fun_interleaved, - analysis_result, + result_data, ax=ax, color="green", ) ax = self._generate_multi_scatter_plot(series_raw, x_raw, y_raw, ax=ax) ax = self._generate_multi_errorbar_plot(series, xdata, ydata, ydata_sigma, ax=ax) - self._format_plot(ax, analysis_result) + self._format_plot(ax, result_data) ax.legend(loc="center right") figures = [ax.get_figure()] else: figures = None + + analysis_result = AnalysisResultV1( + result_data=result_data, + result_type="IRB", + device_components=[Qubit(qubit) for qubit in data[0]["metadata"]["qubits"]], + experiment_id=experiment_data.experiment_id, + ) return analysis_result, figures @staticmethod diff --git a/qiskit_experiments/randomized_benchmarking/rb_analysis.py b/qiskit_experiments/randomized_benchmarking/rb_analysis.py index 45b21e0063..2b30c728e3 100644 --- a/qiskit_experiments/randomized_benchmarking/rb_analysis.py +++ b/qiskit_experiments/randomized_benchmarking/rb_analysis.py @@ -17,6 +17,9 @@ from qiskit.providers.options import Options from qiskit_experiments.experiment_data import ExperimentData +from qiskit.providers.experiment import AnalysisResultV1 +from qiskit.providers.experiment.device_component import Qubit + 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 ( @@ -82,23 +85,31 @@ def fit_fun(x, a, alpha, b): p0 = self._p0(xdata, ydata, num_qubits) bounds = {"a": [0, 1], "alpha": [0, 1], "b": [0, 1]} - analysis_result = curve_fit(fit_fun, xdata, ydata, p0, ydata_sigma, bounds=bounds) + result_data = curve_fit(fit_fun, xdata, ydata, p0, ydata_sigma, bounds=bounds) # Add EPC data - popt = analysis_result["popt"] - popt_err = analysis_result["popt_err"] + popt = result_data["popt"] + popt_err = result_data["popt_err"] scale = (2 ** num_qubits - 1) / (2 ** num_qubits) - analysis_result["EPC"] = scale * (1 - popt[1]) - analysis_result["EPC_err"] = scale * popt_err[1] / popt[1] + result_data["EPC"] = scale * (1 - popt[1]) + result_data["EPC_err"] = scale * popt_err[1] / popt[1] if plot and plotting.HAS_MATPLOTLIB: - ax = plotting.plot_curve_fit(fit_fun, analysis_result, ax=ax) + ax = plotting.plot_curve_fit(fit_fun, result_data, ax=ax) ax = plotting.plot_scatter(x_raw, y_raw, ax=ax) ax = plotting.plot_errorbar(xdata, ydata, ydata_sigma, ax=ax) - self._format_plot(ax, analysis_result) + self._format_plot(ax, result_data) figures = [ax.get_figure()] else: figures = None + + analysis_result = AnalysisResultV1( + result_data=result_data, + result_type="RB", + device_components=[Qubit(qubit) for qubit in data[0]["metadata"]["qubits"]], + experiment_id=experiment_data.experiment_id, + ) + return analysis_result, figures @staticmethod diff --git a/test/test_t1.py b/test/test_t1.py index 4d7dddb763..035ecec6e0 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -22,10 +22,12 @@ from qiskit.providers.models import QasmBackendConfiguration from qiskit.providers.experiment import ResultQuality from qiskit.result import Result -from qiskit_experiments import ExperimentData +from qiskit.providers.experiment import ExperimentDataV1 from qiskit_experiments.composite import ParallelExperiment from qiskit_experiments.characterization import T1Experiment, T1Analysis +from .utils import FakeJob + class T1Backend(BaseBackend): """ @@ -133,7 +135,7 @@ def run(self, qobj): } ) - return Result.from_dict(result) + return FakeJob(self, result=Result.from_dict(result)) class TestT1(QiskitTestCase): @@ -165,21 +167,23 @@ def test_t1_end2end(self): ) ) - # dummy numbers to avoid exception triggerring + # dummy numbers to avoid exception triggering instruction_durations = [ ("measure", [0], 3, "dt"), ("x", [0], 3, "dt"), ] exp = T1Experiment(0, delays, unit="dt") - res = exp.run( + exp_data = exp.run( backend, amplitude_guess=1, t1_guess=t1 / dt_factor, offset_guess=0, instruction_durations=instruction_durations, shots=10000, - ).analysis_result(0) + ) + exp_data.block_for_results() # Wait for analysis to finish. + res = exp_data.analysis_result(0) self.assertEqual(res.quality, ResultQuality.GOOD) self.assertAlmostEqual(res.data()["value"], t1, delta=3) @@ -195,26 +199,27 @@ def test_t1_parallel(self): exp0 = T1Experiment(0, delays) exp2 = T1Experiment(2, delays) par_exp = ParallelExperiment([exp0, exp2]) - res = par_exp.run( - T1Backend([t1[0], None, t1[1]]), + exp_data = par_exp.run( + backend=T1Backend([t1[0], None, t1[1]]), shots=10000, ) + exp_data.block_for_results() for i in range(2): - sub_res = res.component_experiment_data(i).analysis_result(0) - self.assertTrue(sub_res["quality"], "computer_good") - self.assertAlmostEqual(sub_res["value"], t1[i], delta=3) + sub_res = exp_data.component_experiment_data(i).analysis_result(0) + self.assertTrue(sub_res.quality, ResultQuality.GOOD) + self.assertAlmostEqual(sub_res.data()["value"], t1[i], delta=3) def test_t1_analysis(self): """ Test T1Analysis """ - data = ExperimentData(None) + data = ExperimentDataV1("test") numbers = [750, 1800, 2750, 3550, 4250, 4850, 5450, 5900, 6400, 6800, 7000, 7350, 7700] for i, count0 in enumerate(numbers): - data._data.append( + data.add_data( { "counts": {"0": count0, "1": 10000 - count0}, "metadata": { @@ -258,10 +263,10 @@ def test_t1_low_quality(self): A test where the fit's quality will be low """ - data = ExperimentData(None) + data = ExperimentDataV1("test") for i in range(10): - data._data.append( + data.add_data( { "counts": {"0": 10, "1": 10}, "metadata": { diff --git a/test/test_t2star.py b/test/test_t2star.py index 7b08bfb5ec..e5fa41552e 100644 --- a/test/test_t2star.py +++ b/test/test_t2star.py @@ -16,11 +16,14 @@ from qiskit.utils import apply_prefix from qiskit.providers import BaseBackend from qiskit.providers.models import QasmBackendConfiguration +from qiskit.providers.experiment import ResultQuality from qiskit.result import Result from qiskit.test import QiskitTestCase from qiskit_experiments.composite import ParallelExperiment from qiskit_experiments.characterization import T2StarExperiment +from .utils import FakeJob + # Fix seed for simulations SEED = 9000 @@ -138,7 +141,7 @@ def run(self, qobj): "data": {"counts": counts}, } ) - return Result.from_dict(result) + return FakeJob(self, result=Result.from_dict(result)) class TestT2Star(QiskitTestCase): @@ -209,19 +212,21 @@ def test_t2star_run_end2end(self): instruction_durations=instruction_durations, shots=2000, ) + expdata.block_for_results() # Wait for job/analysis to finish. result = expdata.analysis_result(0) + result_data = result.data() self.assertAlmostEqual( - result["t2star_value"], + result_data["t2star_value"], estimated_t2star * dt_factor, - delta=0.08 * result["t2star_value"], + delta=0.08 * result_data["t2star_value"], ) self.assertAlmostEqual( - result["frequency_value"], + result_data["frequency_value"], estimated_freq / dt_factor, - delta=0.08 * result["frequency_value"], + delta=0.08 * result_data["frequency_value"], ) self.assertEqual( - result["quality"], "computer_good", "Result quality bad for unit " + str(unit) + result.quality, ResultQuality.GOOD, "Result quality bad for unit " + str(unit) ) def test_t2star_parallel(self): @@ -245,25 +250,28 @@ def test_t2star_parallel(self): "b_guess": [0.5, None, 0.5], } backend = T2starBackend(p0) - res = par_exp.run( + expdata = par_exp.run( backend=backend, # plot=False, shots=1000, ) + expdata.block_for_results() for i in range(2): - sub_res = res.component_experiment_data(i).analysis_result(0) + sub_res = expdata.component_experiment_data(i).analysis_result(0) + sub_rest_data = sub_res.data() self.assertAlmostEqual( - sub_res["t2star_value"], t2star[i], delta=0.08 * sub_res["t2star_value"] + sub_rest_data["t2star_value"], t2star[i], + delta=0.08 * sub_rest_data["t2star_value"] ) self.assertAlmostEqual( - sub_res["frequency_value"], + sub_rest_data["frequency_value"], estimated_freq[i], - delta=0.08 * sub_res["frequency_value"], + delta=0.08 * sub_rest_data["frequency_value"], ) self.assertEqual( - sub_res["quality"], - "computer_good", + sub_res.quality, + ResultQuality.GOOD, "Result quality bad for experiment on qubit " + str(i), ) diff --git a/test/utils.py b/test/utils.py new file mode 100644 index 0000000000..e640b74312 --- /dev/null +++ b/test/utils.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test utility functions.""" + +import uuid +from typing import Optional, Union +import time + +from qiskit.providers.job import JobV1 as Job +from qiskit.providers.jobstatus import JobStatus +from qiskit.providers.backend import BackendV1 as Backend +from qiskit.providers import BaseBackend +from qiskit.result import Result + + +class FakeJob(Job): + """Fake job.""" + + def __init__(self, backend: Union[Backend, BaseBackend], result: Optional[Result] = None): + """Initialize FakeJob.""" + job_id = uuid.uuid4().hex + super().__init__(backend, job_id) + self._result = result + + def result(self): + """Return job result.""" + time.sleep(3) + return self._result + + def submit(self): + """Submit the job to the backend for execution.""" + pass + + def status(self) -> JobStatus: + """Return the status of the job, among the values of ``JobStatus``.""" + if self._result: + return JobStatus.DONE + return JobStatus.RUNNING