From ea98ac8a55f603ad5e876dd4e37a6f1a703f3278 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Sun, 23 May 2021 16:23:11 +0300 Subject: [PATCH 01/10] Work with Terra PR 5499 --- qiskit_experiments/analysis/curve_fitting.py | 2 +- qiskit_experiments/analysis/plotting.py | 2 +- qiskit_experiments/base_analysis.py | 31 +- .../characterization/t1_experiment.py | 20 +- .../composite/composite_analysis.py | 2 +- qiskit_experiments/experiment_data.py | 307 +----------------- test/test_t1.py | 11 +- 7 files changed, 44 insertions(+), 331 deletions(-) diff --git a/qiskit_experiments/analysis/curve_fitting.py b/qiskit_experiments/analysis/curve_fitting.py index ead843e82b..74233383dd 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.base_analysis import AnalysisResult +from qiskit_experiments.experiment_data import AnalysisResult from qiskit_experiments.analysis.data_processing import filter_data diff --git a/qiskit_experiments/analysis/plotting.py b/qiskit_experiments/analysis/plotting.py index a64256b712..fc2e10c992 100644 --- a/qiskit_experiments/analysis/plotting.py +++ b/qiskit_experiments/analysis/plotting.py @@ -15,7 +15,7 @@ from typing import Callable, Optional import numpy as np -from qiskit_experiments.base_analysis import AnalysisResult +from qiskit_experiments.experiment_data import AnalysisResult from qiskit_experiments.matplotlib import pyplot, requires_matplotlib # pylint: disable = unused-import diff --git a/qiskit_experiments/base_analysis.py b/qiskit_experiments/base_analysis.py index 61cb570296..62f52c592d 100644 --- a/qiskit_experiments/base_analysis.py +++ b/qiskit_experiments/base_analysis.py @@ -18,7 +18,9 @@ from qiskit.exceptions import QiskitError -from .experiment_data import ExperimentData, AnalysisResult +from qiskit.providers.experiment import AnalysisResultV1 + +from .experiment_data import ExperimentData class BaseAnalysis(ABC): @@ -46,13 +48,13 @@ def run( options: kwarg options for analysis function. Returns: - AnalysisResult: the output of the analysis that produces a - single result. - List[AnalysisResult]: the output for analysis that produces - multiple results. + AnalysisResultV1: the output of the analysis that produces a + single result. + 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: @@ -63,18 +65,12 @@ def run( f"Invalid experiment data type, expected {self.__experiment_data__.__name__}" f" but received {type(experiment_data).__name__}" ) - # Run analysis - # pylint: disable=broad-except - try: - analysis_results, figures = self._run_analysis(experiment_data, **options) - analysis_results["success"] = True - except Exception: - analysis_results = AnalysisResult(success=False) - figures = None + + analysis_results, figures = self._run_analysis(experiment_data, **options) # Save to experiment data if save: - if isinstance(analysis_results, AnalysisResult): + if isinstance(analysis_results, AnalysisResultV1): experiment_data.add_analysis_result(analysis_results) else: for res in analysis_results: @@ -82,6 +78,7 @@ def run( if figures: for fig in figures: experiment_data.add_figure(fig) + if return_figures: return analysis_results, figures return analysis_results @@ -89,7 +86,7 @@ def run( @abstractmethod def _run_analysis( self, data: ExperimentData, **options - ) -> Tuple[List[AnalysisResult], List["matplotlib.figure.Figure"]]: + ) -> Tuple[List[AnalysisResultV1], List["matplotlib.figure.Figure"]]: """Run analysis on circuit data. Args: @@ -99,7 +96,7 @@ def _run_analysis( 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 + AnalysisResultV1 objects, and ``figures`` is a list of any figures for the experiment. """ pass diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index e8dd1822cc..c9e5ffb1ee 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -20,6 +20,9 @@ from qiskit.circuit import QuantumCircuit from qiskit.utils import apply_prefix +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 @@ -44,7 +47,7 @@ def _run_analysis( plot=True, ax=None, **kwargs, - ) -> Tuple[AnalysisResult, List["matplotlib.figure.Figure"]]: + ) -> Tuple[AnalysisResultV1, List["matplotlib.figure.Figure"]]: """ Calculate T1 @@ -126,7 +129,16 @@ def fit_fun(x, a, tau, c): else: figures = None - return analysis_result, figures + res_v1 = AnalysisResultV1( + analysis_result, + "T1", + [Qubit(data[0]["metadata"]["qubit"])], + experiment_data.experiment_id, + quality=analysis_result["quality"], + verified=True, + ) + + return res_v1, figures @staticmethod def _fit_quality(fit_out, fit_err, reduced_chisq): @@ -139,9 +151,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): diff --git a/qiskit_experiments/composite/composite_analysis.py b/qiskit_experiments/composite/composite_analysis.py index 265c3d9c03..2b29bb32f5 100644 --- a/qiskit_experiments/composite/composite_analysis.py +++ b/qiskit_experiments/composite/composite_analysis.py @@ -13,7 +13,7 @@ Composite Experiment Analysis class. """ -from qiskit_experiments.base_analysis import BaseAnalysis, AnalysisResult +from qiskit_experiments.base_analysis import BaseAnalysis, AnalysisResultV1 from .composite_experiment_data import CompositeExperimentData diff --git a/qiskit_experiments/experiment_data.py b/qiskit_experiments/experiment_data.py index 13580fffde..b5dfd484e7 100644 --- a/qiskit_experiments/experiment_data.py +++ b/qiskit_experiments/experiment_data.py @@ -24,6 +24,8 @@ 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 @@ -34,7 +36,7 @@ class AnalysisResult(dict): """Placeholder class""" -class ExperimentData: +class ExperimentData(ExperimentDataV1): """Qiskit Experiments Data container class""" def __init__( @@ -53,22 +55,10 @@ def __init__( Raises: ExperimentError: If an input argument is invalid. """ - # Experiment class object self._experiment = experiment - # Terra ExperimentDataV1 attributes - self._backend = backend - self._id = str(uuid.uuid4()) - if experiment is not None: - self._type = experiment._type - else: - self._type = None - job_ids = job_ids or [] - self._jobs = OrderedDict((k, None) for k in job_ids) - self._data = [] - self._figures = OrderedDict() - self._figure_names = [] - self._analysis_results = [] + super().__init__(experiment._type if experiment else None, + backend) @property def experiment(self): @@ -79,290 +69,3 @@ def experiment(self): """ return self._experiment - @property - def experiment_type(self) -> str: - """Return the experiment type.""" - return self._type - - @property - def experiment_id(self) -> str: - """Return the experiment id.""" - return self._id - - @property - def job_ids(self) -> List[str]: - """Return experiment job IDs. - - Returns: - IDs of jobs submitted for this experiment. - """ - return list(self._jobs.keys()) - - @property - def backend(self) -> Backend: - """Return backend. - - Returns: - Backend this experiment is for. - """ - return self._backend - - def add_data( - self, - data: Union[Result, List[Result], Job, List[Job], Dict, List[Dict]], - ): - """Add experiment data. - Args: - data: Experiment data to add. Several types are accepted for convenience: - - * Result: Add data from this ``Result`` object. - * List[Result]: Add data from the ``Result`` objects. - * Job: Add data from the job result. - * List[Job]: Add data from the job results. - * Dict: Add this data. - * List[Dict]: Add this list of data. - - Raises: - QiskitError: if data format is invalid. - KeyboardInterrupt: when job is cancelled by users. - """ - # Set backend from the job, this could be added to base class - if isinstance(data, (Job, BaseJob)): - backend = data.backend() - if self.backend is not None and str(self.backend) != str(backend): - LOG.warning( - "Adding a job from a backend (%s) that is different than" - " the current ExperimentData backend (%s).", - backend, - self.backend, - ) - self._backend = backend - self._jobs[data.job_id()] = data - self._add_result_data(data.result()) - elif isinstance(data, dict): - self._add_single_data(data) - elif isinstance(data, (Job, BaseJob)): - try: - result = data.result() - except JobError as ex: - if hasattr(data, "error_message"): - msg = data.error_message - else: - msg = "Please contact to administrator of your provider." - raise QiskitError(f"Execution of experiment failed. {msg}") from ex - except KeyboardInterrupt as ex: - # remove job from queue list and return the empty result - data.cancel() - raise KeyboardInterrupt(ex) from ex - self._add_result_data(result) - elif isinstance(data, Result): - self._add_result_data(data) - elif isinstance(data, list): - for dat in data: - self.add_data(dat) - else: - raise QiskitError(f"Invalid data type {type(data)}.") - - def _add_result_data(self, result: Result) -> None: - """Add data from a Result object - - Args: - result: Result object containing data to be added. - """ - num_data = len(result.results) - for i in range(num_data): - metadata = result.results[i].header.metadata - if metadata.get("experiment_type") == self._type: - data = result.data(i) - data["metadata"] = metadata - if "counts" in data: - # Format to Counts object rather than hex dict - data["counts"] = result.get_counts(i) - self._add_single_data(data) - - def _add_single_data(self, data: Dict[str, any]) -> None: - """Add a single data dictionary to the experiment. - Args: - data: Data to be added. - """ - self._data.append(data) - - def data(self, index: Optional[Union[int, slice, str]] = None) -> Union[Dict, List[Dict]]: - """Return the experiment data at the specified index. - - Args: - index: Index of the data to be returned. - Several types are accepted for convenience: - - * None: Return all experiment data. - * int: Specific index of the data. - * slice: A list slice of data indexes. - * str: ID of the job that produced the data. - - Returns: - Experiment data. - - Raises: - QiskitError: if index is invalid. - """ - if index is None: - return self._data - if isinstance(index, (int, slice)): - return self._data[index] - if isinstance(index, str): - return [data for data in self._data if data.get("job_id") == index] - raise QiskitError(f"Invalid index type {type(index)}.") - - def add_figure( - self, - figure, - figure_name: Optional[str] = None, - overwrite: bool = False, - ) -> Tuple[str, int]: - """Save the experiment figure. - - Args: - figure (Union[str, bytes, :class:`~matplotlib.figure.Figure`]): Name of the figure file - or figure data to store. This can either be a ``str`` (for a filename to load), - ``bytes`` (for the raw image data), or a :class:`~matplotlib.figure.Figure` object. - figure_name: Name of the figure. If ``None``, use the figure file name, if - given, or a generated name. - overwrite: Whether to overwrite the figure if one already exists with - the same name. - - Returns: - A tuple of the name and size of the saved figure. Returned size - is 0 if there is no experiment service to use. - - Raises: - QiskitError: If the figure with the same name already exists, - and `overwrite=True` is not specified. - """ - if not figure_name: - if isinstance(figure, str): - figure_name = figure - else: - figure_name = f"figure_{self.experiment_id}_{len(self.figure_names)}" - - existing_figure = figure_name in self._figure_names - if existing_figure and not overwrite: - raise QiskitError( - f"A figure with the name {figure_name} for this experiment " - f"already exists. Specify overwrite=True if you " - f"want to overwrite it." - ) - out = [figure_name, 0] - self._figures[figure_name] = figure - self._figure_names.append(figure_name) - return out - - def figure(self, figure_name: Union[str, int], file_name: Optional[str] = None): - """Retrieve the specified experiment figure. - - Args: - figure_name: Name of the figure or figure position. - file_name: Name of the local file to save the figure to. If ``None``, - the content of the figure is returned instead. - - Returns: - Union[int, bytes, :class:`~matplotlib.figure.Figure`]: - - The size of the figure as an ``int`` if ``file_name`` is specified. Otherwise - the content of the figure as ``bytes`` object or a - :class:`~matplotlib.figure.Figure` depending on how the image was loaded. - - Raises: - QiskitError: If the figure cannot be found. - """ - if isinstance(figure_name, int): - figure_name = self._figure_names[figure_name] - - figure_data = self._figures.get(figure_name, None) - if figure_data is not None: - if isinstance(figure_data, str): - with open(figure_data, "rb") as file: - figure_data = file.read() - if file_name: - with open(file_name, "wb") as output: - if HAS_MATPLOTLIB and isinstance(figure_data, pyplot.Figure): - figure_data.savefig(output, format="svg") - num_bytes = os.path.getsize(file_name) - else: - num_bytes = output.write(figure_data) - return num_bytes - return figure_data - raise QiskitError(f"Figure {figure_name} not found.") - - @property - def figure_names(self) -> List[str]: - """Return names of the figures associated with this experiment. - Returns: - Names of figures associated with this experiment. - """ - return self._figure_names - - def add_analysis_result(self, result: AnalysisResult) -> None: - """Save the analysis result. - Args: - result: Analysis result to be saved. - """ - self._analysis_results.append(result) - - def analysis_result( - self, index: Optional[Union[int, slice, str]] - ) -> Union[AnalysisResult, List[AnalysisResult]]: - """Return analysis results associated with this experiment. - - Args: - index: Index of the analysis result to be returned. - Several types are accepted for convenience: - - * None: Return all analysis results. - * int: Specific index of the analysis results. - * slice: A list slice of indexes. - * str: ID of the analysis result. - - Returns: - Analysis results for this experiment. - - Raises: - QiskitError: if index is invalid. - """ - if index is None: - return self._analysis_results - if isinstance(index, (int, slice)): - return self._analysis_results[index] - if isinstance(index, str): - for res in self._analysis_results: - if res.id == index: - return res - raise QiskitError(f"Analysis result {index} not found.") - raise QiskitError(f"Invalid index type {type(index)}.") - - def status(self) -> str: - """Return the data processing status. - - Returns: - Data processing status. - """ - # TODO: Figure out what statuses should be returned including - # execution and analysis status - if not self._jobs and not self._data: - return "EMPTY" - return "DONE" - - def __str__(self): - line = 51 * "-" - n_res = len(self._analysis_results) - ret = line - ret += f"\nExperiment: {self.experiment_type}" - ret += f"\nExperiment ID: {self.experiment_id}" - ret += f"\nStatus: {self.status()}" - 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}" - return ret diff --git a/test/test_t1.py b/test/test_t1.py index 75f794db11..86328a8081 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -19,6 +19,7 @@ import numpy as np from qiskit.providers import BaseBackend from qiskit.providers.models import QasmBackendConfiguration +from qiskit.providers.experiment import ResultQuality from qiskit.result import Result from qiskit_experiments import ExperimentData from qiskit_experiments.composite import ParallelExperiment @@ -179,8 +180,8 @@ def test_t1_end2end(self): shots=10000, ).analysis_result(0) - self.assertEqual(res["quality"], "computer_good") - self.assertAlmostEqual(res["value"], t1, delta=3) + self.assertEqual(res.quality, ResultQuality.GOOD) + self.assertAlmostEqual(res.data()["value"], t1, delta=3) def test_t1_parallel(self): """ @@ -226,8 +227,8 @@ def test_t1_analysis(self): ) res = T1Analysis()._run_analysis(data)[0] - self.assertEqual(res["quality"], "computer_good") - self.assertAlmostEqual(res["value"], 25e-9, delta=3) + self.assertEqual(res.quality, ResultQuality.GOOD) + self.assertAlmostEqual(res.data()["value"], 25e-9, delta=3) def test_t1_metadata(self): """ @@ -273,7 +274,7 @@ def test_t1_low_quality(self): ) res = T1Analysis()._run_analysis(data)[0] - self.assertEqual(res["quality"], "computer_bad") + self.assertEqual(res.quality, ResultQuality.BAD) if __name__ == "__main__": From ca1be36dbd79330f907305e5cae84eeb646714c9 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Thu, 27 May 2021 13:49:01 +0300 Subject: [PATCH 02/10] fixed import --- qiskit_experiments/characterization/t2star_experiment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/characterization/t2star_experiment.py b/qiskit_experiments/characterization/t2star_experiment.py index cc580b8fde..8218196dd2 100644 --- a/qiskit_experiments/characterization/t2star_experiment.py +++ b/qiskit_experiments/characterization/t2star_experiment.py @@ -22,11 +22,11 @@ from qiskit.utils import apply_prefix from qiskit.providers.options import Options 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 +from ..experiment_data import ExperimentData, AnalysisResult # pylint: disable = invalid-name class T2StarAnalysis(BaseAnalysis): From e845cd3ee14e64b191a680150248059a4ff44654 Mon Sep 17 00:00:00 2001 From: jessieyu Date: Thu, 27 May 2021 15:21:44 -0400 Subject: [PATCH 03/10] switch to using terra base --- qiskit_experiments/__init__.py | 3 +- qiskit_experiments/analysis/curve_fitting.py | 8 ++-- qiskit_experiments/analysis/plotting.py | 7 ++-- qiskit_experiments/base_analysis.py | 11 ++--- qiskit_experiments/base_experiment.py | 16 ++++--- .../characterization/t1_experiment.py | 19 ++++----- .../composite/composite_analysis.py | 14 +++++-- .../composite/composite_experiment.py | 13 +++++- .../composite/composite_experiment_data.py | 6 +-- qiskit_experiments/experiment_data.py | 22 +--------- qiskit_experiments/matplotlib.py | 14 ++++++- .../interleaved_rb_analysis.py | 42 ++++++++++++------- .../randomized_benchmarking/rb_analysis.py | 24 +++++++---- test/test_t1.py | 32 ++++++++------ 14 files changed, 130 insertions(+), 101 deletions(-) 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 74233383dd..d893395a6f 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 fc2e10c992..2dedc3741e 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 funcion 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 62f52c592d..c5d2fb2359 100644 --- a/qiskit_experiments/base_analysis.py +++ b/qiskit_experiments/base_analysis.py @@ -70,15 +70,10 @@ def run( # 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 3cafeacd19..2c8d0bed59 100644 --- a/qiskit_experiments/base_experiment.py +++ b/qiskit_experiments/base_experiment.py @@ -144,13 +144,12 @@ def run( else: job = backend.run(circuits, **run_options) - # 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: - # pylint: disable = not-callable - self.__analysis_class__().run(experiment_data, **kwargs) + run_analysis = self.__analysis_class__().run + + experiment_data.add_data(job, post_processing_callback=run_analysis, **kwargs) # Return the ExperimentData future return experiment_data @@ -165,6 +164,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, **kwargs): """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 c9e5ffb1ee..1f37075d65 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -28,7 +28,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): @@ -103,8 +102,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", @@ -114,11 +112,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: @@ -130,11 +127,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/composite/composite_analysis.py b/qiskit_experiments/composite/composite_analysis.py index 2b29bb32f5..fd929e1ed7 100644 --- a/qiskit_experiments/composite/composite_analysis.py +++ b/qiskit_experiments/composite/composite_analysis.py @@ -13,7 +13,10 @@ Composite Experiment Analysis class. """ -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 @@ -61,11 +64,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 0256e1f59c..9d022f932f 100644 --- a/qiskit_experiments/composite/composite_experiment.py +++ b/qiskit_experiments/composite/composite_experiment.py @@ -51,8 +51,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, **kwargs): diff --git a/qiskit_experiments/composite/composite_experiment_data.py b/qiskit_experiments/composite/composite_experiment_data.py index 2e3f493097..ab353e1ad1 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 * "-" 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 ee85befa89..3cab5b190a 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, @@ -53,7 +57,8 @@ def _run_analysis( def data_processor(datum): return level2_probability(datum, datum["metadata"]["ylabel"]) - num_qubits = len(experiment_data.data[0]["metadata"]["qubits"]) + data = experiment_data.data() + num_qubits = len(data[0]["metadata"]["qubits"]) series, x, y, sigma = process_multi_curve_data(experiment_data.data, data_processor) series, xdata, ydata, ydata_sigma = multi_mean_xy_data(series, x, y, sigma) @@ -82,7 +87,7 @@ def fit_fun_interleaved(x, a, _, alpha_int, b): np.mean([p0_std[2], p0_int[2]]), ) - analysis_result = multi_curve_fit( + result_data = multi_curve_fit( [fit_fun_standard, fit_fun_interleaved], series, xdata, @@ -95,8 +100,8 @@ def fit_fun_interleaved(x, a, _, alpha_int, b): # Add EPC data nrb = 2 ** num_qubits scale = (nrb - 1) / (2 ** nrb) - _, alpha, alpha_c, _ = analysis_result["popt"] - _, alpha_err, alpha_c_err, _ = analysis_result["popt_err"] + _, alpha, alpha_c, _ = result_data["popt"] + _, alpha_err, alpha_c_err, _ = result_data["popt_err"] # Calculate epc_est (=r_c^est) - Eq. (4): epc_est = scale * (1 - alpha_c / alpha) @@ -116,24 +121,31 @@ def fit_fun_interleaved(x, a, _, alpha_int, b): ((nrb - 1) / nrb) * (alpha_c / alpha) * (np.sqrt(alpha_err_sq + alpha_c_err_sq)) ) - analysis_result["EPC"] = epc_est - analysis_result["EPC_err"] = epc_est_err - analysis_result["systematic_err"] = systematic_err - analysis_result["systematic_err_L"] = systematic_err_l - analysis_result["systematic_err_R"] = systematic_err_r - analysis_result["plabels"] = ["A", "alpha", "alpha_c", "B"] + result_data["EPC"] = epc_est + result_data["EPC_err"] = epc_est_err + result_data["systematic_err"] = systematic_err + result_data["systematic_err_L"] = systematic_err_l + result_data["systematic_err_R"] = systematic_err_r + result_data["plabels"] = ["A", "alpha", "alpha_c", "B"] + + result_data = AnalysisResultV1( + result_data=result_data, + result_type="IRB", + device_components=[Qubit(data[0]["metadata"]["qubit"])], + experiment_id=experiment_data.experiment_id, + ) if plot: - ax = plotting.plot_curve_fit(fit_fun_standard, analysis_result, ax=ax) - ax = plotting.plot_curve_fit(fit_fun_interleaved, analysis_result, ax=ax) + ax = plotting.plot_curve_fit(fit_fun_standard, result_data, ax=ax) + ax = plotting.plot_curve_fit(fit_fun_interleaved, result_data, ax=ax) ax = plotting.plot_scatter(std_xdata, std_ydata, ax=ax) ax = plotting.plot_scatter(int_xdata, int_ydata, ax=ax) ax = plotting.plot_errorbar(std_xdata, std_ydata, std_ydata_sigma, ax=ax) ax = plotting.plot_errorbar(int_xdata, int_ydata, int_ydata_sigma, ax=ax) - self._format_plot(ax, analysis_result) - analysis_result.plt = plotting.pyplot + self._format_plot(ax, result_data) + result_data.plt = plotting.pyplot - return analysis_result, None + return result_data, None @classmethod def _format_plot(cls, ax, analysis_result, add_label=True): diff --git a/qiskit_experiments/randomized_benchmarking/rb_analysis.py b/qiskit_experiments/randomized_benchmarking/rb_analysis.py index ccf6d5c173..e1e2deaba8 100644 --- a/qiskit_experiments/randomized_benchmarking/rb_analysis.py +++ b/qiskit_experiments/randomized_benchmarking/rb_analysis.py @@ -15,6 +15,9 @@ from typing import Optional, List +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 ( @@ -71,20 +74,27 @@ 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] + + analysis_result = AnalysisResultV1( + result_data=result_data, + result_type="RB", + device_components=[Qubit(data[0]["metadata"]["qubit"])], + experiment_id=experiment_data.experiment_id, + ) if plot and HAS_MATPLOTLIB: - ax = plot_curve_fit(fit_fun, analysis_result, ax=ax) + ax = plot_curve_fit(fit_fun, result_data, ax=ax) ax = plot_scatter(x_raw, y_raw, ax=ax) ax = 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 diff --git a/test/test_t1.py b/test/test_t1.py index 86328a8081..a826b4913b 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -17,14 +17,17 @@ import unittest import numpy as np + from qiskit.providers import BaseBackend 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): """ @@ -132,7 +135,7 @@ def run(self, qobj): } ) - return Result.from_dict(result) + return FakeJob(self, result=Result.from_dict(result)) class TestT1(unittest.TestCase): @@ -171,14 +174,16 @@ def test_t1_end2end(self): ] 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) @@ -194,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": { @@ -257,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": { From 5505020bdbd66d568e9b6a833502c35110d2aa50 Mon Sep 17 00:00:00 2001 From: jessieyu Date: Thu, 27 May 2021 20:52:00 -0400 Subject: [PATCH 04/10] return analysis result --- qiskit_experiments/base_analysis.py | 9 ++-- qiskit_experiments/base_experiment.py | 2 +- .../characterization/t2star_experiment.py | 39 ++++++++++----- .../composite/composite_analysis.py | 1 - .../composite/composite_experiment_data.py | 8 +-- .../interleaved_rb_analysis.py | 25 ++++++---- .../randomized_benchmarking/rb_analysis.py | 2 +- test/utils.py | 50 +++++++++++++++++++ 8 files changed, 105 insertions(+), 31 deletions(-) create mode 100644 test/utils.py diff --git a/qiskit_experiments/base_analysis.py b/qiskit_experiments/base_analysis.py index 1c4fabb54a..0e2f488a12 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 @@ -95,9 +94,13 @@ def run( # 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) + analysis_results = AnalysisResultV1( + result_data={"success": False, "error_message": ex}, + result_type=type(self).__name__, + device_components=[], + experiment_id=experiment_data.experiment_id + ) figures = None # Save to experiment data diff --git a/qiskit_experiments/base_experiment.py b/qiskit_experiments/base_experiment.py index bad6b454ae..969dc04b14 100644 --- a/qiskit_experiments/base_experiment.py +++ b/qiskit_experiments/base_experiment.py @@ -122,7 +122,7 @@ def run( if analysis and self.__analysis_class__ is not None: run_analysis = self.__analysis_class__().run - experiment_data.add_data(job, post_processing_callback=run_analysis, **kwargs) + experiment_data.add_data(job, post_processing_callback=run_analysis) # Return the ExperimentData future return experiment_data diff --git a/qiskit_experiments/characterization/t2star_experiment.py b/qiskit_experiments/characterization/t2star_experiment.py index 8218196dd2..6152dab38a 100644 --- a/qiskit_experiments/characterization/t2star_experiment.py +++ b/qiskit_experiments/characterization/t2star_experiment.py @@ -21,17 +21,22 @@ 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): """T2Star Experiment result analysis class.""" + _type = "T2Star" + @classmethod def _default_options(cls): return Options(user_p0=None, user_bounds=None) @@ -45,7 +50,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 +87,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 +116,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 +127,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 +192,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 +246,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 1bd296811f..cf7679a354 100644 --- a/qiskit_experiments/composite/composite_analysis.py +++ b/qiskit_experiments/composite/composite_analysis.py @@ -14,7 +14,6 @@ """ 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 diff --git a/qiskit_experiments/composite/composite_experiment_data.py b/qiskit_experiments/composite/composite_experiment_data.py index ab353e1ad1..80766c7afc 100644 --- a/qiskit_experiments/composite/composite_experiment_data.py +++ b/qiskit_experiments/composite/composite_experiment_data.py @@ -55,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/randomized_benchmarking/interleaved_rb_analysis.py b/qiskit_experiments/randomized_benchmarking/interleaved_rb_analysis.py index 244faa1ce9..3065afe8fb 100644 --- a/qiskit_experiments/randomized_benchmarking/interleaved_rb_analysis.py +++ b/qiskit_experiments/randomized_benchmarking/interleaved_rb_analysis.py @@ -92,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)) @@ -110,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(data[0]["metadata"]["qubit"])], + 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 603760061d..ecf5af96d1 100644 --- a/qiskit_experiments/randomized_benchmarking/rb_analysis.py +++ b/qiskit_experiments/randomized_benchmarking/rb_analysis.py @@ -102,7 +102,7 @@ def fit_fun(x, a, alpha, b): ) 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) 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 From b5e33bd1bd4df8e3ce93b6d39ee617efb7db995e Mon Sep 17 00:00:00 2001 From: jessieyu Date: Thu, 27 May 2021 20:55:11 -0400 Subject: [PATCH 05/10] remove unused --- qiskit_experiments/characterization/t2star_experiment.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit_experiments/characterization/t2star_experiment.py b/qiskit_experiments/characterization/t2star_experiment.py index 6152dab38a..7a146fff97 100644 --- a/qiskit_experiments/characterization/t2star_experiment.py +++ b/qiskit_experiments/characterization/t2star_experiment.py @@ -35,8 +35,6 @@ class T2StarAnalysis(BaseAnalysis): """T2Star Experiment result analysis class.""" - _type = "T2Star" - @classmethod def _default_options(cls): return Options(user_p0=None, user_bounds=None) From 86f7d8a9b9e78f5f9cce4b8b7a207bfbf944acf6 Mon Sep 17 00:00:00 2001 From: jessieyu Date: Fri, 28 May 2021 13:31:13 -0400 Subject: [PATCH 06/10] fix rb device components --- qiskit_experiments/base_analysis.py | 11 +----- .../interleaved_rb_analysis.py | 2 +- .../randomized_benchmarking/rb_analysis.py | 17 +++++----- test/test_t1.py | 2 +- test/test_t2star.py | 34 ++++++++++++------- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/qiskit_experiments/base_analysis.py b/qiskit_experiments/base_analysis.py index 0e2f488a12..0163d7087a 100644 --- a/qiskit_experiments/base_analysis.py +++ b/qiskit_experiments/base_analysis.py @@ -92,16 +92,7 @@ def run( # Run analysis # pylint: disable=broad-except - try: - analysis_results, figures = self._run_analysis(experiment_data, **analysis_options) - except Exception as ex: - analysis_results = AnalysisResultV1( - result_data={"success": False, "error_message": ex}, - result_type=type(self).__name__, - device_components=[], - experiment_id=experiment_data.experiment_id - ) - figures = None + analysis_results, figures = self._run_analysis(experiment_data, **analysis_options) # Save to experiment data if save: diff --git a/qiskit_experiments/randomized_benchmarking/interleaved_rb_analysis.py b/qiskit_experiments/randomized_benchmarking/interleaved_rb_analysis.py index 3065afe8fb..e73f169bad 100644 --- a/qiskit_experiments/randomized_benchmarking/interleaved_rb_analysis.py +++ b/qiskit_experiments/randomized_benchmarking/interleaved_rb_analysis.py @@ -132,7 +132,7 @@ def fit_fun_interleaved(x, a, alpha, alpha_c, b): analysis_result = AnalysisResultV1( result_data=result_data, result_type="IRB", - device_components=[Qubit(data[0]["metadata"]["qubit"])], + device_components=[Qubit(qubit) for qubit in data[0]["metadata"]["qubits"]], experiment_id=experiment_data.experiment_id, ) return analysis_result, figures diff --git a/qiskit_experiments/randomized_benchmarking/rb_analysis.py b/qiskit_experiments/randomized_benchmarking/rb_analysis.py index ecf5af96d1..2b30c728e3 100644 --- a/qiskit_experiments/randomized_benchmarking/rb_analysis.py +++ b/qiskit_experiments/randomized_benchmarking/rb_analysis.py @@ -94,21 +94,22 @@ def fit_fun(x, a, alpha, b): result_data["EPC"] = scale * (1 - popt[1]) result_data["EPC_err"] = scale * popt_err[1] / popt[1] - analysis_result = AnalysisResultV1( - result_data=result_data, - result_type="RB", - device_components=[Qubit(data[0]["metadata"]["qubit"])], - experiment_id=experiment_data.experiment_id, - ) - if plot and plotting.HAS_MATPLOTLIB: 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 bad7dd3ebd..035ecec6e0 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -167,7 +167,7 @@ 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"), 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), ) From 4dd80a61ded4aab77c237c5fff4f73d83294ba43 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Wed, 2 Jun 2021 14:58:39 +0300 Subject: [PATCH 07/10] fixing the post processing callback function --- qiskit_experiments/base_experiment.py | 2 +- test/test_t2star.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_experiments/base_experiment.py b/qiskit_experiments/base_experiment.py index 969dc04b14..588ba0862d 100644 --- a/qiskit_experiments/base_experiment.py +++ b/qiskit_experiments/base_experiment.py @@ -120,7 +120,7 @@ def run( # Add Job to ExperimentData and add analysis for post processing. run_analysis = None if analysis and self.__analysis_class__ is not None: - run_analysis = self.__analysis_class__().run + run_analysis = self.run_analysis experiment_data.add_data(job, post_processing_callback=run_analysis) diff --git a/test/test_t2star.py b/test/test_t2star.py index 9cb7a3238c..5265da4c4a 100644 --- a/test/test_t2star.py +++ b/test/test_t2star.py @@ -22,7 +22,7 @@ from qiskit_experiments.composite import ParallelExperiment from qiskit_experiments.characterization import T2StarExperiment -from .utils import FakeJob +from test.utils import FakeJob class T2starBackend(BaseBackend): From 134cc8e1d7c2d9caf71f9fbbbaec4bd9812477a9 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Wed, 2 Jun 2021 15:20:50 +0300 Subject: [PATCH 08/10] fixed data processor tests --- test/data_processing/test_data_processing.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/data_processing/test_data_processing.py b/test/data_processing/test_data_processing.py index c07da481a6..4c87ed2ab2 100644 --- a/test/data_processing/test_data_processing.py +++ b/test/data_processing/test_data_processing.py @@ -16,6 +16,7 @@ from test.data_processing.fake_experiment import FakeExperiment, BaseDataProcessorTest import numpy as np +import unittest from qiskit.result.models import ExperimentResultData, ExperimentResult from qiskit.result import Result @@ -93,6 +94,7 @@ def test_to_real(self): [[3016514.0, -14548009.0], [-3404756.0, -16743348.0]], ], "metadata": {"experiment_type": "fake_test_experiment"}, + "job_id": "job-123", } expected_new = np.array([[1103.26, 2959.012], [442.17, -5279.41], [3016.514, -3404.7560]]) @@ -127,6 +129,7 @@ def test_to_imag(self): [[3016514.0, -14548009.0], [-3404756.0, -16743348.0]], ], "metadata": {"experiment_type": "fake_test_experiment"}, + "job_id": "job-123", } expected_new = np.array( @@ -384,3 +387,7 @@ def test_averaging_and_svd(self): expected_std = np.array([np.std([1, 1, 1, -1, 1, 1, 1, -1]) / np.sqrt(8.0)] * 2) self.assertTrue(np.allclose(processed, np.array([0.5, -0.5]) / np.sqrt(2.0))) self.assertTrue(np.allclose(error, expected_std)) + + +if __name__ == "__main__": + unittest.main() From 62b9735afa095bd28dcdfc0fad527a201cc36535 Mon Sep 17 00:00:00 2001 From: yaelbh Date: Wed, 2 Jun 2021 15:45:14 +0300 Subject: [PATCH 09/10] removed relative import --- test/test_t1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_t1.py b/test/test_t1.py index 035ecec6e0..a0d2833b67 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -26,7 +26,7 @@ from qiskit_experiments.composite import ParallelExperiment from qiskit_experiments.characterization import T1Experiment, T1Analysis -from .utils import FakeJob +from test.utils import FakeJob class T1Backend(BaseBackend): From 48fd05c7eaa014d2847f05393d7d8ad4d10c5275 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 15 Jun 2021 15:56:00 +0300 Subject: [PATCH 10/10] black --- qiskit_experiments/analysis/curve_analysis.py | 4 +++- .../characterization/t1_experiment.py | 18 +++++++------- .../characterization/t2star_experiment.py | 24 +++++++++---------- .../composite/composite_analysis.py | 2 +- .../composite/composite_experiment_data.py | 5 ++-- qiskit_experiments/experiment_data.py | 5 ++-- qiskit_experiments/matplotlib.py | 3 ++- test/test_t1.py | 2 +- test/test_t2star.py | 3 +-- 9 files changed, 34 insertions(+), 32 deletions(-) diff --git a/qiskit_experiments/analysis/curve_analysis.py b/qiskit_experiments/analysis/curve_analysis.py index e2e853b9ba..79ee35a9ef 100644 --- a/qiskit_experiments/analysis/curve_analysis.py +++ b/qiskit_experiments/analysis/curve_analysis.py @@ -851,7 +851,9 @@ def _run_analysis( analysis_result = AnalysisResultV1( result_data=result_data, result_type="RB", - device_components=[Qubit(qubit) for qubit in experiment_data.data(0)["metadata"]["qubits"]], + device_components=[ + Qubit(qubit) for qubit in experiment_data.data(0)["metadata"]["qubits"] + ], experiment_id=experiment_data.experiment_id, ) diff --git a/qiskit_experiments/characterization/t1_experiment.py b/qiskit_experiments/characterization/t1_experiment.py index fbc5e75739..3edb5580ed 100644 --- a/qiskit_experiments/characterization/t1_experiment.py +++ b/qiskit_experiments/characterization/t1_experiment.py @@ -130,15 +130,15 @@ def fit_fun(x, a, tau, c): fit_result = curve_fit(fit_fun, xdata, ydata, init, sigma=sigma, bounds=bounds) 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"] - ), - } + "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": diff --git a/qiskit_experiments/characterization/t2star_experiment.py b/qiskit_experiments/characterization/t2star_experiment.py index eea5e8e7bb..63593e9424 100644 --- a/qiskit_experiments/characterization/t2star_experiment.py +++ b/qiskit_experiments/characterization/t2star_experiment.py @@ -90,9 +90,7 @@ def _format_plot(ax, 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( - data, lambda datum: level2_probability(datum, "0") - ) + xdata, ydata, sigma = process_curve_data(data, lambda datum: level2_probability(datum, "0")) si_xdata = xdata * conversion_factor t2star_estimate = np.mean(si_xdata) @@ -115,16 +113,16 @@ def _format_plot(ax, unit): # Output unit is 'sec', regardless of the unit used in the input result_data = { - "t2star_value": fit_result["popt"][1], - "frequency_value": fit_result["popt"][2], - "stderr": fit_result["popt_err"][1], - "unit": "s", - "label": "T2*", - "fit": fit_result, - "quality": self._fit_quality( - fit_result["popt"], fit_result["popt_err"], fit_result["reduced_chisq"] - ), - } + "t2star_value": fit_result["popt"][1], + "frequency_value": fit_result["popt"][2], + "stderr": fit_result["popt_err"][1], + "unit": "s", + "label": "T2*", + "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": diff --git a/qiskit_experiments/composite/composite_analysis.py b/qiskit_experiments/composite/composite_analysis.py index 790c1feb83..7dec6ee54b 100644 --- a/qiskit_experiments/composite/composite_analysis.py +++ b/qiskit_experiments/composite/composite_analysis.py @@ -80,6 +80,6 @@ def _run_analysis(self, experiment_data: CompositeExperimentData, **options): }, result_type="composite", device_components=[Qubit(qidx) for qidx in sub_qubits], - experiment_id=experiment_data.experiment_id + experiment_id=experiment_data.experiment_id, ) return [analysis_result], None diff --git a/qiskit_experiments/composite/composite_experiment_data.py b/qiskit_experiments/composite/composite_experiment_data.py index 80766c7afc..0e3fdc5040 100644 --- a/qiskit_experiments/composite/composite_experiment_data.py +++ b/qiskit_experiments/composite/composite_experiment_data.py @@ -44,8 +44,9 @@ def __init__( ) # Initialize sub experiments - self._components = [expr.__experiment_data__(expr) - for expr in experiment.component_experiment()] + self._components = [ + expr.__experiment_data__(expr) for expr in experiment.component_experiment() + ] def __str__(self): line = 51 * "-" diff --git a/qiskit_experiments/experiment_data.py b/qiskit_experiments/experiment_data.py index 899820a815..47fd56eb2e 100644 --- a/qiskit_experiments/experiment_data.py +++ b/qiskit_experiments/experiment_data.py @@ -53,8 +53,9 @@ def __init__( """ self._experiment = experiment - super().__init__(experiment_type=experiment.experiment_type if experiment else "unknown", - backend=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 005a7aa97b..d96c5a569f 100644 --- a/qiskit_experiments/matplotlib.py +++ b/qiskit_experiments/matplotlib.py @@ -40,8 +40,9 @@ def wrapped(*args, **kwargs): # 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') + pyplot.switch_backend("Agg") try: ret_val = func(*args, **kwargs) finally: diff --git a/test/test_t1.py b/test/test_t1.py index 7d0fad9ac8..97e7f78ef9 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -228,7 +228,7 @@ def test_t1_analysis(self): res = T1Analysis()._run_analysis(data)[0] self.assertEqual(res.quality, ResultQuality.GOOD) - self.assertAlmostEqual(res.data()["value"], 25e-9, delta=3) + self.assertAlmostEqual(res.data()["value"], 25e-9, delta=3) def test_t1_metadata(self): """ diff --git a/test/test_t2star.py b/test/test_t2star.py index 3a15b3f9ad..8a97d5713c 100644 --- a/test/test_t2star.py +++ b/test/test_t2star.py @@ -260,8 +260,7 @@ def test_t2star_parallel(self): sub_res = expdata.component_experiment_data(i).analysis_result(0) sub_rest_data = sub_res.data() self.assertAlmostEqual( - sub_rest_data["t2star_value"], t2star[i], - delta=0.08 * sub_rest_data["t2star_value"] + sub_rest_data["t2star_value"], t2star[i], delta=0.08 * sub_rest_data["t2star_value"] ) self.assertAlmostEqual( sub_rest_data["frequency_value"],