From ce485f84bfd60d240d13c2c6bd70306b0e820fef Mon Sep 17 00:00:00 2001 From: Helena Zhang Date: Thu, 23 May 2024 20:20:56 -0400 Subject: [PATCH] Remove deprecated code for 0.7 (#1452) Removes code scheduled for removal in 0.7. For analysis results, `dataframe` is still set to `False` by default as `True` is a breaking change. --- docs/conf.py | 8 - docs/howtos/artifacts.rst | 2 +- docs/manuals/verification/quantum_volume.rst | 9 +- .../verification/randomized_benchmarking.rst | 3 +- docs/tutorials/curve_analysis.rst | 13 +- docs/tutorials/getting_started.rst | 28 +- qiskit_experiments/curve_analysis/__init__.py | 18 - .../curve_analysis/base_curve_analysis.py | 39 -- .../composite_curve_analysis.py | 48 +- .../curve_analysis/curve_analysis.py | 14 +- .../curve_analysis/curve_data.py | 187 +------ .../curve_analysis/visualization/__init__.py | 26 - .../visualization/base_drawer.py | 296 ----------- .../curve_analysis/visualization/curves.py | 210 -------- .../visualization/fit_result_plotters.py | 470 ------------------ .../visualization/mpl_drawer.py | 409 --------------- .../curve_analysis/visualization/style.py | 57 --- qiskit_experiments/framework/backend_data.py | 82 --- .../framework/composite/batch_experiment.py | 14 +- .../framework/composite/composite_analysis.py | 13 +- .../composite/composite_experiment.py | 13 +- .../composite/parallel_experiment.py | 13 +- .../framework/experiment_data.py | 20 +- .../randomized_benchmarking/clifford_utils.py | 53 +- .../library/tomography/fitters/lstsq_utils.py | 7 - qiskit_experiments/visualization/__init__.py | 2 +- .../visualization/drawers/__init__.py | 1 - .../visualization/drawers/base_drawer.py | 4 +- .../drawers/legacy_curve_compat_drawer.py | 246 --------- .../0_6_deprecations-9a399c48c2d461f1.yaml | 29 ++ test/curve_analysis/test_baseclass.py | 35 -- .../test_db_experiment_data.py | 12 +- 32 files changed, 77 insertions(+), 2304 deletions(-) delete mode 100644 qiskit_experiments/curve_analysis/visualization/__init__.py delete mode 100644 qiskit_experiments/curve_analysis/visualization/base_drawer.py delete mode 100644 qiskit_experiments/curve_analysis/visualization/curves.py delete mode 100644 qiskit_experiments/curve_analysis/visualization/fit_result_plotters.py delete mode 100644 qiskit_experiments/curve_analysis/visualization/mpl_drawer.py delete mode 100644 qiskit_experiments/curve_analysis/visualization/style.py delete mode 100644 qiskit_experiments/visualization/drawers/legacy_curve_compat_drawer.py create mode 100644 releasenotes/notes/0_6_deprecations-9a399c48c2d461f1.yaml diff --git a/docs/conf.py b/docs/conf.py index d286d13fa4..27d4ba580d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -213,7 +213,6 @@ def setup(app): # Should come up with better way to address this from qiskit_experiments.curve_analysis import ParameterRepr -from qiskit_experiments.curve_analysis import SeriesDef def maybe_skip_member(app, what, name, obj, skip, options): @@ -227,9 +226,6 @@ def maybe_skip_member(app, what, name, obj, skip, options): "y", "y_err", "name", - "filter_kwargs", - "fit_func", - "signature", "artifact_id", "artifact_data", "device_components", @@ -239,10 +235,6 @@ def maybe_skip_member(app, what, name, obj, skip, options): skip_members = [ ParameterRepr.repr, ParameterRepr.unit, - SeriesDef.plot_color, - SeriesDef.plot_symbol, - SeriesDef.model_description, - SeriesDef.canvas, ] if not skip: return (name in skip_names or obj in skip_members) and what == "attribute" diff --git a/docs/howtos/artifacts.rst b/docs/howtos/artifacts.rst index 57b4222f71..b3075507f4 100644 --- a/docs/howtos/artifacts.rst +++ b/docs/howtos/artifacts.rst @@ -36,7 +36,7 @@ artifacts as a list of :class:`.ArtifactData` objects accessed by :meth:`.Experi backend = AerSimulator.from_backend(FakePerth()) exp1 = T1(physical_qubits=[0], delays=np.arange(1e-6, 6e-4, 5e-5)) exp2 = T1(physical_qubits=[1], delays=np.arange(1e-6, 6e-4, 5e-5)) - data = ParallelExperiment([exp1, exp2], flatten_results=True).run(backend).block_for_results() + data = ParallelExperiment([exp1, exp2]).run(backend).block_for_results() data.artifacts() Artifacts can be accessed using either the artifact ID, which has to be unique in each diff --git a/docs/manuals/verification/quantum_volume.rst b/docs/manuals/verification/quantum_volume.rst index 1fb06a9565..f73cc89ac2 100644 --- a/docs/manuals/verification/quantum_volume.rst +++ b/docs/manuals/verification/quantum_volume.rst @@ -158,7 +158,7 @@ Extracting the maximum Quantum Volume. .. jupyter-execute:: qv_values = [ - batch_expdata.child_data(i).analysis_results("quantum_volume").value + batch_expdata.analysis_results("quantum_volume")[i].value for i in range(batch_exp.num_experiments) ] @@ -169,10 +169,9 @@ Extracting the maximum Quantum Volume. for i in range(batch_exp.num_experiments): print(f"\nComponent experiment {i}") - sub_data = batch_expdata.child_data(i) - display(sub_data.figure(0)) - for result in sub_data.analysis_results(): - print(result) + display(batch_expdata.figure(i)) + for result in batch_expdata.analysis_results(): + print(result) References ---------- diff --git a/docs/manuals/verification/randomized_benchmarking.rst b/docs/manuals/verification/randomized_benchmarking.rst index a955d909ae..d38cbe02b7 100644 --- a/docs/manuals/verification/randomized_benchmarking.rst +++ b/docs/manuals/verification/randomized_benchmarking.rst @@ -182,8 +182,7 @@ The EPGs of two-qubit RB are analyzed with the corrected EPC if available. [ StandardRB((qubit,), lengths_1_qubit, num_samples=num_samples, seed=seed) for qubit in qubits - ], - flatten_results=True, + ] ) expdata_1q = single_exps.run(backend).block_for_results() diff --git a/docs/tutorials/curve_analysis.rst b/docs/tutorials/curve_analysis.rst index 81af38bb67..828ad83740 100644 --- a/docs/tutorials/curve_analysis.rst +++ b/docs/tutorials/curve_analysis.rst @@ -343,7 +343,7 @@ For example, :class:`.StarkRamseyXYAmpScanAnalysis` defines four `series` labels in its ``data_subfit_map`` option (``Xpos``, ``Ypos``, ``Xneg``, ``Yneg``) but only two models (``FREQpos``, ``FREQneg``) whose names do not match the series labels. -It does this by overriding the ``CurveData._format_data()`` method and adding +It does this by overriding the ``CurveAnalysis._format_data()`` method and adding its own series to the :class:`.ScatterTable` with series labels to match its fit model names (by combining ``Xpos`` and ``Ypos`` series data into a ``FREQpos`` series and similary for the series with names ending with ``neg``). @@ -400,11 +400,6 @@ See the :doc:`Artifacts how-to ` for more information. Curve Analysis workflow ----------------------- -.. warning:: - - :class:`CurveData` dataclass is replaced with :class:`.ScatterTable` dataframe. - This class will be deprecated and removed in the future release. - Typically curve analysis performs fitting as follows. This workflow is defined in the method :meth:`CurveAnalysis._run_analysis`. @@ -534,9 +529,9 @@ one can get the list of parameters with the :attr:`CurveAnalysis.parameters`. Ea boundary value can be a tuple of floats representing minimum and maximum values. Apart from user provided guesses, the analysis can systematically generate those values -with the method :meth:`_generate_fit_guesses`, which is called with the :class:`CurveData` -dataclass. If the analysis contains multiple model definitions, we can get the subset -of curve data with :meth:`.CurveData.get_subset_of` using the name of the series. A +with the method :meth:`_generate_fit_guesses`, which is called with the :class:`.ScatterTable` +class. If the analysis contains multiple model definitions, we can get the subset +of curve data with :meth:`.ScatterTable.get_subset_of` using the name of the series. A developer can implement the algorithm to generate initial guesses and boundaries by using this curve data object, which will be provided to the fitter. Note that there are several common initial guess estimators available in :mod:`curve_analysis.guess`. diff --git a/docs/tutorials/getting_started.rst b/docs/tutorials/getting_started.rst index 50321e6a5c..5c5092b0cd 100644 --- a/docs/tutorials/getting_started.rst +++ b/docs/tutorials/getting_started.rst @@ -384,7 +384,7 @@ simultaneously on the same device: child_exp1 = T1(physical_qubits=(2,), delays=delays) child_exp2 = StandardRB(physical_qubits=(3,1), lengths=np.arange(1,100,10), num_samples=2) - parallel_exp = ParallelExperiment([child_exp1, child_exp2], flatten_results=False) + parallel_exp = ParallelExperiment([child_exp1, child_exp2]) Note that when the transpile and run options are set for a composite experiment, the child experiments's options are also set to the same options recursively. Let's examine @@ -437,35 +437,19 @@ arbitrarily to make complex composite experiments. Viewing child experiment data ----------------------------- -The experiment data returned from a composite experiment contains individual analysis -results for each child experiment that can be accessed using -:meth:`~.ExperimentData.child_data`. By default, the parent data object does not contain -analysis results. +The experiment data returned from a composite experiment contains analysis +results for each child experiment in the parent experiment. .. note:: - This behavior will be updated in Qiskit Experiments 0.7. By default, all analysis results will be stored in the parent data object, - and you need to explicitly set ``flatten_results=False`` to generate child data objects. - -.. jupyter-execute:: - - parallel_data = parallel_exp.run(backend, seed_simulator=101).block_for_results() - - for i, sub_data in enumerate(parallel_data.child_data()): - print("Component experiment",i) - display(sub_data.figure(0)) - for result in sub_data.analysis_results(): - print(result) - -If you want the parent data object to contain the analysis results instead, you can set -the ``flatten_results`` flag to true to flatten the results of all component experiments -into one level: + and you need to explicitly set ``flatten_results=False`` to generate child + data objects in the legacy format. .. jupyter-execute:: parallel_exp = ParallelExperiment( - [T1(physical_qubits=(i,), delays=delays) for i in range(2)], flatten_results=True + [T1(physical_qubits=(i,), delays=delays) for i in range(2)] ) parallel_data = parallel_exp.run(backend, seed_simulator=101).block_for_results() diff --git a/qiskit_experiments/curve_analysis/__init__.py b/qiskit_experiments/curve_analysis/__init__.py index 992946d28e..f55b6ece56 100644 --- a/qiskit_experiments/curve_analysis/__init__.py +++ b/qiskit_experiments/curve_analysis/__init__.py @@ -40,21 +40,10 @@ :toctree: ../stubs/ ScatterTable - SeriesDef - CurveData CurveFitResult ParameterRepr FitOptions -Visualization -============= - -.. autosummary:: - :toctree: ../stubs/ - - BaseCurveDrawer - MplCurveDrawer - Standard Analysis Library ========================= @@ -120,18 +109,14 @@ from .composite_curve_analysis import CompositeCurveAnalysis from .scatter_table import ScatterTable from .curve_data import ( - CurveData, CurveFitResult, - FitData, FitOptions, ParameterRepr, - SeriesDef, ) from .curve_fit import ( process_curve_data, process_multi_curve_data, ) -from .visualization import BaseCurveDrawer, MplCurveDrawer from . import guess from . import fit_function from . import utils @@ -146,6 +131,3 @@ ErrorAmplificationAnalysis, BlochTrajectoryAnalysis, ) - -# deprecated -from .visualization import plot_curve_fit, plot_errorbar, plot_scatter, FitResultPlotters diff --git a/qiskit_experiments/curve_analysis/base_curve_analysis.py b/qiskit_experiments/curve_analysis/base_curve_analysis.py index cd9c0128d7..c477a2dfb7 100644 --- a/qiskit_experiments/curve_analysis/base_curve_analysis.py +++ b/qiskit_experiments/curve_analysis/base_curve_analysis.py @@ -14,14 +14,11 @@ Base class of curve analysis. """ -import warnings from abc import ABC, abstractmethod from typing import Dict, List, Union import lmfit -from qiskit.utils.deprecation import deprecate_func - from qiskit_experiments.data_processing import DataProcessor from qiskit_experiments.data_processing.processor_library import get_processor from qiskit_experiments.framework import ( @@ -31,10 +28,8 @@ Options, ) from qiskit_experiments.visualization import ( - BaseDrawer, BasePlotter, CurvePlotter, - LegacyCurveCompatDrawer, MplDrawer, ) @@ -130,20 +125,6 @@ def plotter(self) -> BasePlotter: """A short-cut to the curve plotter instance.""" return self._options.plotter - @property - @deprecate_func( - since="0.5", - additional_msg="Use `plotter` from the new visualization module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def drawer(self) -> BaseDrawer: - """A short-cut for curve drawer instance, if set. ``None`` otherwise.""" - if isinstance(self.plotter.drawer, LegacyCurveCompatDrawer): - return self.plotter.drawer._curve_drawer - else: - return None - @classmethod def _default_options(cls) -> Options: """Return default analysis options. @@ -159,8 +140,6 @@ def _default_options(cls) -> Options: not create a figure. This overrides the behavior of ``generate_figures``. return_fit_parameters (bool): (Deprecated) Set ``True`` to return all fit model parameters with details of the fit outcome. Default to ``False``. - return_data_points (bool): (Deprecated) Set ``True`` to include in the analysis result - the formatted data points given to the fitter. Default to ``False``. data_processor (Callable): A callback function to format experiment data. This can be a :class:`.DataProcessor` instance that defines the `self.__call__` method. @@ -211,7 +190,6 @@ def _default_options(cls) -> Options: options.plot_raw_data = False options.plot_residuals = False options.return_fit_parameters = True - options.return_data_points = False options.data_processor = None options.normalization = False options.average_method = "shots_weighted" @@ -405,20 +383,3 @@ def _initialize( if not data_processor.is_trained: data_processor.train(data=experiment_data.data()) self.set_options(data_processor=data_processor) - - # Check if a model contains legacy data mapping option. - data_subfit_map = {} - for model in self.models: - if "data_sort_key" in model.opts: - data_subfit_map[model._name] = model.opts["data_sort_key"] - del model.opts["data_sort_key"] - if data_subfit_map: - warnings.warn( - "Setting 'data_sort_key' to an LMFIT model constructor is no longer " - "valid configuration of the model. " - "Use 'data_subfit_map' option in the analysis options. " - "This warning will be dropped in v0.6 along with the support for the " - "'data_sort_key' in the LMFIT model options.", - DeprecationWarning, - ) - self.set_options(data_subfit_map=data_subfit_map) diff --git a/qiskit_experiments/curve_analysis/composite_curve_analysis.py b/qiskit_experiments/curve_analysis/composite_curve_analysis.py index 134cccf273..bede9179b2 100644 --- a/qiskit_experiments/curve_analysis/composite_curve_analysis.py +++ b/qiskit_experiments/curve_analysis/composite_curve_analysis.py @@ -22,8 +22,6 @@ import numpy as np import pandas as pd -from qiskit.utils.deprecation import deprecate_func - from qiskit_experiments.framework import ( AnalysisResultData, BaseAnalysis, @@ -31,10 +29,8 @@ Options, ) from qiskit_experiments.visualization import ( - BaseDrawer, BasePlotter, CurvePlotter, - LegacyCurveCompatDrawer, MplDrawer, ) @@ -76,7 +72,6 @@ class CompositeCurveAnalysis(BaseAnalysis): for qi in (0, 1): analysis = curve.OscillationAnalysis(name=f"init{qi}") analysis.set_options( - return_fit_parameters=["freq"], filter_data={"init_state": qi}, ) analysis = CompositeCurveAnalysis(analyses=analyses) @@ -147,20 +142,6 @@ def plotter(self) -> BasePlotter: """A short-cut to the plotter instance.""" return self._options.plotter - @property - @deprecate_func( - since="0.5", - additional_msg="Use `plotter` from the new visualization module instead.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def drawer(self) -> BaseDrawer: - """A short-cut for curve drawer instance, if set. ``None`` otherwise.""" - if hasattr(self._options, "curve_drawer"): - return self._options.curve_drawer - else: - return None - def analyses( self, index: Optional[Union[str, int]] = None ) -> Union[BaseCurveAnalysis, List[BaseCurveAnalysis]]: @@ -273,8 +254,6 @@ def _default_options(cls) -> Options: This is ``True`` by default. return_fit_parameters (bool): (Deprecated) Set ``True`` to return all fit model parameters with details of the fit outcome. Default to ``False``. - return_data_points (bool): (Deprecated) Set ``True`` to include in the analysis result - the formatted data points given to the fitter. Default to ``False``. extra (Dict[str, Any]): A dictionary that is appended to all database entries as extra information. """ @@ -283,7 +262,6 @@ def _default_options(cls) -> Options: plotter=CurvePlotter(MplDrawer()), plot=True, return_fit_parameters=False, - return_data_points=False, extra={}, ) @@ -293,27 +271,6 @@ def _default_options(cls) -> Options: return options def set_options(self, **fields): - # TODO remove this in Qiskit Experiments 0.6 - if "curve_drawer" in fields: - warnings.warn( - "The option 'curve_drawer' is replaced with 'plotter'. " - "This option will be removed in Qiskit Experiments 0.6.", - DeprecationWarning, - stacklevel=2, - ) - # Set the plotter drawer to `curve_drawer`. If `curve_drawer` is the right type, set it - # directly. If not, wrap it in a compatibility drawer. - if isinstance(fields["curve_drawer"], BaseDrawer): - plotter = self.options.plotter - plotter.drawer = fields.pop("curve_drawer") - fields["plotter"] = plotter - else: - drawer = fields["curve_drawer"] - compat_drawer = LegacyCurveCompatDrawer(drawer) - plotter = self.options.plotter - plotter.drawer = compat_drawer - fields["plotter"] = plotter - for field in fields: if not hasattr(self.options, field): warnings.warn( @@ -349,10 +306,7 @@ def _run_analysis( metadata = analysis.options.extra metadata["group"] = analysis.name analysis.set_options( - plot=False, - extra=metadata, - return_fit_parameters=self.options.return_fit_parameters, - return_data_points=self.options.return_data_points, + plot=False, extra=metadata, return_fit_parameters=self.options.return_fit_parameters ) results, _ = analysis._run_analysis(experiment_data) for res in results: diff --git a/qiskit_experiments/curve_analysis/curve_analysis.py b/qiskit_experiments/curve_analysis/curve_analysis.py index 7604432953..d2742afdd4 100644 --- a/qiskit_experiments/curve_analysis/curve_analysis.py +++ b/qiskit_experiments/curve_analysis/curve_analysis.py @@ -34,7 +34,7 @@ from qiskit_experiments.data_processing.exceptions import DataProcessorError from qiskit_experiments.visualization import PlotStyle -from .base_curve_analysis import BaseCurveAnalysis, DATA_ENTRY_PREFIX, PARAMS_ENTRY_PREFIX +from .base_curve_analysis import BaseCurveAnalysis, PARAMS_ENTRY_PREFIX from .curve_data import FitOptions, CurveFitResult from .scatter_table import ScatterTable from .utils import ( @@ -62,7 +62,7 @@ class CurveAnalysis(BaseCurveAnalysis): to create Y data with uncertainty. X data and other metadata are generated within this method by inspecting the circuit metadata. The series classification is also performed based upon the - matching of circuit metadata and :attr:`SeriesDef.filter_kwargs`. + matching of circuit metadata. .. rubric:: _format_data @@ -704,16 +704,6 @@ def _run_analysis( ) ) - if self.options.return_data_points: - # Add raw data points - warnings.warn( - f"{DATA_ENTRY_PREFIX + self.name} has been moved to experiment data artifacts. " - "Saving this result with 'return_data_points'=True will be disabled in " - "Qiskit Experiments 0.7.", - DeprecationWarning, - ) - result_data.extend(self._create_curve_data(curve_data=formatted_subset)) - artifacts.append( ArtifactData( name="curve_data", diff --git a/qiskit_experiments/curve_analysis/curve_data.py b/qiskit_experiments/curve_analysis/curve_data.py index 13263227bb..25fb4e0a7e 100644 --- a/qiskit_experiments/curve_analysis/curve_data.py +++ b/qiskit_experiments/curve_analysis/curve_data.py @@ -15,139 +15,15 @@ """ import dataclasses import itertools -import inspect -from typing import Any, Dict, Union, List, Tuple, Optional, Iterable, Callable +from typing import Any, Dict, Union, List, Tuple, Optional, Iterable import numpy as np import uncertainties from uncertainties.unumpy import uarray -from qiskit.utils.deprecation import deprecate_func - from qiskit_experiments.exceptions import AnalysisError -@dataclasses.dataclass(frozen=True) -class SeriesDef: - """A dataclass to describe the definition of the curve. - - Attributes: - fit_func: A callable that defines the fit model of this curve. The argument names - in the callable are parsed to create the fit parameter list, which will appear - in the analysis results. The first argument should be ``x`` that represents - X-values that the experiment sweeps. - filter_kwargs: Optional. Dictionary of properties that uniquely identifies this series. - This dictionary is used for data processing. - This must be provided when the curve analysis consists of multiple series. - name: Optional. Name of this series. - plot_color: Optional. String representation of the color that is used to draw fit data - and data points in the output figure. This depends on the drawer class - being set to the curve analysis options. Usually this conforms to the - Matplotlib color names. - plot_symbol: Optional. String representation of the marker shape that is used to draw - data points in the output figure. This depends on the drawer class - being set to the curve analysis options. Usually this conforms to the - Matplotlib symbol names. - canvas: Optional. Index of sub-axis in the output figure that draws this curve. - This option is valid only when the drawer instance provides multi-axis drawing. - model_description: Optional. Arbitrary string representation of this fit model. - This string will appear in the analysis results as a part of metadata. - """ - - fit_func: Callable - filter_kwargs: Dict[str, Any] = dataclasses.field(default_factory=dict) - name: str = "Series-0" - plot_color: str = "black" - plot_symbol: str = "o" - canvas: Optional[int] = None - model_description: Optional[str] = None - signature: Tuple[str, ...] = dataclasses.field(init=False) - - @deprecate_func( - since="0.5", - additional_msg="SeriesDef has been replaced by the LMFIT module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def __init__(self, *args, **kwargs): - self.args = args - self.kwargs = kwargs - - def __post_init__(self): - """Parse the fit function signature to extract the names of the variables. - Fit functions take arguments F(x, p0, p1, p2, ...) thus the first value should be excluded. - """ - signature = list(inspect.signature(self.fit_func).parameters.keys()) - fitparams = tuple(signature[1:]) - - # Note that this dataclass is frozen - object.__setattr__(self, "signature", fitparams) - - -@dataclasses.dataclass(frozen=True) -class CurveData: - """A dataclass that manages the multiple arrays comprising the dataset for fitting. - - This dataset can consist of X, Y values from multiple series. - To extract curve data of the particular series, :meth:`get_subset_of` can be used. - - Attributes: - x: X-values that experiment sweeps. - y: Y-values that observed and processed by the data processor. - y_err: Uncertainty of the Y-values which is created by the data processor. - Usually this assumes standard error. - shots: Number of shots used in the experiment to obtain the Y-values. - data_allocation: List with identical size with other arrays. - The value indicates the series index of the corresponding element. - This is classified based upon the matching of :attr:`SeriesDef.filter_kwargs` - with the circuit metadata of the corresponding data index. - If metadata doesn't match with any series definition, element is filled with ``-1``. - labels: List of curve labels. The list index corresponds to the series index. - """ - - x: np.ndarray - y: np.ndarray - y_err: np.ndarray - shots: np.ndarray - data_allocation: np.ndarray - labels: List[str] - - @deprecate_func( - since="0.6", - additional_msg="CurveData is replaced by `ScatterTable`'s DataFrame representation.", - removal_timeline="after 0.7", - package_name="qiskit-experiments", - ) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def get_subset_of(self, index: Union[str, int]) -> "CurveData": - """Filter data by series name or index. - - Args: - index: Series index of name. - - Returns: - A subset of data corresponding to a particular series. - """ - if isinstance(index, int): - _index = index - _name = self.labels[index] - else: - _index = self.labels.index(index) - _name = index - - locs = self.data_allocation == _index - return CurveData( - x=self.x[locs], - y=self.y[locs], - y_err=self.y_err[locs], - shots=self.shots[locs], - data_allocation=np.full(np.count_nonzero(locs), _index), - labels=[_name], - ) - - class CurveFitResult: """Result of Qiskit Experiment curve analysis.""" @@ -352,67 +228,6 @@ def __json_decode__(cls, value): return cls(**value) -@dataclasses.dataclass(frozen=True) -class FitData: - """A dataclass to store the outcome of the fitting. - - Attributes: - popt: List of optimal parameter values with uncertainties if available. - popt_keys: List of parameter names being fit. - pcov: Covariance matrix from the least square fitting. - reduced_chisq: Reduced Chi-squared value for the fit curve. - dof: Degree of freedom in this fit model. - x_data: X-values provided to the fitter. - y_data: Y-values provided to the fitter. - """ - - popt: List[uncertainties.UFloat] - popt_keys: List[str] - pcov: np.ndarray - reduced_chisq: float - dof: int - x_data: np.ndarray - y_data: np.ndarray - - @deprecate_func( - since="0.5", - additional_msg="Fit data is replaced with 'CurveFitResult' based on LMFIT minimizer result.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - @property - def x_range(self) -> Tuple[float, float]: - """Range of x values.""" - return np.min(self.x_data), np.max(self.x_data) - - @property - def y_range(self) -> Tuple[float, float]: - """Range of y values.""" - return np.min(self.y_data), np.max(self.y_data) - - def fitval(self, key: str) -> uncertainties.UFloat: - """A helper method to get fit value object from parameter key name. - - Args: - key: Name of parameters to extract. - - Returns: - A UFloat object which functions as a standard Python float object - but with automatic error propagation. - - Raises: - ValueError: When specified parameter is not defined. - """ - try: - index = self.popt_keys.index(key) - return self.popt[index] - except ValueError as ex: - raise ValueError(f"Parameter {key} is not defined.") from ex - - @dataclasses.dataclass class ParameterRepr: """Detailed description of fitting parameter. diff --git a/qiskit_experiments/curve_analysis/visualization/__init__.py b/qiskit_experiments/curve_analysis/visualization/__init__.py deleted file mode 100644 index 42a7c838f2..0000000000 --- a/qiskit_experiments/curve_analysis/visualization/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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. -""" -Deprecated Visualization Functions. - -.. note:: - This module is deprecated and replaced by :mod:`qiskit_experiments.visualization`. The new - visualization module contains classes to manage drawing to a figure canvas and plotting data - obtained from an experiment or analysis. -""" - -from . import fit_result_plotters -from .base_drawer import BaseCurveDrawer -from .curves import plot_curve_fit, plot_errorbar, plot_scatter -from .fit_result_plotters import FitResultPlotters -from .mpl_drawer import MplCurveDrawer -from .style import PlotterStyle diff --git a/qiskit_experiments/curve_analysis/visualization/base_drawer.py b/qiskit_experiments/curve_analysis/visualization/base_drawer.py deleted file mode 100644 index 19f3206ee8..0000000000 --- a/qiskit_experiments/curve_analysis/visualization/base_drawer.py +++ /dev/null @@ -1,296 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Curve drawer abstract class.""" - -from abc import ABC, abstractmethod -from typing import Dict, Optional, Sequence - -from qiskit.utils.deprecation import deprecate_func - -from qiskit_experiments.framework import Options - - -class BaseCurveDrawer(ABC): - """Abstract class for the serializable Qiskit Experiments curve drawer. - - A curve drawer may be implemented by different drawing backends such as matplotlib - or plotly. Sub-classes that wrap these backends by subclassing `BaseCurveDrawer` must - implement the following abstract methods. - - initialize_canvas - - This method should implement a protocol to initialize a drawing canvas - with user input ``axis`` object. Note that curve analysis drawer - supports visualization of experiment results in multiple canvases - tiled into N (row) x M (column) inset grids, which is specified in the option ``subplots``. - By default, this is N=1, M=1 and thus no inset grid will be initialized. - The data points to draw might be provided with a canvas number defined in - :attr:`SeriesDef.canvas` which defaults to ``None``, i.e. no-inset grids. - - This method should first check the drawing options for the axis object - and initialize the axis only when it is not provided by the options. - Once axis is initialized, this is set to the instance member ``self._axis``. - - format_canvas - - This method should implement a protocol to format the appearance of canvas. - Typically, it updates axis and tick labels. Note that the axis SI unit - may be specified in the drawing options. In this case, axis numbers should be - auto-scaled with the unit prefix. - - draw_raw_data - - This method is called after data processing is completed. - This method draws raw experiment data points on the canvas. - - draw_formatted_data - - This method is called after data formatting is completed. - The formatted data might be averaged over the same x values, - or smoothed by a filtering algorithm, depending on how analysis class is implemented. - This method is called with error bars of y values and the name of the curve. - - draw_fit_line - - This method is called after fitting is completed and when there is valid fit outcome. - This method is called with the interpolated x and y values. - - draw_confidence_interval - - This method is called after fitting is completed and when there is valid fit outcome. - This method is called with the interpolated x and a pair of y values - that represent the upper and lower bound within certain confidence interval. - This might be called multiple times with different interval sizes. - - draw_fit_report - - This method is called after fitting is completed and when there is valid fit outcome. - This method is called with the list of analysis results and the reduced chi-squared values. - The fit report should be generated to show this information on the canvas. - - """ - - @deprecate_func( - since="0.5", - additional_msg="Plotting and drawing functionality has been moved to the new " - "`qiskit_experiments.visualization` module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def __init__(self): - self._options = self._default_options() - self._set_options = set() - self._axis = None - self._curves = [] - - @property - def options(self) -> Options: - """Return the drawing options.""" - return self._options - - @classmethod - def _default_options(cls) -> Options: - """Return default draw options. - - Draw Options: - axis (Any): Arbitrary object that can be used as a drawing canvas. - subplots (Tuple[int, int]): Number of rows and columns when the experimental - result is drawn in the multiple windows. - xlabel (Union[str, List[str]]): X-axis label string of the output figure. - If there are multiple columns in the canvas, this could be a list of labels. - ylabel (Union[str, List[str]]): Y-axis label string of the output figure. - If there are multiple rows in the canvas, this could be a list of labels. - xlim (Tuple[float, float]): Min and max value of the horizontal axis. - If not provided, it is automatically scaled based on the input data points. - ylim (Tuple[float, float]): Min and max value of the vertical axis. - If not provided, it is automatically scaled based on the input data points. - xval_unit (str): SI unit of x values. No prefix is needed here. - For example, when the x values represent time, this option will be just "s" - rather than "ms". In the output figure, the prefix is automatically selected - based on the maximum value in this axis. If your x values are in [1e-3, 1e-4], - they are displayed as [1 ms, 10 ms]. This option is likely provided by the - analysis class rather than end-users. However, users can still override - if they need different unit notation. By default, this option is set to ``None``, - and no scaling is applied. If nothing is provided, the axis numbers will be - displayed in the scientific notation. - yval_unit (str): Unit of y values. See ``xval_unit`` for details. - figsize (Tuple[int, int]): A tuple of two numbers representing the size of - the output figure (width, height). Note that this is applicable - only when ``axis`` object is not provided. If any canvas object is provided, - the figure size associated with the axis is preferentially applied. - legend_loc (str): Vertical and horizontal location of the curve legend window in - a single string separated by a space. This defaults to ``center right``. - Vertical position can be ``upper``, ``center``, ``lower``. - Horizontal position can be ``right``, ``center``, ``left``. - tick_label_size (int): Size of text representing the axis tick numbers. - axis_label_size (int): Size of text representing the axis label. - fit_report_rpos (Tuple[int, int]): A tuple of numbers showing the location of - the fit report window. These numbers are horizontal and vertical position - of the top left corner of the window in the relative coordinate - on the output figure, i.e. ``[0, 1]``. - The fit report window shows the selected fit parameters and the reduced - chi-squared value. - fit_report_text_size (int): Size of text in the fit report window. - plot_sigma (List[Tuple[float, float]]): A list of two number tuples - showing the configuration to write confidence intervals for the fit curve. - The first argument is the relative sigma (n_sigma), and the second argument is - the transparency of the interval plot in ``[0, 1]``. - Multiple n_sigma intervals can be drawn for the single curve. - plot_options (Dict[str, Dict[str, Any]]): A dictionary of plot options for each curve. - This is keyed on the model name for each curve. Sub-dictionary is expected to have - following three configurations, "canvas", "color", and "symbol"; "canvas" is the - integer index of axis (when multi-canvas plot is set), "color" is the - color of the curve, and "symbol" is the marker style of the curve for scatter plots. - figure_title (str): Title of the figure. Defaults to None, i.e. nothing is shown. - """ - return Options( - axis=None, - subplots=(1, 1), - xlabel=None, - ylabel=None, - xlim=None, - ylim=None, - xval_unit=None, - yval_unit=None, - figsize=(8, 5), - legend_loc="center right", - tick_label_size=14, - axis_label_size=16, - fit_report_rpos=(0.6, 0.95), - fit_report_text_size=14, - plot_sigma=[(1.0, 0.7), (3.0, 0.3)], - plot_options={}, - figure_title=None, - ) - - def set_options(self, **fields): - """Set the drawing options. - Args: - fields: The fields to update the options - """ - self._options.update_options(**fields) - self._set_options = self._set_options.union(fields) - - @abstractmethod - def initialize_canvas(self): - """Initialize the drawing canvas.""" - - @abstractmethod - def format_canvas(self): - """Final cleanup for the canvas appearance.""" - - @abstractmethod - def draw_raw_data( - self, - x_data: Sequence[float], - y_data: Sequence[float], - name: Optional[str] = None, - **options, - ): - """Draw raw data. - - Args: - x_data: X values. - y_data: Y values. - name: Name of this curve. - options: Valid options for the drawer backend API. - """ - - @abstractmethod - def draw_formatted_data( - self, - x_data: Sequence[float], - y_data: Sequence[float], - y_err_data: Sequence[float], - name: Optional[str] = None, - **options, - ): - """Draw the formatted data that is used for fitting. - - Args: - x_data: X values. - y_data: Y values. - y_err_data: Standard deviation of Y values. - name: Name of this curve. - options: Valid options for the drawer backend API. - """ - - @abstractmethod - def draw_fit_line( - self, - x_data: Sequence[float], - y_data: Sequence[float], - name: Optional[str] = None, - **options, - ): - """Draw fit line. - - Args: - x_data: X values. - y_data: Fit Y values. - name: Name of this curve. - options: Valid options for the drawer backend API. - """ - - @abstractmethod - def draw_confidence_interval( - self, - x_data: Sequence[float], - y_ub: Sequence[float], - y_lb: Sequence[float], - name: Optional[str] = None, - **options, - ): - """Draw confidence interval. - - Args: - x_data: X values. - y_ub: The upper boundary of Y values. - y_lb: The lower boundary of Y values. - name: Name of this curve. - options: Valid options for the drawer backend API. - """ - - @abstractmethod - def draw_fit_report( - self, - description: str, - **options, - ): - """Draw text box that shows fit reports. - - Args: - description: A string to describe the fiting outcome. - options: Valid options for the drawer backend API. - """ - - @property - @abstractmethod - def figure(self): - """Return figure object handler to be saved in the database.""" - - def config(self) -> Dict: - """Return the config dictionary for this drawing.""" - options = dict((key, getattr(self._options, key)) for key in self._set_options) - - return {"cls": type(self), "options": options} - - def __json_encode__(self): - return self.config() - - @classmethod - def __json_decode__(cls, value): - instance = cls() - if "options" in value: - instance.set_options(**value["options"]) - return instance diff --git a/qiskit_experiments/curve_analysis/visualization/curves.py b/qiskit_experiments/curve_analysis/visualization/curves.py deleted file mode 100644 index 2b2d61dce0..0000000000 --- a/qiskit_experiments/curve_analysis/visualization/curves.py +++ /dev/null @@ -1,210 +0,0 @@ -# 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. -""" -Plotting functions for experiment analysis -""" -from typing import Callable, List, Optional, Tuple - -import numpy as np -from uncertainties import unumpy as unp - -from qiskit.utils.deprecation import deprecate_func - -from qiskit_experiments.curve_analysis.curve_data import FitData -from qiskit_experiments.framework.matplotlib import get_non_gui_ax - - -@deprecate_func( - since="0.5", - additional_msg="Plotting and drawing functionality has been moved to the new " - "`qiskit_experiments.visualization` module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", -) -def plot_curve_fit( - func: Callable, - result: FitData, - ax=None, - num_fit_points: int = 100, - labelsize: int = 14, - grid: bool = True, - fit_uncertainty: List[Tuple[float, float]] = None, - **kwargs, -): - """Generate plot of a curve fit analysis result. - - Wraps :func:`matplotlib.pyplot.plot`. - - Args: - func: the fit function for curve_fit. - result: a fitting data set. - ax (matplotlib.axes.Axes): Optional, a matplotlib axes to add the plot to. - num_fit_points: the number of points to plot for xrange. - labelsize: label size for plot - grid: Show grid on plot. - fit_uncertainty: a list of sigma values to plot confidence interval of fit line. - **kwargs: Additional options for matplotlib.pyplot.plot - - Returns: - matplotlib.axes.Axes: the matplotlib axes containing the plot. - - Raises: - ImportError: If matplotlib is not installed. - """ - if ax is None: - ax = get_non_gui_ax() - - if fit_uncertainty is None: - fit_uncertainty = [] - elif isinstance(fit_uncertainty, tuple): - fit_uncertainty = [fit_uncertainty] - - # Default plot options - plot_opts = kwargs.copy() - if "color" not in plot_opts: - plot_opts["color"] = "blue" - if "linestyle" not in plot_opts: - plot_opts["linestyle"] = "-" - if "linewidth" not in plot_opts: - plot_opts["linewidth"] = 2 - - xmin, xmax = result.x_range - - # Plot fit data - xs = np.linspace(xmin, xmax, num_fit_points) - ys_fit_with_error = func(xs, **dict(zip(result.popt_keys, result.popt))) - - # Line - ax.plot(xs, unp.nominal_values(ys_fit_with_error), **plot_opts) - - # Confidence interval of N sigma values - stdev_arr = unp.std_devs(ys_fit_with_error) - if np.isfinite(stdev_arr).all(): - for sigma, alpha in fit_uncertainty: - ax.fill_between( - xs, - y1=unp.nominal_values(ys_fit_with_error) - sigma * stdev_arr, - y2=unp.nominal_values(ys_fit_with_error) + sigma * stdev_arr, - alpha=alpha, - color=plot_opts["color"], - ) - - # Formatting - ax.tick_params(labelsize=labelsize) - ax.grid(grid) - return ax - - -@deprecate_func( - since="0.5", - additional_msg="Plotting and drawing functionality has been moved to the new " - "`qiskit_experiments.visualization` module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", -) -def plot_scatter( - xdata: np.ndarray, - ydata: np.ndarray, - ax=None, - labelsize: int = 14, - grid: bool = True, - **kwargs, -): - """Generate a scatter plot of xy data. - - Wraps :func:`matplotlib.pyplot.scatter`. - - Args: - xdata: xdata used for fitting - ydata: ydata used for fitting - ax (matplotlib.axes.Axes): Optional, a matplotlib axes to add the plot to. - labelsize: label size for plot - grid: Show grid on plot. - **kwargs: Additional options for :func:`matplotlib.pyplot.scatter` - - Returns: - matplotlib.axes.Axes: the matplotlib axes containing the plot. - """ - if ax is None: - ax = get_non_gui_ax() - - # Default plot options - plot_opts = kwargs.copy() - if "c" not in plot_opts: - plot_opts["c"] = "grey" - if "marker" not in plot_opts: - plot_opts["marker"] = "x" - if "alpha" not in plot_opts: - plot_opts["alpha"] = 0.8 - - # Plot data - ax.scatter(xdata, unp.nominal_values(ydata), **plot_opts) - - # Formatting - ax.tick_params(labelsize=labelsize) - ax.grid(grid) - return ax - - -@deprecate_func( - since="0.5", - additional_msg="Plotting and drawing functionality has been moved to the new " - "`qiskit_experiments.visualization` module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", -) -def plot_errorbar( - xdata: np.ndarray, - ydata: np.ndarray, - sigma: Optional[np.ndarray] = None, - ax=None, - labelsize: int = 14, - grid: bool = True, - **kwargs, -): - """Generate an errorbar plot of xy data. - - Wraps :func:`matplotlib.pyplot.errorbar` - - Args: - xdata: xdata used for fitting - ydata: ydata used for fitting - sigma: Optional, standard deviation of ydata - ax (matplotlib.axes.Axes): Optional, a matplotlib axes to add the plot to. - labelsize: label size for plot - grid: Show grid on plot. - **kwargs: Additional options for :func:`matplotlib.pyplot.errorbar` - - Returns: - matplotlib.axes.Axes: the matplotlib axes containing the plot. - """ - if ax is None: - ax = get_non_gui_ax() - - # Default plot options - plot_opts = kwargs.copy() - if "color" not in plot_opts: - plot_opts["color"] = "red" - if "marker" not in plot_opts: - plot_opts["marker"] = "." - if "markersize" not in plot_opts: - plot_opts["markersize"] = 9 - if "linestyle" not in plot_opts: - plot_opts["linestyle"] = "None" - - # Plot data - ax.errorbar(xdata, ydata, yerr=sigma, **plot_opts) - - # Formatting - ax.tick_params(labelsize=labelsize) - ax.grid(grid) - return ax diff --git a/qiskit_experiments/curve_analysis/visualization/fit_result_plotters.py b/qiskit_experiments/curve_analysis/visualization/fit_result_plotters.py deleted file mode 100644 index 5c9e9cbfbd..0000000000 --- a/qiskit_experiments/curve_analysis/visualization/fit_result_plotters.py +++ /dev/null @@ -1,470 +0,0 @@ -# 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. -""" -A collection of functions that draw formatted curve analysis results. - -For example, this visualization contains not only fit curves and raw data points, -but also some extra fitting information, such as fit values of some interesting parameters -and goodness of the fitting represented by chi-squared. These extra information can be -also visualized as a fit report. - -Note that plotter is a class that only has a class method to draw the image. -This is just like a function, but allows serialization via Enum. -""" - -from collections import defaultdict -from enum import Enum -from typing import Dict, List, Optional - -import numpy as np -import uncertainties -from matplotlib.ticker import FuncFormatter -from qiskit.utils import detach_prefix -from qiskit.utils.deprecation import deprecate_func - -from qiskit_experiments.curve_analysis.curve_data import CurveData, FitData, SeriesDef -from qiskit_experiments.framework import AnalysisResultData -from qiskit_experiments.framework.matplotlib import get_non_gui_ax - -from .curves import plot_curve_fit, plot_errorbar, plot_scatter -from .style import PlotterStyle - - -class MplDrawSingleCanvas: - """A plotter to draw a single canvas figure for fit result.""" - - @deprecate_func( - since="0.5", - additional_msg="Plotting and drawing of analysis figures has been moved to the new " - "`qiskit_experiments.visualization` module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def __init__(self): - super().__init__() - - @classmethod - def draw( - cls, - series_defs: List[SeriesDef], - raw_samples: List[CurveData], - fit_samples: List[CurveData], - tick_labels: Dict[str, str], - fit_data: FitData, - result_entries: List[AnalysisResultData], - style: Optional[PlotterStyle] = None, - axis: Optional["matplotlib.axes.Axes"] = None, - ) -> "pyplot.Figure": - """Create a fit result of all curves in the single canvas. - - Args: - series_defs: List of definition for each curve. - raw_samples: List of raw sample data for each curve. - fit_samples: List of formatted sample data for each curve. - tick_labels: Dictionary of axis label information. Axis units and label for x and y - value should be explained. - fit_data: fit data generated by the analysis. - result_entries: List of analysis result data entries. - style: Optional. A configuration object to modify the appearance of the figure. - axis: Optional. A matplotlib Axis object. - - Returns: - A matplotlib figure of the curve fit result. - """ - if axis is None: - axis = get_non_gui_ax() - - # update image size to experiment default - figure = axis.get_figure() - figure.set_size_inches(*style.figsize) - else: - figure = axis.get_figure() - - # draw all curves on the same canvas - for series_def, raw_samp, fit_samp in zip(series_defs, raw_samples, fit_samples): - draw_single_curve_mpl( - axis=axis, - series_def=series_def, - raw_sample=raw_samp, - fit_sample=fit_samp, - fit_data=fit_data, - style=style, - ) - - # add legend - if len(series_defs) > 1: - axis.legend(loc=style.legend_loc) - - # get axis scaling factor - for this_axis in ("x", "y"): - sub_axis = getattr(axis, this_axis + "axis") - unit = tick_labels[this_axis + "val_unit"] - label = tick_labels[this_axis + "label"] - if unit: - maxv = np.max(np.abs(sub_axis.get_data_interval())) - scaled_maxv, prefix = detach_prefix(maxv, decimal=3) - prefactor = scaled_maxv / maxv - # pylint: disable=cell-var-from-loop - sub_axis.set_major_formatter(FuncFormatter(lambda x, p: f"{x * prefactor: .3g}")) - sub_axis.set_label_text(f"{label} [{prefix}{unit}]", fontsize=style.axis_label_size) - else: - sub_axis.set_label_text(label, fontsize=style.axis_label_size) - axis.ticklabel_format(axis=this_axis, style="sci", scilimits=(-3, 3)) - - if tick_labels["xlim"]: - axis.set_xlim(tick_labels["xlim"]) - - if tick_labels["ylim"]: - axis.set_ylim(tick_labels["ylim"]) - - # write analysis report - if fit_data: - report_str = write_fit_report(result_entries) - report_str += r"Fit $\chi^2$ = " + f"{fit_data.reduced_chisq: .4g}" - - report_handler = axis.text( - *style.fit_report_rpos, - report_str, - ha="center", - va="top", - size=style.fit_report_text_size, - transform=axis.transAxes, - ) - - bbox_props = { - "boxstyle": "square, pad=0.3", - "fc": "white", - "ec": "black", - "lw": 1, - "alpha": 0.8, - } - report_handler.set_bbox(bbox_props) - - axis.tick_params(labelsize=style.tick_label_size) - axis.grid(True) - - return figure - - -class MplDrawMultiCanvasVstack: - """A plotter to draw a vertically stacked multi canvas figure for fit result.""" - - @deprecate_func( - since="0.5", - additional_msg="Plotting and drawing of analysis figures has been moved to the new " - "`qiskit_experiments.visualization` module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def __init__(self): - pass - - @classmethod - def draw( - cls, - series_defs: List[SeriesDef], - raw_samples: List[CurveData], - fit_samples: List[CurveData], - tick_labels: Dict[str, str], - fit_data: FitData, - result_entries: List[AnalysisResultData], - style: Optional[PlotterStyle] = None, - axis: Optional["matplotlib.axes.Axes"] = None, - ) -> "pyplot.Figure": - """Create a fit result of all curves in the single canvas. - - Args: - series_defs: List of definition for each curve. - raw_samples: List of raw sample data for each curve. - fit_samples: List of formatted sample data for each curve. - tick_labels: Dictionary of axis label information. Axis units and label for x and y - value should be explained. - fit_data: fit data generated by the analysis. - result_entries: List of analysis result data entries. - style: Optional. A configuration object to modify the appearance of the figure. - axis: Optional. A matplotlib Axis object. - - Returns: - A matplotlib figure of the curve fit result. - """ - if axis is None: - axis = get_non_gui_ax() - - # update image size to experiment default - figure = axis.get_figure() - figure.set_size_inches(*style.figsize) - else: - figure = axis.get_figure() - - # get canvas number - n_subplots = max(series_def.canvas for series_def in series_defs) + 1 - - # use inset axis. this allows us to draw multiple canvases on a given single axis object - inset_ax_h = (1 - (0.05 * (n_subplots - 1))) / n_subplots - inset_axes = [ - axis.inset_axes( - [0, 1 - (inset_ax_h + 0.05) * n_axis - inset_ax_h, 1, inset_ax_h], - transform=axis.transAxes, - zorder=1, - ) - for n_axis in range(n_subplots) - ] - - # show x label only in the bottom canvas - for inset_axis in inset_axes[:-1]: - inset_axis.set_xticklabels([]) - inset_axes[-1].get_shared_x_axes().join(*inset_axes) - - # remove original axis frames - axis.spines.right.set_visible(False) - axis.spines.left.set_visible(False) - axis.spines.top.set_visible(False) - axis.spines.bottom.set_visible(False) - axis.set_xticks([]) - axis.set_yticks([]) - - # collect data source per canvas - plot_map = defaultdict(list) - for curve_ind, series_def in enumerate(series_defs): - plot_map[series_def.canvas].append(curve_ind) - - y_labels = tick_labels["ylabel"].split(",") - if len(y_labels) == 1: - y_labels = y_labels * n_subplots - - for ax_ind, curve_inds in plot_map.items(): - inset_axis = inset_axes[ax_ind] - - for curve_ind in curve_inds: - draw_single_curve_mpl( - axis=inset_axis, - series_def=series_defs[curve_ind], - raw_sample=raw_samples[curve_ind], - fit_sample=fit_samples[curve_ind], - fit_data=fit_data, - style=style, - ) - - # add legend to each inset axis - if len(curve_inds) > 1: - inset_axis.legend(loc=style.legend_loc) - - # format y axis tick value of each inset axis - yaxis = getattr(inset_axis, "yaxis") - unit = tick_labels["yval_unit"] - label = y_labels[ax_ind] - if unit: - maxv = np.max(np.abs(yaxis.get_data_interval())) - scaled_maxv, prefix = detach_prefix(maxv, decimal=3) - prefactor = scaled_maxv / maxv - # pylint: disable=cell-var-from-loop - yaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x * prefactor: .3g}")) - yaxis.set_label_text(f"{label} [{prefix}{unit}]", fontsize=style.axis_label_size) - else: - inset_axis.ticklabel_format(axis="y", style="sci", scilimits=(-3, 3)) - yaxis.set_label_text(label, fontsize=style.axis_label_size) - - if tick_labels["ylim"]: - inset_axis.set_ylim(tick_labels["ylim"]) - - # format x axis - xaxis = getattr(inset_axes[-1], "xaxis") - unit = tick_labels["xval_unit"] - label = tick_labels["xlabel"] - if unit: - maxv = np.max(np.abs(xaxis.get_data_interval())) - scaled_maxv, prefix = detach_prefix(maxv, decimal=3) - prefactor = scaled_maxv / maxv - # pylint: disable=cell-var-from-loop - xaxis.set_major_formatter(FuncFormatter(lambda x, p: f"{x * prefactor: .3g}")) - xaxis.set_label_text(f"{label} [{prefix}{unit}]", fontsize=style.axis_label_size) - else: - axis.ticklabel_format(axis="x", style="sci", scilimits=(-3, 3)) - xaxis.set_label_text(label, fontsize=style.axis_label_size) - - if tick_labels["xlim"]: - inset_axes[-1].set_xlim(tick_labels["xlim"]) - - # write analysis report - if fit_data: - report_str = write_fit_report(result_entries) - report_str += r"Fit $\chi^2$ = " + f"{fit_data.reduced_chisq: .4g}" - - report_handler = axis.text( - *style.fit_report_rpos, - report_str, - ha="center", - va="top", - size=style.fit_report_text_size, - transform=axis.transAxes, - ) - - bbox_props = { - "boxstyle": "square, pad=0.3", - "fc": "white", - "ec": "black", - "lw": 1, - "alpha": 0.8, - } - report_handler.set_bbox(bbox_props) - - axis.tick_params(labelsize=style.tick_label_size) - axis.grid(True) - - return figure - - -@deprecate_func( - since="0.5", - additional_msg="Plotting and drawing of analysis figures has been moved to the new " - "`qiskit_experiments.visualization` module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", -) -def draw_single_curve_mpl( - axis: "matplotlib.axes.Axes", - series_def: SeriesDef, - raw_sample: CurveData, - fit_sample: CurveData, - fit_data: FitData, - style: PlotterStyle, -): - """A function that draws a single curve on the given plotter canvas. - - Args: - axis: Drawer canvas. - series_def: Definition of the curve to draw. - raw_sample: Raw sample data. - fit_sample: Formatted sample data. - fit_data: Fitting parameter collection. - style: Style sheet for plotting. - """ - - # plot raw data if data is formatted - if not np.array_equal(raw_sample.y, fit_sample.y): - plot_scatter(xdata=raw_sample.x, ydata=raw_sample.y, ax=axis, zorder=0) - - # plot formatted data - if np.all(np.isnan(fit_sample.y_err)): - sigma = None - else: - sigma = np.nan_to_num(fit_sample.y_err) - - plot_errorbar( - xdata=fit_sample.x, - ydata=fit_sample.y, - sigma=sigma, - ax=axis, - label=series_def.name, - marker=series_def.plot_symbol, - color=series_def.plot_color, - zorder=1, - linestyle="", - ) - - # plot fit curve - if fit_data: - plot_curve_fit( - func=series_def.fit_func, - result=fit_data, - ax=axis, - color=series_def.plot_color, - zorder=2, - fit_uncertainty=style.plot_sigma, - ) - - -@deprecate_func( - since="0.5", - additional_msg="Plotting and drawing of analysis figures has been moved to the new " - "`qiskit_experiments.visualization` module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", -) -def write_fit_report(result_entries: List[AnalysisResultData]) -> str: - """A function that generates fit reports documentation from list of data. - - Args: - result_entries: List of data entries. - - Returns: - Documentation of fit reports. - """ - analysis_description = "" - - def format_val(float_val: float) -> str: - if np.abs(float_val) < 1e-3 or np.abs(float_val) > 1e3: - return f"{float_val: .4e}" - return f"{float_val: .4g}" - - for res in result_entries: - if isinstance(res.value, uncertainties.UFloat): - fitval = res.value - unit = res.extra.get("unit", None) - if unit: - # unit is defined. do detaching prefix, i.e. 1000 Hz -> 1 kHz - try: - val, val_prefix = detach_prefix(fitval.nominal_value, decimal=3) - except ValueError: - # Value is too small or too big - val = fitval.nominal_value - val_prefix = "" - val_unit = val_prefix + unit - value_repr = f"{val: .3g}" - - # write error bar if it is finite value - if fitval.std_dev is not None and np.isfinite(fitval.std_dev): - # with stderr - try: - err, err_prefix = detach_prefix(fitval.std_dev, decimal=3) - except ValueError: - # Value is too small or too big - err = fitval.std_dev - err_prefix = "" - err_unit = err_prefix + unit - if val_unit == err_unit: - # same value scaling, same prefix - value_repr += f" \u00B1 {err: .2f} {val_unit}" - else: - # different value scaling, different prefix - value_repr += f" {val_unit} \u00B1 {err: .2f} {err_unit}" - else: - # without stderr, just append unit - value_repr += f" {val_unit}" - else: - # unit is not defined. raw value formatting is performed. - value_repr = format_val(fitval.nominal_value) - if np.isfinite(fitval.std_dev): - # with stderr - value_repr += f" \u00B1 {format_val(fitval.std_dev)}" - - analysis_description += f"{res.name} = {value_repr}\n" - - return analysis_description - - -# pylint: disable=invalid-name -class FitResultPlotters(Enum): - """Map the plotter name to the plotters.""" - - mpl_single_canvas = MplDrawSingleCanvas - mpl_multiv_canvas = MplDrawMultiCanvasVstack - - @deprecate_func( - since="0.5", - additional_msg="Plotting and drawing of analysis figures has been moved to the new " - "`qiskit_experiments.visualization` module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def __post_init__(self): - pass diff --git a/qiskit_experiments/curve_analysis/visualization/mpl_drawer.py b/qiskit_experiments/curve_analysis/visualization/mpl_drawer.py deleted file mode 100644 index 3463dac1c1..0000000000 --- a/qiskit_experiments/curve_analysis/visualization/mpl_drawer.py +++ /dev/null @@ -1,409 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Curve drawer for matplotlib backend.""" - -from typing import Optional, Sequence, Tuple - -import numpy as np -from matplotlib.axes import Axes -from matplotlib.cm import tab10 -from matplotlib.figure import Figure -from matplotlib.markers import MarkerStyle -from matplotlib.ticker import Formatter, ScalarFormatter -from qiskit.utils import detach_prefix -from qiskit.utils.deprecation import deprecate_func - -from qiskit_experiments.framework.matplotlib import get_non_gui_ax - -from .base_drawer import BaseCurveDrawer - - -class MplCurveDrawer(BaseCurveDrawer): - """Curve drawer for MatplotLib backend.""" - - DefaultMarkers = MarkerStyle.filled_markers - DefaultColors = tab10.colors - - @deprecate_func( - since="0.5", - additional_msg="Plotting and drawing of analysis figures has been moved to the new " - "`qiskit_experiments.visualization` module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def __init__(self): - super().__init__() - - class PrefixFormatter(Formatter): - """Matplotlib axis formatter to detach prefix. - - If a value is, e.g., x=1000.0 and the factor is 1000, then it will be shown - as 1.0 in the ticks and its unit will be shown with the prefactor 'k' - in the axis label. - """ - - def __init__(self, factor: float): - self.factor = factor - - def __call__(self, x, pos=None): - return self.fix_minus(f"{x * self.factor:.3g}") - - def initialize_canvas(self): - # Create axis if empty - if not self.options.axis: - axis = get_non_gui_ax() - figure = axis.get_figure() - figure.set_size_inches(*self.options.figsize) - else: - axis = self.options.axis - - n_rows, n_cols = self.options.subplots - n_subplots = n_cols * n_rows - if n_subplots > 1: - # Add inset axis. User may provide a single axis object via the analysis option, - # while this analysis tries to draw its result in multiple canvases, - # especially when the analysis consists of multiple curves. - # Inset axis is experimental implementation of matplotlib 3.0 so maybe unstable API. - # This draws inset axes with shared x and y axis. - inset_ax_h = 1 / n_rows - inset_ax_w = 1 / n_cols - for i in range(n_rows): - for j in range(n_cols): - # x0, y0, width, height - bounds = [ - inset_ax_w * j, - 1 - inset_ax_h * (i + 1), - inset_ax_w, - inset_ax_h, - ] - sub_ax = axis.inset_axes(bounds, transform=axis.transAxes, zorder=1) - if j != 0: - # remove y axis except for most-left plot - sub_ax.set_yticklabels([]) - else: - # this axis locates at left, write y-label - if self.options.ylabel: - label = self.options.ylabel - if isinstance(label, list): - # Y label can be given as a list for each sub axis - label = label[i] - sub_ax.set_ylabel(label, fontsize=self.options.axis_label_size) - if i != n_rows - 1: - # remove x axis except for most-bottom plot - sub_ax.set_xticklabels([]) - else: - # this axis locates at bottom, write x-label - if self.options.xlabel: - label = self.options.xlabel - if isinstance(label, list): - # X label can be given as a list for each sub axis - label = label[j] - sub_ax.set_xlabel(label, fontsize=self.options.axis_label_size) - if j == 0 or i == n_rows - 1: - # Set label size for outer axes where labels are drawn - sub_ax.tick_params(labelsize=self.options.tick_label_size) - sub_ax.grid() - - # Remove original axis frames - axis.axis("off") - else: - axis.set_xlabel(self.options.xlabel, fontsize=self.options.axis_label_size) - axis.set_ylabel(self.options.ylabel, fontsize=self.options.axis_label_size) - axis.tick_params(labelsize=self.options.tick_label_size) - axis.grid() - - self._axis = axis - - def format_canvas(self): - if self._axis.child_axes: - # Multi canvas mode - all_axes = self._axis.child_axes - else: - all_axes = [self._axis] - - # Add data labels if there are multiple labels registered per sub_ax. - for sub_ax in all_axes: - _, labels = sub_ax.get_legend_handles_labels() - if len(labels) > 1: - sub_ax.legend() - - # Format x and y axis - for ax_type in ("x", "y"): - # Get axis formatter from drawing options - if ax_type == "x": - lim = self.options.xlim - unit = self.options.xval_unit - else: - lim = self.options.ylim - unit = self.options.yval_unit - - # Compute data range from auto scale - if not lim: - v0 = np.nan - v1 = np.nan - for sub_ax in all_axes: - if ax_type == "x": - this_v0, this_v1 = sub_ax.get_xlim() - else: - this_v0, this_v1 = sub_ax.get_ylim() - v0 = np.nanmin([v0, this_v0]) - v1 = np.nanmax([v1, this_v1]) - lim = (v0, v1) - - # Format axis number notation - if unit: - # If value is specified, automatically scale axis magnitude - # and write prefix to axis label, i.e. 1e3 Hz -> 1 kHz - maxv = max(np.abs(lim[0]), np.abs(lim[1])) - try: - scaled_maxv, prefix = detach_prefix(maxv, decimal=3) - prefactor = scaled_maxv / maxv - except ValueError: - prefix = "" - prefactor = 1 - - formatter = MplCurveDrawer.PrefixFormatter(prefactor) - units_str = f" [{prefix}{unit}]" - else: - # Use scientific notation with 3 digits, 1000 -> 1e3 - formatter = ScalarFormatter() - formatter.set_scientific(True) - formatter.set_powerlimits((-3, 3)) - - units_str = "" - - for sub_ax in all_axes: - if ax_type == "x": - ax = getattr(sub_ax, "xaxis") - tick_labels = sub_ax.get_xticklabels() - else: - ax = getattr(sub_ax, "yaxis") - tick_labels = sub_ax.get_yticklabels() - - if tick_labels: - # Set formatter only when tick labels exist - ax.set_major_formatter(formatter) - if units_str: - # Add units to label if both exist - label_txt_obj = ax.get_label() - label_str = label_txt_obj.get_text() - if label_str: - label_txt_obj.set_text(label_str + units_str) - - # Auto-scale all axes to the first sub axis - if ax_type == "x": - # get_shared_y_axes() is immutable from matplotlib>=3.6.0. Must use Axis.sharey() - # instead, but this can only be called once per axis. Here we call sharey on all axes in - # a chain, which should have the same effect. - if len(all_axes) > 1: - for ax1, ax2 in zip(all_axes[1:], all_axes[0:-1]): - ax1.sharex(ax2) - all_axes[0].set_xlim(lim) - else: - # get_shared_y_axes() is immutable from matplotlib>=3.6.0. Must use Axis.sharey() - # instead, but this can only be called once per axis. Here we call sharey on all axes in - # a chain, which should have the same effect. - if len(all_axes) > 1: - for ax1, ax2 in zip(all_axes[1:], all_axes[0:-1]): - ax1.sharey(ax2) - all_axes[0].set_ylim(lim) - # Add title - if self.options.figure_title is not None: - self._axis.set_title( - label=self.options.figure_title, - fontsize=self.options.axis_label_size, - ) - - def _get_axis(self, index: Optional[int] = None) -> Axes: - """A helper method to get inset axis. - - Args: - index: Index of inset axis. If nothing is provided, it returns the entire axis. - - Returns: - Corresponding axis object. - - Raises: - IndexError: When axis index is specified but no inset axis is found. - """ - if index is not None: - try: - return self._axis.child_axes[index] - except IndexError as ex: - raise IndexError( - f"Canvas index {index} is out of range. " - f"Only {len(self._axis.child_axes)} subplots are initialized." - ) from ex - else: - return self._axis - - def _get_default_color(self, name: str) -> Tuple[float, ...]: - """A helper method to get default color for the curve. - - Args: - name: Name of the curve. - - Returns: - Default color available in matplotlib. - """ - if name not in self._curves: - self._curves.append(name) - - ind = self._curves.index(name) % len(self.DefaultColors) - return self.DefaultColors[ind] - - def _get_default_marker(self, name: str) -> str: - """A helper method to get default marker for the scatter plot. - - Args: - name: Name of the curve. - - Returns: - Default marker available in matplotlib. - """ - if name not in self._curves: - self._curves.append(name) - - ind = self._curves.index(name) % len(self.DefaultMarkers) - return self.DefaultMarkers[ind] - - def draw_raw_data( - self, - x_data: Sequence[float], - y_data: Sequence[float], - name: Optional[str] = None, - **options, - ): - curve_opts = self.options.plot_options.get(name, {}) - marker = curve_opts.get("symbol", self._get_default_marker(name)) - axis = curve_opts.get("canvas", None) - - draw_options = { - "color": "grey", - "marker": marker, - "alpha": 0.8, - "zorder": 2, - } - draw_options.update(**options) - self._get_axis(axis).scatter(x_data, y_data, **draw_options) - - def draw_formatted_data( - self, - x_data: Sequence[float], - y_data: Sequence[float], - y_err_data: Sequence[float], - name: Optional[str] = None, - **options, - ): - curve_opts = self.options.plot_options.get(name, {}) - axis = curve_opts.get("canvas", None) - color = curve_opts.get("color", self._get_default_color(name)) - marker = curve_opts.get("symbol", self._get_default_marker(name)) - - draw_ops = { - "color": color, - "marker": marker, - "markersize": 9, - "alpha": 0.8, - "zorder": 4, - "linestyle": "", - } - draw_ops.update(**options) - if name: - draw_ops["label"] = name - - if not np.all(np.isfinite(y_err_data)): - y_err_data = None - self._get_axis(axis).errorbar(x_data, y_data, yerr=y_err_data, **draw_ops) - - def draw_fit_line( - self, - x_data: Sequence[float], - y_data: Sequence[float], - name: Optional[str] = None, - **options, - ): - curve_opts = self.options.plot_options.get(name, {}) - axis = curve_opts.get("canvas", None) - color = curve_opts.get("color", self._get_default_color(name)) - - draw_ops = { - "color": color, - "zorder": 5, - "linestyle": "-", - "linewidth": 2, - } - draw_ops.update(**options) - self._get_axis(axis).plot(x_data, y_data, **draw_ops) - - def draw_confidence_interval( - self, - x_data: Sequence[float], - y_ub: Sequence[float], - y_lb: Sequence[float], - name: Optional[str] = None, - **options, - ): - curve_opts = self.options.plot_options.get(name, {}) - axis = curve_opts.get("canvas", None) - color = curve_opts.get("color", self._get_default_color(name)) - - draw_ops = { - "zorder": 3, - "alpha": 0.1, - "color": color, - } - draw_ops.update(**options) - self._get_axis(axis).fill_between(x_data, y1=y_lb, y2=y_ub, **draw_ops) - - def draw_fit_report( - self, - description: str, - **options, - ): - bbox_props = { - "boxstyle": "square, pad=0.3", - "fc": "white", - "ec": "black", - "lw": 1, - "alpha": 0.8, - } - bbox_props.update(**options) - - report_handler = self._axis.text( - *self.options.fit_report_rpos, - s=description, - ha="center", - va="top", - size=self.options.fit_report_text_size, - transform=self._axis.transAxes, - zorder=6, - ) - report_handler.set_bbox(bbox_props) - - @property - def figure(self) -> Figure: - """Return figure object handler to be saved in the database. - - In the MatplotLib the ``Figure`` and ``Axes`` are different object. - User can pass a part of the figure (i.e. multi-axes) to the drawer option ``axis``. - For example, a user wants to combine two different experiment results in the - same figure, one can call ``pyplot.subplots`` with two rows and pass one of the - generated two axes to each experiment drawer. Once all the experiments complete, - the user will obtain the single figure collecting all experimental results. - - Note that this method returns the entire figure object, rather than a single axis. - Thus, the experiment data saved in the database might have a figure - collecting all child axes drawings. - """ - return self._axis.get_figure() diff --git a/qiskit_experiments/curve_analysis/visualization/style.py b/qiskit_experiments/curve_analysis/visualization/style.py deleted file mode 100644 index 059ed249b0..0000000000 --- a/qiskit_experiments/curve_analysis/visualization/style.py +++ /dev/null @@ -1,57 +0,0 @@ -# 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. -""" -Configurable stylesheet. -""" -import dataclasses -from typing import List, Tuple - -from qiskit.utils.deprecation import deprecate_func - - -@dataclasses.dataclass -class PlotterStyle: - """A stylesheet for curve analysis figure.""" - - # size of figure (width, height) - figsize: Tuple[int, int] = (8, 5) - - # legend location (vertical, horizontal) - legend_loc: str = "center right" - - # size of tick label - tick_label_size: int = 14 - - # size of axis label - axis_label_size: int = 16 - - # relative position of fit report - fit_report_rpos: Tuple[float, float] = (0.6, 0.95) - - # size of fit report text - fit_report_text_size: int = 14 - - # sigma values for confidence interval, which are the tuple of (sigma, alpha). - # the alpha indicates the transparency of the corresponding interval plot. - plot_sigma: List[Tuple[float, float]] = dataclasses.field( - default_factory=lambda: [(1.0, 0.7), (3.0, 0.3)] - ) - - @deprecate_func( - since="0.5", - additional_msg="Plotting and drawing of analysis figures has been replaced with the new" - "`qiskit_experiments.visualization` module.", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) diff --git a/qiskit_experiments/framework/backend_data.py b/qiskit_experiments/framework/backend_data.py index 2dd1e94ed4..a9241d998d 100644 --- a/qiskit_experiments/framework/backend_data.py +++ b/qiskit_experiments/framework/backend_data.py @@ -17,56 +17,6 @@ class unifies data access for various data fields. """ from qiskit.providers.models import PulseBackendConfiguration from qiskit.providers import BackendV1, BackendV2 -from qiskit.providers.fake_provider import FakeBackend -from qiskit.utils.deprecation import deprecate_func - -try: - # Removed in Qiskit 1.0. - from qiskit.providers.fake_provider.fake_backend import FakeBackendV2 -except ImportError: - - class FakeBackendV2: - """Dummy class for when FakeBackendV2 import fails - - This class is only used in isinstance checks. If the import fails, then - there won't be an instance of the class either so any dummy class is - fine. - """ - - pass - - -try: - # Removed in Qiskit 1.0. Different from the other FakeBackendV2's - from qiskit.providers.fake_provider import QiskitFakeBackendV2 -except ImportError: - - class QiskitFakeBackendV2: - """Dummy class for when FakeBackendV2 import fails - - This class is only used in isinstance checks. If the import fails, then - there won't be an instance of the class either so any dummy class is - fine. - """ - - pass - - -try: - # A copy of qiskit.providers.fake_provider.fake_backend.FakeBackendV2, at - # least as of qiskit-ibm-runtime 0.18.0 and Qiskit 1.0 - from qiskit_ibm_runtime.fake_provider.fake_backend import FakeBackendV2 as RuntimeFakeBackendV2 -except ImportError: - - class RuntimeFakeBackendV2: - """Dummy class for when FakeBackendV2 import fails - - This class is only used in isinstance checks. If the import fails, then - there won't be an instance of the class either so any dummy class is - fine. - """ - - pass class BackendData: @@ -289,38 +239,6 @@ def num_qubits(self): return self._backend.num_qubits return None - @property - @deprecate_func( - is_property=True, - since="0.6", - additional_msg=( - "is_simulator is deprecated because BackendV2 does not provide a " - "standard way for checking this property. Calling code must " - "determine if a backend uses a simulator from context." - ), - package_name="qiskit-experiments", - ) # Note: remove all FakeBackend imports when removing this code - def is_simulator(self): - """Returns True given an indication the backend is a simulator - - .. note:: - - For `BackendV2` we sometimes cannot be sure, because it lacks - a `simulator` field, as was present in `BackendV1`'s configuration. - We still check whether the backend inherits `FakeBackendV2`, for - either of its existing implementations in Qiskit. - """ - if self._v1: - if self._backend.configuration().simulator or isinstance(self._backend, FakeBackend): - return True - if self._v2: - if isinstance( - self._backend, (FakeBackendV2, QiskitFakeBackendV2, RuntimeFakeBackendV2) - ): - return True - - return False - def qubit_t1(self, qubit: int) -> float: """Return the T1 value for a qubit from the backend properties diff --git a/qiskit_experiments/framework/composite/batch_experiment.py b/qiskit_experiments/framework/composite/batch_experiment.py index 7ef50ac2a0..19ffaaa87d 100644 --- a/qiskit_experiments/framework/composite/batch_experiment.py +++ b/qiskit_experiments/framework/composite/batch_experiment.py @@ -15,7 +15,6 @@ from typing import List, Optional, Dict from collections import OrderedDict, defaultdict -import warnings from qiskit import QuantumCircuit from qiskit.providers import Job, Backend, Options @@ -47,7 +46,7 @@ def __init__( self, experiments: List[BaseExperiment], backend: Optional[Backend] = None, - flatten_results: bool = None, + flatten_results: bool = True, analysis: Optional[CompositeAnalysis] = None, experiment_type: Optional[str] = None, ): @@ -66,17 +65,6 @@ def __init__( provided this will be initialized automatically from the supplied experiments. """ - if flatten_results is None: - # Backward compatibility for 0.6 - # This if-clause will be removed in 0.7 and flatten_result=True is set in arguments. - warnings.warn( - "Default value of flatten_results will be turned to True in Qiskit Experiments 0.7. " - "If you want child experiment data for each subset experiment, " - "set 'flatten_results=False' explicitly.", - DeprecationWarning, - ) - flatten_results = False - # Generate qubit map self._qubit_map = OrderedDict() logical_qubit = 0 diff --git a/qiskit_experiments/framework/composite/composite_analysis.py b/qiskit_experiments/framework/composite/composite_analysis.py index fb04ba50f9..ff3f987a15 100644 --- a/qiskit_experiments/framework/composite/composite_analysis.py +++ b/qiskit_experiments/framework/composite/composite_analysis.py @@ -14,7 +14,6 @@ """ from typing import List, Dict, Union, Optional, Tuple -import warnings import numpy as np from qiskit.result import marginal_distribution from qiskit.result.postprocess import format_counts_memory @@ -55,7 +54,7 @@ class CompositeAnalysis(BaseAnalysis): def __init__( self, analyses: List[BaseAnalysis], - flatten_results: bool = None, + flatten_results: bool = True, generate_figures: Optional[str] = "always", ): """Initialize a composite analysis class. @@ -71,16 +70,6 @@ def __init__( If ``always``, figures are always generated. If ``never``, figures are never generated. If ``selective``, figures are generated if the analysis ``quality`` is ``bad``. """ - if flatten_results is None: - # Backward compatibility for 0.6 - # This if-clause will be removed in 0.7 and flatten_result=True is set in arguments. - warnings.warn( - "Default value of flatten_results will be turned to True in Qiskit Experiments 0.7. " - "If you want child experiment data for each subset experiment, " - "set 'flatten_results=False' explicitly.", - DeprecationWarning, - ) - flatten_results = False super().__init__() self._analyses = analyses self._flatten_results = False diff --git a/qiskit_experiments/framework/composite/composite_experiment.py b/qiskit_experiments/framework/composite/composite_experiment.py index 16c15a79f3..6441ff7d62 100644 --- a/qiskit_experiments/framework/composite/composite_experiment.py +++ b/qiskit_experiments/framework/composite/composite_experiment.py @@ -31,7 +31,7 @@ def __init__( physical_qubits: Sequence[int], backend: Optional[Backend] = None, experiment_type: Optional[str] = None, - flatten_results: bool = None, + flatten_results: bool = True, analysis: Optional[CompositeAnalysis] = None, ): """Initialize the composite experiment object. @@ -55,17 +55,6 @@ def __init__( QiskitError: If the provided analysis class is not a CompositeAnalysis instance. """ - if flatten_results is None: - # Backward compatibility for 0.6 - # This if-clause will be removed in 0.7 and flatten_result=True is set in arguments. - warnings.warn( - "Default value of flatten_results will be turned to True in Qiskit Experiments 0.7. " - "If you want child experiment data for each subset experiment, " - "set 'flatten_results=False' explicitly.", - DeprecationWarning, - ) - flatten_results = False - self._experiments = experiments self._num_experiments = len(experiments) if analysis is None: diff --git a/qiskit_experiments/framework/composite/parallel_experiment.py b/qiskit_experiments/framework/composite/parallel_experiment.py index 24f901a292..92d01c1e50 100644 --- a/qiskit_experiments/framework/composite/parallel_experiment.py +++ b/qiskit_experiments/framework/composite/parallel_experiment.py @@ -13,7 +13,6 @@ Parallel Experiment class. """ from typing import List, Optional -import warnings import numpy as np from qiskit import QuantumCircuit, ClassicalRegister @@ -47,7 +46,7 @@ def __init__( self, experiments: List[BaseExperiment], backend: Optional[Backend] = None, - flatten_results: bool = None, + flatten_results: bool = True, analysis: Optional[CompositeAnalysis] = None, experiment_type: Optional[str] = None, ): @@ -66,16 +65,6 @@ def __init__( provided this will be initialized automatically from the supplied experiments. """ - if flatten_results is None: - # Backward compatibility for 0.6 - # This if-clause will be removed in 0.7 and flatten_result=True is set in arguments. - warnings.warn( - "Default value of flatten_results will be turned to True in Qiskit Experiments 0.7. " - "If you want child experiment data for each subset experiment, " - "set 'flatten_results=False' explicitly.", - DeprecationWarning, - ) - flatten_results = False qubits = [] for exp in experiments: qubits += exp.physical_qubits diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index c1a7efb51f..21bc979ce4 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -2674,11 +2674,18 @@ def delete_artifact( """Delete specified artifact data. Args: - artifact_key: UID, name or index of the figure. + artifact_key: UID or name of the artifact. Deleting by index is deprecated. Returns: Deleted artifact ids. """ + if isinstance(artifact_key, int): + warnings.warn( + "Accessing artifacts via a numerical index is deprecated and will be " + "removed in a future release. Use the ID or name of the artifact " + "instead.", + DeprecationWarning, + ) artifact_keys = self._find_artifact_keys(artifact_key) for key in artifact_keys: @@ -2696,14 +2703,21 @@ def artifacts( """Return specified artifact data. Args: - artifact_key: UID, name or index of the figure. + artifact_key: UID or name of the artifact. Supplying a numerical index + is deprecated. Returns: A list of specified artifact data. """ if artifact_key is None: return self._artifacts.values() - + elif isinstance(artifact_key, int): + warnings.warn( + "Accessing artifacts via a numerical index is deprecated and will be " + "removed in a future release. Use the ID or name of the artifact " + "instead.", + DeprecationWarning, + ) artifact_keys = self._find_artifact_keys(artifact_key) out = [] diff --git a/qiskit_experiments/library/randomized_benchmarking/clifford_utils.py b/qiskit_experiments/library/randomized_benchmarking/clifford_utils.py index 666f8b0cc9..acf0935d4b 100644 --- a/qiskit_experiments/library/randomized_benchmarking/clifford_utils.py +++ b/qiskit_experiments/library/randomized_benchmarking/clifford_utils.py @@ -20,7 +20,6 @@ from typing import Optional, Union, Tuple, Sequence, Iterable import numpy as np -from numpy.random import Generator, default_rng from qiskit.circuit import CircuitInstruction, Qubit from qiskit.circuit import Gate, Instruction @@ -28,10 +27,9 @@ from qiskit.circuit.library import SdgGate, HGate, SGate, XGate, YGate, ZGate from qiskit.compiler import transpile from qiskit.exceptions import QiskitError -from qiskit.quantum_info import Clifford, random_clifford +from qiskit.quantum_info import Clifford from qiskit.transpiler import CouplingMap, PassManager from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig, HighLevelSynthesis -from qiskit.utils.deprecation import deprecate_func DEFAULT_SYNTHESIS_METHOD = "rb_default" @@ -48,6 +46,7 @@ _clifford_num_to_dense_index = {idx: ii for ii, idx in enumerate(_valid_sparse_indices)} _CLIFFORD_TENSOR_1Q = np.load(f"{_DATA_FOLDER}/clifford_tensor_1q.npz")["table"] + # Transpilation utilities def _transpile_clifford_circuit( circuit: QuantumCircuit, physical_qubits: Sequence[int] @@ -313,54 +312,6 @@ def clifford_2_qubit(cls, num): """ return Clifford(cls.clifford_2_qubit_circuit(num), validate=False) - @classmethod - @deprecate_func( - since="0.5", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def random_cliffords( - cls, num_qubits: int, size: int = 1, rng: Optional[Union[int, Generator]] = None - ): - """Generate a list of random clifford elements""" - if rng is None: - rng = default_rng() - elif isinstance(rng, int): - rng = default_rng(rng) - - if num_qubits == 1: - samples = rng.integers(cls.NUM_CLIFFORD_1_QUBIT, size=size) - return [Clifford(cls.clifford_1_qubit_circuit(i), validate=False) for i in samples] - if num_qubits == 2: - samples = rng.integers(cls.NUM_CLIFFORD_2_QUBIT, size=size) - return [Clifford(cls.clifford_2_qubit_circuit(i), validate=False) for i in samples] - - return [random_clifford(num_qubits, seed=rng) for _ in range(size)] - - @classmethod - @deprecate_func( - since="0.5", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def random_clifford_circuits( - cls, num_qubits: int, size: int = 1, rng: Optional[Union[int, Generator]] = None - ): - """Generate a list of random clifford circuits""" - if rng is None: - rng = default_rng() - elif isinstance(rng, int): - rng = default_rng(rng) - - if num_qubits == 1: - samples = rng.integers(cls.NUM_CLIFFORD_1_QUBIT, size=size) - return [cls.clifford_1_qubit_circuit(i) for i in samples] - if num_qubits == 2: - samples = rng.integers(cls.NUM_CLIFFORD_2_QUBIT, size=size) - return [cls.clifford_2_qubit_circuit(i) for i in samples] - - return [random_clifford(num_qubits, seed=rng).to_circuit() for _ in range(size)] - @classmethod @lru_cache(maxsize=24) def clifford_1_qubit_circuit( diff --git a/qiskit_experiments/library/tomography/fitters/lstsq_utils.py b/qiskit_experiments/library/tomography/fitters/lstsq_utils.py index ec0608d551..5583fca90f 100644 --- a/qiskit_experiments/library/tomography/fitters/lstsq_utils.py +++ b/qiskit_experiments/library/tomography/fitters/lstsq_utils.py @@ -16,7 +16,6 @@ from typing import Optional, Tuple, Callable, Sequence, Union import functools import numpy as np -from qiskit.utils.deprecation import deprecate_arg from qiskit_experiments.exceptions import AnalysisError from qiskit_experiments.library.tomography.basis import ( MeasurementBasis, @@ -189,12 +188,6 @@ def zero(_): return basis_mat, probs, prob_weights -@deprecate_arg( - "beta", - new_alias="outcome_prior", - since="0.5", - package_name="qiskit-experiments", -) def binomial_weights( outcome_data: np.ndarray, shot_data: Optional[Union[np.ndarray, int]] = None, diff --git a/qiskit_experiments/visualization/__init__.py b/qiskit_experiments/visualization/__init__.py index b1f91d5d32..6f6162e589 100644 --- a/qiskit_experiments/visualization/__init__.py +++ b/qiskit_experiments/visualization/__init__.py @@ -68,6 +68,6 @@ PlotStyle """ -from .drawers import BaseDrawer, LegacyCurveCompatDrawer, MplDrawer +from .drawers import BaseDrawer, MplDrawer from .plotters import BasePlotter, CurvePlotter, IQPlotter from .style import PlotStyle diff --git a/qiskit_experiments/visualization/drawers/__init__.py b/qiskit_experiments/visualization/drawers/__init__.py index 3c1e6f2343..cb833b58ee 100644 --- a/qiskit_experiments/visualization/drawers/__init__.py +++ b/qiskit_experiments/visualization/drawers/__init__.py @@ -12,5 +12,4 @@ """Drawers submodule, defining interfaces to figure backends.""" from .base_drawer import BaseDrawer, SeriesName -from .legacy_curve_compat_drawer import LegacyCurveCompatDrawer from .mpl_drawer import MplDrawer diff --git a/qiskit_experiments/visualization/drawers/base_drawer.py b/qiskit_experiments/visualization/drawers/base_drawer.py index d9fb1af075..28d21853db 100644 --- a/qiskit_experiments/visualization/drawers/base_drawer.py +++ b/qiskit_experiments/visualization/drawers/base_drawer.py @@ -40,9 +40,7 @@ class BaseDrawer(ABC): input ``axis`` object. Note that ``drawer`` supports visualization of experiment results in multiple canvases tiled into N (row) x M (column) inset grids, which is specified in the option ``subplots``. By default, this is N=1, M=1 and thus - no inset grid will be initialized. The data points to draw might be provided - with a canvas number defined in :attr:`SeriesDef.canvas` which defaults to - ``None``, i.e. no-inset grids. + no inset grid will be initialized. This method should first check the drawer options (:attr:`options`) for the axis object and initialize the axis only when it is not provided by the options. Once diff --git a/qiskit_experiments/visualization/drawers/legacy_curve_compat_drawer.py b/qiskit_experiments/visualization/drawers/legacy_curve_compat_drawer.py deleted file mode 100644 index 2d6698e60f..0000000000 --- a/qiskit_experiments/visualization/drawers/legacy_curve_compat_drawer.py +++ /dev/null @@ -1,246 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Compatibility wrapper for legacy BaseCurveDrawer.""" - -import warnings -from typing import Any, Optional, Sequence, Tuple, Union -import numpy as np - -from qiskit.utils.deprecation import deprecate_func - -from qiskit_experiments.curve_analysis.visualization import BaseCurveDrawer - -from ..utils import ExtentTuple -from .base_drawer import BaseDrawer - - -class LegacyCurveCompatDrawer(BaseDrawer): - """A compatibility wrapper for the legacy and deprecated :class:`BaseCurveDrawer`. - - :mod:`qiskit_experiments.curve_analysis.visualization` is deprecated and will be - replaced with the new :mod:`qiskit_experiments.visualization` module. Analysis - classes instead use subclasses of :class:`BasePlotter` to generate figures. This - class wraps the legacy :class:`BaseCurveDrawer` class so it can be used by analysis - classes, such as :class:`CurveAnalysis`, until it is removed. - - .. note:: - As :class:`BaseCurveDrawer` doesn't support customizing legend entries, the - ``legend`` and ``label`` parameters in drawing methods (such as - :meth:`scatter`) are unsupported and do nothing. - """ - - @deprecate_func( - since="0.5", - additional_msg="Legacy drawers from ``curve_analysis.visualization`` are deprecated. " - "This compatibility wrapper will be removed alongside the deprecated modules removal", - removal_timeline="after 0.6", - package_name="qiskit-experiments", - ) - def __init__(self, curve_drawer: BaseCurveDrawer): - """Create a LegacyCurveCompatDrawer instance. - - Args: - curve_drawer: A legacy BaseCurveDrawer to wrap in the compatibility drawer. - """ - super().__init__() - self._curve_drawer = curve_drawer - - def initialize_canvas(self): - self._curve_drawer.initialize_canvas() - - def format_canvas(self): - self._curve_drawer.format_canvas() - - # pylint: disable=unused-argument - def scatter( - self, - x_data: Sequence[float], - y_data: Sequence[float], - x_err: Optional[Sequence[float]] = None, - y_err: Optional[Sequence[float]] = None, - name: Optional[str] = None, - label: Optional[str] = None, - legend: bool = False, - **options, - ): - """Draws scatter points with optional Y errorbars. - - Args: - x_data: X values. - y_data: Y values. - x_err: Unsupported as :class:`BaseCurveDrawer` doesn't support X errorbars. - Defaults to None. - y_err: Optional error for Y values. - name: Name of this series. - label: Unsupported as :class:`BaseCurveDrawer` doesn't support customizing - legend entries. - legend: Unsupported as :class:`BaseCurveDrawer` doesn't support toggling - legend entries. - options: Valid options for the drawer backend API. - """ - if x_err is not None: - warnings.warn(f"{self.__class__.__name__} doesn't support x_err.") - - if y_err is not None: - self._curve_drawer.draw_formatted_data(x_data, y_data, y_err, name, **options) - else: - self._curve_drawer.draw_raw_data(x_data, y_data, name, **options) - - # pylint: disable=unused-argument - def line( - self, - x_data: Sequence[float], - y_data: Sequence[float], - name: Optional[str] = None, - label: Optional[str] = None, - legend: bool = False, - **options, - ): - """Draw fit line. - - Args: - x_data: X values. - y_data: Fit Y values. - name: Name of this series. - label: Unsupported as :class:`BaseCurveDrawer` doesn't support customizing - legend entries. - legend: Unsupported as :class:`BaseCurveDrawer` doesn't support toggling - legend entries. - options: Valid options for the drawer backend API. - """ - self._curve_drawer.draw_fit_line(x_data, y_data, name, **options) - - # pylint: disable=unused-argument - def hline( - self, - y_value: float, - name: Optional[str] = None, - label: Optional[str] = None, - legend: bool = False, - **options, - ): - """Draw a horizontal line. - - .. note:: - - This method was added to fulfill the - :class:`~qiskit_experiments.visualization.BaseDrawer` interface, - but it is not supported for this class since there was no - equivalent in - :class:`~qiskit_experiments.curve_analysis.visualization.BaseCurveDrawer`. - - Args: - y_value: Y value for line. - name: Name of this series. - label: Unsupported label option - legend: Unsupported legend option - options: Additional options - """ - warnings.warn( - "hline is not supported by the LegacyCurveCompatDrawer", - UserWarning, - ) - - # pylint: disable=unused-argument - def filled_y_area( - self, - x_data: Sequence[float], - y_ub: Sequence[float], - y_lb: Sequence[float], - name: Optional[str] = None, - label: Optional[str] = None, - legend: bool = False, - **options, - ): - """Draw filled area as a function of x-values. - - Args: - x_data: X values. - y_ub: The upper boundary of Y values. - y_lb: The lower boundary of Y values. - name: Name of this series. - label: Unsupported as :class:`BaseCurveDrawer` doesn't support customizing - legend entries. - legend: Unsupported as :class:`BaseCurveDrawer` doesn't support toggling - legend entries. - options: Valid options for the drawer backend API. - """ - - self._curve_drawer.draw_confidence_interval(x_data, y_ub, y_lb, name, **options) - - # pylint: disable=unused-argument - def filled_x_area( - self, - x_ub: Sequence[float], - x_lb: Sequence[float], - y_data: Sequence[float], - name: Optional[str] = None, - label: Optional[str] = None, - legend: bool = False, - **options, - ): - """Does nothing as this is functionality not supported by :class:`BaseCurveDrawer`.""" - warnings.warn(f"{self.__class__.__name__}.filled_x_area is not supported.") - - # pylint: disable=unused-argument - def textbox( - self, - description: str, - rel_pos: Optional[Tuple[float, float]] = None, - **options, - ): - """Draw textbox. - - Args: - description: A string to be drawn inside a text box. - rel_pos: Unsupported as :class:`BaseCurveDrawer` doesn't support modifying - the location of text in :meth:`textbox` or - :meth:`BaseCurveDrawer.draw_fit_report`. - options: Valid options for the drawer backend API. - """ - - self._curve_drawer.draw_fit_report(description, **options) - - # pylint: disable=unused-argument - def image( - self, - data: np.ndarray, - extent: Optional[ExtentTuple] = None, - name: Optional[str] = None, - label: Optional[str] = None, - cmap: Optional[Union[str, Any]] = None, - cmap_use_series_colors: bool = False, - colorbar: bool = False, - **options, - ): - warnings.warn(f"{self.__class__.__name__}.image is not supported.") - - @property - def figure(self): - return self._curve_drawer.figure - - def set_options(self, **fields): - ## Handle option name changes - # BaseCurveDrawer used `plot_options` instead of `series_params` - if "series_params" in fields: - fields["plot_options"] = fields.pop("series_params") - # PlotStyle parameters are normal options in BaseCurveDrawer. - if "custom_style" in fields: - custom_style = fields.pop("custom_style") - for key, value in custom_style.items(): - fields[key] = value - - self._curve_drawer.set_options(**fields) - - def set_figure_options(self, **fields): - self.set_options(**fields) diff --git a/releasenotes/notes/0_6_deprecations-9a399c48c2d461f1.yaml b/releasenotes/notes/0_6_deprecations-9a399c48c2d461f1.yaml new file mode 100644 index 0000000000..92558e08b2 --- /dev/null +++ b/releasenotes/notes/0_6_deprecations-9a399c48c2d461f1.yaml @@ -0,0 +1,29 @@ +--- +upgrade_package: + - | + Several deprecated modules, methods, functions, and options have been removed and will no longer work: + + * The visualization module of :mod:`.CurveAnalysis` has been replaced by the + standalone :mod:`.visualization` module. The ``LegacyCurveCompatDrawer`` has + been removed from :mod:`.visualization`. + * The ``curve_drawer`` option to :class:`.CompositeCurveAnalysis` has been + replaced by the plotter in the visualization module. + * The ``SeriesDef`` dataclass has been removed and replaced by the ``LMFIT`` module. + * The ``CurveData`` dataclass has been removed and replaced by :class:`.ScatterTable`'s DataFrame representation. + * ``random_cliffords()`` and ``random_clifford_circuits()`` have been + removed from :class:`.CliffordUtils` and replaced by :meth:`.StandardRB.__sample_sequence`. + * ``beta`` has been renamed to ``outcome_prior`` in the tomography utility + function ``binomial_weights()``. + * The ``return_data_points`` option has been removed from curve analysis. + Data points are now automatically provided in :class:`.ExperimentData` objects via the ``curve_data`` + artifact. + * The default value of ``flatten_results`` in composite experiments has changed to ``True``. +deprecations: + - | + Accessing experiment data artifacts by numerical index has been + deprecated. Use the name or ID of the artifact instead. +issues: + - | + Fit parameters are not populated in composite analysis results and are + found only in the fit summary artifact. In a future release, they will be + removed from all analysis result objects and live in the artifacts only. diff --git a/test/curve_analysis/test_baseclass.py b/test/curve_analysis/test_baseclass.py index 8c9626f870..06a9d0b68c 100644 --- a/test/curve_analysis/test_baseclass.py +++ b/test/curve_analysis/test_baseclass.py @@ -994,38 +994,3 @@ def test_complicated(self): self.assertDictEqual(opt1.options, ref_opt1) self.assertDictEqual(opt2.options, ref_opt2) - - -class TestBackwardCompatibility(QiskitExperimentsTestCase): - """Test case for backward compatibility.""" - - def test_lmfit_model_with_data_sort_key(self): - """Test providing LMFIT model with legacy 'data_sort_key' option.""" - - class _DeprecatedAnalysis(CurveAnalysis): - def __init__(self): - super().__init__( - models=[ - ExpressionModel( - expr="x + a", - name="experiment1", - data_sort_key={"tag": 1}, - ), - ExpressionModel( - expr="x + b", - name="experiment2", - data_sort_key={"tag": 2}, - ), - ] - ) - - instance = _DeprecatedAnalysis() - experiment_data = ExperimentData() - - with self.assertWarns(DeprecationWarning): - instance._initialize(experiment_data) - - self.assertDictEqual( - instance.options.data_subfit_map, - {"experiment1": {"tag": 1}, "experiment2": {"tag": 2}}, - ) diff --git a/test/database_service/test_db_experiment_data.py b/test/database_service/test_db_experiment_data.py index 0382d2b72a..27a54a89f7 100644 --- a/test/database_service/test_db_experiment_data.py +++ b/test/database_service/test_db_experiment_data.py @@ -1223,7 +1223,6 @@ def test_add_delete_artifact(self): self.assertEqual(exp_data.artifacts(), []) new_artifact = ArtifactData(name="test", data="foo") exp_data.add_artifacts(new_artifact) - self.assertEqual(exp_data.artifacts(0), new_artifact) self.assertEqual(exp_data.artifacts("test"), new_artifact) service = mock.create_autospec(IBMExperimentService, instance=True) @@ -1236,8 +1235,6 @@ def test_add_delete_artifact(self): exp_data.delete_artifact("test") self.assertEqual(exp_data.artifacts(), []) self.assertEqual(exp_data._deleted_artifacts, {"test"}) - with self.assertRaises(ExperimentEntryNotFound): - exp_data.artifacts(0) exp_data.save() # after saving, artifact_files should be updated again @@ -1250,9 +1247,9 @@ def test_add_delete_artifact(self): self.assertEqual(exp_data.artifacts(), [new_artifact, new_artifact2, new_artifact3]) self.assertEqual(exp_data.artifacts("test"), [new_artifact, new_artifact2]) - deleted_id = exp_data.artifacts(0).artifact_id + deleted_id = exp_data.artifacts()[0].artifact_id # delete by index - exp_data.delete_artifact(0) + exp_data.delete_artifact(deleted_id) self.assertEqual(exp_data.artifacts(), [new_artifact2, new_artifact3]) with self.assertRaises(ExperimentEntryNotFound): @@ -1266,7 +1263,7 @@ def test_add_delete_artifact(self): # finish deleting artifacts named test # delete by id - exp_data.delete_artifact(exp_data.artifacts(0).artifact_id) + exp_data.delete_artifact(exp_data.artifacts()[0].artifact_id) self.assertEqual(exp_data.artifacts(), [new_artifact3]) exp_data.save() self.assertEqual(exp_data._deleted_artifacts, set()) @@ -1296,8 +1293,5 @@ def test_delete_nonexistent_artifact(self): new_artifact1 = ArtifactData(artifact_id="0", name="test", data="foo") exp_data.add_artifacts(new_artifact1) - with self.assertRaises(ExperimentEntryNotFound): - exp_data.delete_artifact(2) - with self.assertRaises(ExperimentEntryNotFound): exp_data.delete_artifact("123")