diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d2c8228b0d..51e2447f0d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -424,12 +424,11 @@ release notes. #### Adding deprecation warnings -We use the deprecation wrappers in [Qiskit -Utilities](https://docs.quantum.ibm.com/api/qiskit/utils) to add warnings: +We use deprecation wrappers in `qiskit_experiments.framework.deprecation` to add warnings: ```python - from qiskit.utils.deprecation import deprecate_func + from qiskit_experiments.framework.deprecation import deprecate_func @deprecate_func( since="0.5", diff --git a/docs/conf.py b/docs/conf.py index 1822ed2bff..af6c7e7504 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -211,9 +211,12 @@ def setup(app): # Should come up with better way to address this from qiskit_experiments.curve_analysis import ParameterRepr +from qiskit_experiments.framework import MeasLevel, MeasReturnType def maybe_skip_member(app, what, name, obj, skip, options): + if skip: + return True skip_names = [ "analysis", "set_run_options", @@ -234,6 +237,11 @@ def maybe_skip_member(app, what, name, obj, skip, options): ParameterRepr.repr, ParameterRepr.unit, ] + if ( + not name.isupper() and + (getattr(MeasLevel, name, None) == obj or getattr(MeasReturnType, name, None) == obj) + ): + return True if not skip: return (name in skip_names or obj in skip_members) and what == "attribute" return skip diff --git a/docs/manuals/characterization/t1.rst b/docs/manuals/characterization/t1.rst index 1fefa30371..11b181aabe 100644 --- a/docs/manuals/characterization/t1.rst +++ b/docs/manuals/characterization/t1.rst @@ -37,8 +37,7 @@ for qubit 0. .. jupyter-execute:: import numpy as np - from qiskit.qobj.utils import MeasLevel - from qiskit_experiments.framework import ParallelExperiment + from qiskit_experiments.framework import MeasLevel, ParallelExperiment from qiskit_experiments.library import T1 from qiskit_experiments.library.characterization.analysis.t1_analysis import T1KerneledAnalysis diff --git a/docs/tutorials/custom_experiment.rst b/docs/tutorials/custom_experiment.rst index ab533306bc..f0161aa00f 100644 --- a/docs/tutorials/custom_experiment.rst +++ b/docs/tutorials/custom_experiment.rst @@ -601,7 +601,7 @@ perfect symmetrical results between :math:`|0000\rangle` and :math:`|1111\rangle .. jupyter-execute:: - expdata_ideal = exp.run(AerSimulator(), shots=shots) + expdata_ideal = exp.run(backend_ideal, shots=shots) counts_ideal = expdata_ideal.analysis_results("counts").value print(counts_ideal) diff --git a/docs/tutorials/getting_started.rst b/docs/tutorials/getting_started.rst index 72062fde4a..da187157bc 100644 --- a/docs/tutorials/getting_started.rst +++ b/docs/tutorials/getting_started.rst @@ -297,7 +297,7 @@ supports can be set: .. jupyter-execute:: - from qiskit.qobj.utils import MeasLevel + from qiskit_experiments.framework import MeasLevel exp.set_run_options(shots=1000, meas_level=MeasLevel.CLASSIFIED) diff --git a/qiskit_experiments/curve_analysis/utils.py b/qiskit_experiments/curve_analysis/utils.py index 21405a37af..3b34d0554d 100644 --- a/qiskit_experiments/curve_analysis/utils.py +++ b/qiskit_experiments/curve_analysis/utils.py @@ -19,7 +19,6 @@ import lmfit import numpy as np import pandas as pd -from qiskit.utils.deprecation import deprecate_func from qiskit.utils import detach_prefix from uncertainties import UFloat, wrap as wrap_function from uncertainties import unumpy @@ -27,6 +26,7 @@ from qiskit_experiments.curve_analysis.curve_data import CurveFitResult from qiskit_experiments.exceptions import AnalysisError, QiskitError from qiskit_experiments.framework import AnalysisResultData +from qiskit_experiments.framework.deprecation import deprecate_func UNUMPY_FUNCS = {fn: getattr(unumpy, fn) for fn in unumpy.__all__} diff --git a/qiskit_experiments/data_processing/mitigation/local_readout_mitigator.py b/qiskit_experiments/data_processing/mitigation/local_readout_mitigator.py index 4e843dbbd4..d2bb66d539 100644 --- a/qiskit_experiments/data_processing/mitigation/local_readout_mitigator.py +++ b/qiskit_experiments/data_processing/mitigation/local_readout_mitigator.py @@ -15,6 +15,7 @@ import math +import warnings from typing import Optional, List, Tuple, Iterable, Callable, Union, Dict import numpy as np @@ -53,6 +54,8 @@ def __init__( Raises: QiskitError: matrices sizes do not agree with number of qubits """ + if assignment_matrices is None and backend is None: + raise QiskitError("Either assignment_matrices or backend must be provided.") if assignment_matrices is None: assignment_matrices = self._from_backend(backend, qubits) else: @@ -293,7 +296,14 @@ def stddev_upper_bound(self, shots: int, qubits: List[int] = None): def _from_backend(self, backend, qubits): """Calculates amats from backend properties readout_error""" - backend_qubits = backend.properties().qubits + try: + backend_qubits = backend.properties().qubits + except AttributeError as exc: + raise QiskitError( + "Either assignment_matrices must be passed or a backend " + "supporting backend.properties().qubits" + ) from exc + if qubits is not None: if any(qubit >= len(backend_qubits) for qubit in qubits): raise QiskitError("The chosen backend does not contain the specified qubits.") @@ -304,13 +314,20 @@ def _from_backend(self, backend, qubits): amats = np.zeros([num_qubits, 2, 2], dtype=float) for qubit_idx, qubit_prop in enumerate(backend_qubits): + props_to_find = {"prob_meas0_prep1", "prob_meas1_prep0"} for prop in qubit_prop: if prop.name == "prob_meas0_prep1": + props_to_find.discard(prop.name) (amats[qubit_idx])[0, 1] = prop.value (amats[qubit_idx])[1, 1] = 1 - prop.value if prop.name == "prob_meas1_prep0": + props_to_find.discard(prop.name) (amats[qubit_idx])[1, 0] = prop.value (amats[qubit_idx])[0, 0] = 1 - prop.value + if props_to_find: + warnings.warn( + f"Expected readout error properties were missing: {','.join(props_to_find)}" + ) return amats diff --git a/qiskit_experiments/data_processing/nodes.py b/qiskit_experiments/data_processing/nodes.py index c8d597abc3..e0cfff0c31 100644 --- a/qiskit_experiments/data_processing/nodes.py +++ b/qiskit_experiments/data_processing/nodes.py @@ -23,7 +23,7 @@ from uncertainties import unumpy as unp, ufloat from qiskit.result.postprocess import format_counts_memory -from qiskit.utils import deprecate_func +from qiskit_experiments.framework.deprecation import deprecate_func from qiskit_experiments.data_processing.data_action import DataAction, TrainableDataAction from qiskit_experiments.data_processing.exceptions import DataProcessorError from qiskit_experiments.data_processing.discriminator import BaseDiscriminator @@ -921,6 +921,7 @@ class RestlessNode(DataAction, ABC): "processor on an experiment." ), package_name="qiskit-experiments", + stacklevel=3, ) def __init__( self, validate: bool = True, memory_allocation: ShotOrder = ShotOrder.circuit_first diff --git a/qiskit_experiments/data_processing/processor_library.py b/qiskit_experiments/data_processing/processor_library.py index 9370a28d66..32d3607dd4 100644 --- a/qiskit_experiments/data_processing/processor_library.py +++ b/qiskit_experiments/data_processing/processor_library.py @@ -14,9 +14,8 @@ import warnings from typing import Union, Optional, List -from qiskit.qobj.utils import MeasLevel, MeasReturnType -from qiskit_experiments.framework import ExperimentData, Options +from qiskit_experiments.framework import ExperimentData, MeasLevel, MeasReturnType, Options from qiskit_experiments.data_processing.exceptions import DataProcessorError from qiskit_experiments.data_processing.data_action import DataAction from qiskit_experiments.data_processing.data_processor import DataProcessor diff --git a/qiskit_experiments/framework/__init__.py b/qiskit_experiments/framework/__init__.py index 0de746149a..d83447a2f2 100644 --- a/qiskit_experiments/framework/__init__.py +++ b/qiskit_experiments/framework/__init__.py @@ -99,6 +99,8 @@ Job BaseJob ExtendedJob + MeasLevel + MeasReturnType .. _composite-experiment: @@ -145,6 +147,17 @@ FigureData, FigureType, ) +from .provider_interfaces import ( + BaseJob, + BaseProvider, + ExtendedJob, + IBMProvider, + Job, + MeasLevel, + MeasReturnType, + Provider, +) + from .base_analysis import BaseAnalysis from .base_experiment import BaseExperiment from .backend_timing import BackendTiming @@ -159,4 +172,3 @@ CompositeAnalysis, ) from .json import ExperimentEncoder, ExperimentDecoder -from .provider_interfaces import BaseJob, BaseProvider, ExtendedJob, IBMProvider, Job, Provider diff --git a/qiskit_experiments/framework/backend_data.py b/qiskit_experiments/framework/backend_data.py index fab0ac9775..1bc9d6b719 100644 --- a/qiskit_experiments/framework/backend_data.py +++ b/qiskit_experiments/framework/backend_data.py @@ -12,12 +12,19 @@ """ Backend data access helper class -Since `BackendV1` and `BackendV2` do not share the same interface, this -class unifies data access for various data fields. +This class was introduced to unify backend data access to either `BackendV1` and `BackendV2` +objects, wrapped by an object of this class. With the removal of `BackendV1` in Qiskit 2.0, +this class will not serve any useful purpose once support for Qiskit 1 is dropped. + +TODO: remove this class """ +import os +import sys import warnings -from qiskit.providers.models import PulseBackendConfiguration # pylint: disable=no-name-in-module -from qiskit.providers import BackendV1, BackendV2 + +from qiskit.providers import BackendV2 + +import qiskit_experiments class BackendData: @@ -27,22 +34,33 @@ def __init__(self, backend): """Inits the backend and verifies version""" self._backend = backend - self._v1 = isinstance(backend, BackendV1) + self._v1 = False self._v2 = isinstance(backend, BackendV2) - - if self._v2: - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", message=".*qiskit.qobj.pulse_qobj.*", category=DeprecationWarning - ) - self._parse_additional_data() - - def _parse_additional_data(self): - # data specific parsing not done yet in upstream qiskit - if hasattr(self._backend, "_conf_dict") and self._backend._conf_dict["open_pulse"]: - if "u_channel_lo" not in self._backend._conf_dict: - self._backend._conf_dict["u_channel_lo"] = [] # to avoid qiskit bug - self._pulse_conf = PulseBackendConfiguration.from_dict(self._backend._conf_dict) + if not self._v2: + try: + from qiskit.providers import BackendV1 + + self._v1 = isinstance(backend, BackendV1) + + if self._v1: + if sys.version_info[:2] >= (3, 12): + kwargs = { + "skip_file_prefixes": (os.path.dirname(qiskit_experiments.__file__),) + } + else: + kwargs = {} + warnings.warn( + ( + "Support for BackendV1 with Qiskit Experiments is " + "deprecated and will be removed in a future release. " + "Please update to using BackendV2 backends." + ), + DeprecationWarning, + stacklevel=2, + **kwargs, + ) + except ImportError: + pass @property def name(self): diff --git a/qiskit_experiments/framework/base_experiment.py b/qiskit_experiments/framework/base_experiment.py index 4338b851c2..9a51012a49 100644 --- a/qiskit_experiments/framework/base_experiment.py +++ b/qiskit_experiments/framework/base_experiment.py @@ -21,11 +21,10 @@ from qiskit import transpile, QuantumCircuit from qiskit.providers import Job, Backend from qiskit.exceptions import QiskitError -from qiskit.qobj.utils import MeasLevel from qiskit.providers.options import Options from qiskit.primitives.base import BaseSamplerV2 from qiskit_ibm_runtime import SamplerV2 as Sampler -from qiskit_experiments.framework import BackendData +from qiskit_experiments.framework import BackendData, MeasLevel from qiskit_experiments.framework.store_init_args import StoreInitArgs from qiskit_experiments.framework.base_analysis import BaseAnalysis from qiskit_experiments.framework.experiment_data import ExperimentData diff --git a/qiskit_experiments/framework/deprecation.py b/qiskit_experiments/framework/deprecation.py new file mode 100644 index 0000000000..4422a2e7fb --- /dev/null +++ b/qiskit_experiments/framework/deprecation.py @@ -0,0 +1,382 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2025. +# +# 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. + +"""Deprecation utilities""" + +from __future__ import annotations + +import functools +import inspect +import warnings +from collections.abc import Callable +from typing import Any, Type + + +def deprecate_func( + *, + since: str, + additional_msg: str | None = None, + pending: bool = False, + package_name: str = "Qiskit Experiments", + removal_timeline: str = "no earlier than 3 months after the release date", + is_property: bool = False, + stacklevel: int = 2, +): + """Decorator to indicate a function has been deprecated. + + It should be placed beneath other decorators like `@staticmethod` and property decorators. + + When deprecating a class, set this decorator on its `__init__` function. + + Args: + since: The version the deprecation started at. If the deprecation is pending, set + the version to when that started; but later, when switching from pending to + deprecated, update ``since`` to the new version. + additional_msg: Put here any additional information, such as what to use instead. + For example, "Instead, use the function ``new_func`` from the module + ``.``, which is similar but uses GPU acceleration." + pending: Set to ``True`` if the deprecation is still pending. + package_name: The package name shown in the deprecation message (e.g. the PyPI package name). + removal_timeline: How soon can this deprecation be removed? Expects a value + like "no sooner than 6 months after the latest release" or "in release 9.99". + is_property: If the deprecated function is a `@property`, set this to True so that the + generated message correctly describes it as such. (This isn't necessary for + property setters, as their docstring is ignored by Python.) + stacklevel: Stack level passed to :func:`warnings.warn`. + Returns: + Callable: The decorated callable. + """ + + def decorator(func): + qualname = func.__qualname__ # For methods, `qualname` includes the class name. + mod_name = func.__module__ + + # Detect what function type this is. + if is_property: + # `inspect.isdatadescriptor()` doesn't work because you must apply our decorator + # before `@property`, so it looks like the function is a normal method. + deprecated_entity = f"The property ``{mod_name}.{qualname}``" + # To determine if's a method, we use the heuristic of looking for a `.` in the qualname. + # This is because top-level functions will only have the function name. This is not + # perfect, e.g. it incorrectly classifies nested/inner functions, but we don't expect + # those to be deprecated. + # + # We can't use `inspect.ismethod()` because that only works when calling it on an instance + # of the class, rather than the class type itself, i.e. `ismethod(C().foo)` vs + # `ismethod(C.foo)`. + elif "." in qualname: + if func.__name__ == "__init__": + cls_name = qualname[: -len(".__init__")] + deprecated_entity = f"The class ``{mod_name}.{cls_name}``" + else: + deprecated_entity = f"The method ``{mod_name}.{qualname}()``" + else: + deprecated_entity = f"The function ``{mod_name}.{qualname}()``" + + msg, category = _write_deprecation_msg( + deprecated_entity=deprecated_entity, + package_name=package_name, + since=since, + pending=pending, + additional_msg=additional_msg, + removal_timeline=removal_timeline, + ) + + @functools.wraps(func) + def wrapper(*args, **kwargs): + warnings.warn(msg, category=category, stacklevel=stacklevel) + return func(*args, **kwargs) + + add_deprecation_to_docstring(wrapper, msg, since=since, pending=pending) + return wrapper + + return decorator + + +def deprecate_arg( + name: str, + *, + since: str, + additional_msg: str | None = None, + deprecation_description: str | None = None, + pending: bool = False, + package_name: str = "Qiskit", + new_alias: str | None = None, + predicate: Callable[[Any], bool] | None = None, + removal_timeline: str = "no earlier than 3 months after the release date", +): + """Decorator to indicate an argument has been deprecated in some way. + + This decorator may be used multiple times on the same function, once per deprecated argument. + It should be placed beneath other decorators like ``@staticmethod`` and property decorators. + + Args: + name: The name of the deprecated argument. + since: The version the deprecation started at. If the deprecation is pending, set + the version to when that started; but later, when switching from pending to + deprecated, update `since` to the new version. + deprecation_description: What is being deprecated? E.g. "Setting my_func()'s `my_arg` + argument to `None`." If not set, will default to "{func_name}'s argument `{name}`". + additional_msg: Put here any additional information, such as what to use instead + (if new_alias is not set). For example, "Instead, use the argument `new_arg`, + which is similar but does not impact the circuit's setup." + pending: Set to `True` if the deprecation is still pending. + package_name: The package name shown in the deprecation message (e.g. the PyPI package name). + new_alias: If the arg has simply been renamed, set this to the new name. The decorator will + dynamically update the `kwargs` so that when the user sets the old arg, it will be + passed in as the `new_alias` arg. + predicate: Only log the runtime warning if the predicate returns True. This is useful to + deprecate certain values or types for an argument, e.g. + `lambda my_arg: isinstance(my_arg, dict)`. Regardless of if a predicate is set, the + runtime warning will only log when the user specifies the argument. + removal_timeline: How soon can this deprecation be removed? Expects a value + like "no sooner than 6 months after the latest release" or "in release 9.99". + + Returns: + Callable: The decorated callable. + """ + + def decorator(func): + # For methods, `__qualname__` includes the class name. + func_name = f"{func.__module__}.{func.__qualname__}()" + deprecated_entity = deprecation_description or f"``{func_name}``'s argument ``{name}``" + + if new_alias: + alias_msg = f"Instead, use the argument ``{new_alias}``, which behaves identically." + if additional_msg: + final_additional_msg = f"{alias_msg}. {additional_msg}" + else: + final_additional_msg = alias_msg + else: + final_additional_msg = additional_msg + + msg, category = _write_deprecation_msg( + deprecated_entity=deprecated_entity, + package_name=package_name, + since=since, + pending=pending, + additional_msg=final_additional_msg, + removal_timeline=removal_timeline, + ) + + @functools.wraps(func) + def wrapper(*args, **kwargs): + _maybe_warn_and_rename_kwarg( + args, + kwargs, + func_name=func_name, + original_func_co_varnames=wrapper.__original_func_co_varnames, + old_arg_name=name, + new_alias=new_alias, + warning_msg=msg, + category=category, + predicate=predicate, + ) + return func(*args, **kwargs) + + # When decorators get called repeatedly, `func` refers to the result of the prior + # decorator, not the original underlying function. This trick allows us to record the + # original function's variable names regardless of how many decorators are used. + # + # If it's the very first decorator call, we also check that *args and **kwargs are not used. + if hasattr(func, "__original_func_co_varnames"): + wrapper.__original_func_co_varnames = func.__original_func_co_varnames + else: + wrapper.__original_func_co_varnames = func.__code__.co_varnames + param_kinds = {param.kind for param in inspect.signature(func).parameters.values()} + if inspect.Parameter.VAR_POSITIONAL in param_kinds: + raise ValueError( + "@deprecate_arg cannot be used with functions that take variable *args. Use " + "warnings.warn() directly instead." + ) + + add_deprecation_to_docstring(wrapper, msg, since=since, pending=pending) + return wrapper + + return decorator + + +def _maybe_warn_and_rename_kwarg( + args: tuple[Any, ...], + kwargs: dict[str, Any], + *, + func_name: str, + original_func_co_varnames: tuple[str, ...], + old_arg_name: str, + new_alias: str | None, + warning_msg: str, + category: Type[Warning], + predicate: Callable[[Any], bool] | None, +) -> None: + # In Python 3.10+, we should set `zip(strict=False)` (the default). That is, we want to + # stop iterating once `args` is done, since some args may have not been explicitly passed as + # positional args. + arg_names_to_values = {name: val for val, name in zip(args, original_func_co_varnames)} + arg_names_to_values.update(kwargs) + + if old_arg_name not in arg_names_to_values: + return + if new_alias and new_alias in arg_names_to_values: + raise TypeError(f"{func_name} received both {new_alias} and {old_arg_name} (deprecated).") + + val = arg_names_to_values[old_arg_name] + if predicate and not predicate(val): + return + warnings.warn(warning_msg, category=category, stacklevel=3) + + # Finally, if there's a new_alias, add its value dynamically to kwargs so that the code author + # only has to deal with the new_alias in their logic. + if new_alias is not None: + kwargs[new_alias] = val + + +def _write_deprecation_msg( + *, + deprecated_entity: str, + package_name: str, + since: str, + pending: bool, + additional_msg: str, + removal_timeline: str, +) -> tuple[str, Type[DeprecationWarning] | Type[PendingDeprecationWarning]]: + if pending: + category: Type[DeprecationWarning] | Type[ + PendingDeprecationWarning + ] = PendingDeprecationWarning + deprecation_status = "pending deprecation" + removal_desc = f"marked deprecated in a future release, and then removed {removal_timeline}" + else: + category = DeprecationWarning + deprecation_status = "deprecated" + removal_desc = f"removed {removal_timeline}" + + msg = ( + f"{deprecated_entity} is {deprecation_status} as of {package_name} {since}. " + f"It will be {removal_desc}." + ) + if additional_msg: + msg += f" {additional_msg}" + return msg, category + + +# We insert deprecations in-between the description and Napoleon's meta sections. The below is from +# https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html#docstring-sections. We use +# lowercase because Napoleon is case-insensitive. +_NAPOLEON_META_LINES = frozenset( + { + "args:", + "arguments:", + "attention:", + "attributes:", + "caution:", + "danger:", + "error:", + "example:", + "examples:", + "hint:", + "important:", + "keyword args:", + "keyword arguments:", + "note:", + "notes:", + "other parameters:", + "parameters:", + "return:", + "returns:", + "raises:", + "references:", + "see also:", + "tip:", + "todo:", + "warning:", + "warnings:", + "warn:", + "warns:", + "yield:", + "yields:", + } +) + + +def add_deprecation_to_docstring( + func: Callable, msg: str, *, since: str | None, pending: bool +) -> None: + """Dynamically insert the deprecation message into ``func``'s docstring. + + Args: + func: The function to modify. + msg: The full deprecation message. + since: The version the deprecation started at. + pending: Is the deprecation still pending? + """ + if "\n" in msg: + raise ValueError( + "Deprecation messages cannot contain new lines (`\\n`), but the deprecation for " + f'{func.__qualname__} had them. Usually this happens when using `"""` multiline ' + f"strings; instead, use string concatenation.\n\n" + "This is a simplification to facilitate deprecation messages being added to the " + "documentation. If you have a compelling reason to need " + "new lines, feel free to improve this function or open a request at " + "https://github.com/Qiskit/qiskit/issues." + ) + + if since is None: + version_str = "unknown" + else: + version_str = f"{since}_pending" if pending else since + + indent = "" + meta_index = None + if func.__doc__: + original_lines = func.__doc__.splitlines() + content_encountered = False + for i, line in enumerate(original_lines): + stripped = line.strip() + + # Determine the indent based on the first line with content. But, we don't consider the + # first line, which corresponds to the format """Docstring.""", as it does not properly + # capture the indentation of lines beneath it. + if not content_encountered and i != 0 and stripped: + num_leading_spaces = len(line) - len(line.lstrip()) + indent = " " * num_leading_spaces + content_encountered = True + + if stripped.lower() in _NAPOLEON_META_LINES: + meta_index = i + if not content_encountered: + raise ValueError( + "add_deprecation_to_docstring cannot currently handle when a Napoleon " + "metadata line like 'Args' is the very first line of docstring, " + f'e.g. `"""Args:`. So, it cannot process {func.__qualname__}. Instead, ' + f'move the metadata line to the second line, e.g.:\n\n"""\nArgs:' + ) + # We can stop checking since we only care about the first meta line, and + # we've validated content_encountered is True to determine the indent. + break + else: + original_lines = [] + + # We defensively include new lines in the beginning and end. This is sometimes necessary, + # depending on the original docstring. It is not a big deal to have extra, other than `help()` + # being a little longer. + new_lines = [ + indent, + f"{indent}.. deprecated:: {version_str}", + f"{indent} {msg}", + indent, + ] + + if meta_index: + original_lines[meta_index - 1 : meta_index - 1] = new_lines + else: + original_lines.extend(new_lines) + func.__doc__ = "\n".join(original_lines) diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 04c5d3d26d..a48027507f 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -38,7 +38,6 @@ from qiskit.providers.jobstatus import JobStatus, JOB_FINAL_STATES from qiskit.exceptions import QiskitError from qiskit.providers import Backend -from qiskit.utils.deprecation import deprecate_arg from qiskit.primitives import BitArray, SamplerPubResult from qiskit_ibm_experiment import ( @@ -61,6 +60,7 @@ from qiskit_experiments.framework import BackendData from qiskit_experiments.framework.containers import ArtifactData from qiskit_experiments.framework import ExperimentStatus, AnalysisStatus, AnalysisCallback +from qiskit_experiments.framework.deprecation import deprecate_arg from qiskit_experiments.framework.package_deps import qiskit_version from qiskit_experiments.database_service.exceptions import ( ExperimentDataError, @@ -1069,8 +1069,13 @@ def _add_result_data( # Format to Counts object rather than hex dict data["counts"] = result.get_counts(i) expr_result = result.results[i] - if hasattr(expr_result, "header") and hasattr(expr_result.header, "metadata"): - data["metadata"] = expr_result.header.metadata + if hasattr(expr_result, "header"): + if hasattr(expr_result.header, "metadata"): + data["metadata"] = expr_result.header.metadata + elif isinstance(expr_result.header, dict): + data["metadata"] = expr_result.header.get("metadata", {}) + else: + data["metadata"] = {} data["shots"] = expr_result.shots data["meas_level"] = expr_result.meas_level if hasattr(expr_result, "meas_return"): diff --git a/qiskit_experiments/framework/provider_interfaces.py b/qiskit_experiments/framework/provider_interfaces.py index 12c980b59b..5dec37992e 100644 --- a/qiskit_experiments/framework/provider_interfaces.py +++ b/qiskit_experiments/framework/provider_interfaces.py @@ -21,6 +21,7 @@ """ from __future__ import annotations +from enum import Enum, IntEnum from typing import Protocol, Union from qiskit.result import Result @@ -106,3 +107,18 @@ def active_account(self) -> dict[str, str] | None: Provider = Union[BaseProvider, IBMProvider] """Union type of provider interfaces supported by Qiskit Experiments""" + + +class MeasReturnType(str, Enum): + """Backend return types for Qobj and backend.run jobs""" + + AVERAGE = "avg" + SINGLE = "single" + + +class MeasLevel(IntEnum): + """Measurement level types for legacy Qobj and Sampler jobs""" + + RAW = 0 + KERNELED = 1 + CLASSIFIED = 2 diff --git a/qiskit_experiments/library/characterization/correlated_readout_error.py b/qiskit_experiments/library/characterization/correlated_readout_error.py index 617feb523f..938914f864 100644 --- a/qiskit_experiments/library/characterization/correlated_readout_error.py +++ b/qiskit_experiments/library/characterization/correlated_readout_error.py @@ -81,8 +81,7 @@ class CorrelatedReadoutError(BaseExperiment): from qiskit_aer import AerSimulator num_qubits=5 - backend = AerSimulator.from_backend(GenericBackendV2(num_qubits=num_qubits, - calibrate_instructions=True)) + backend = AerSimulator.from_backend(GenericBackendV2(num_qubits=num_qubits)) .. jupyter-execute:: diff --git a/qiskit_experiments/library/characterization/fine_amplitude.py b/qiskit_experiments/library/characterization/fine_amplitude.py index 2b0906b6f2..77abb6fc3f 100644 --- a/qiskit_experiments/library/characterization/fine_amplitude.py +++ b/qiskit_experiments/library/characterization/fine_amplitude.py @@ -19,9 +19,9 @@ from qiskit.circuit import Gate from qiskit.circuit.library import XGate, SXGate from qiskit.providers.backend import Backend -from qiskit.utils import deprecate_func from qiskit_experiments.framework import BaseExperiment, Options +from qiskit_experiments.framework.deprecation import deprecate_func from qiskit_experiments.library.characterization.analysis import FineAmplitudeAnalysis @@ -460,11 +460,7 @@ def _default_transpile_options(cls) -> Options: Experiment Options: basis_gates: Set to :code:`["szx"]`. - inst_map: The instruction schedule map that will contain the schedule for the - Rzx(pi/2) gate. This schedule should be stored under the instruction name - ``szx``. """ options = super()._default_transpile_options() options.basis_gates = ["szx"] - options.inst_map = None return options diff --git a/qiskit_experiments/library/characterization/fine_frequency.py b/qiskit_experiments/library/characterization/fine_frequency.py index 6a694bc54e..5329834e57 100644 --- a/qiskit_experiments/library/characterization/fine_frequency.py +++ b/qiskit_experiments/library/characterization/fine_frequency.py @@ -123,7 +123,6 @@ def _default_experiment_options(cls) -> Options: def _default_transpile_options(cls) -> Options: """Default transpiler options.""" options = super()._default_transpile_options() - options.inst_map = None options.basis_gates = ["sx", "rz", "delay"] return options diff --git a/qiskit_experiments/library/characterization/multi_state_discrimination.py b/qiskit_experiments/library/characterization/multi_state_discrimination.py index e3adadcbf6..f1c615c432 100644 --- a/qiskit_experiments/library/characterization/multi_state_discrimination.py +++ b/qiskit_experiments/library/characterization/multi_state_discrimination.py @@ -19,8 +19,7 @@ from qiskit.circuit import Gate from qiskit.providers import Backend from qiskit.providers.options import Options -from qiskit.qobj.utils import MeasLevel, MeasReturnType -from qiskit_experiments.framework import BaseExperiment +from qiskit_experiments.framework import BaseExperiment, MeasLevel, MeasReturnType from qiskit_experiments.library.characterization import MultiStateDiscriminationAnalysis diff --git a/qiskit_experiments/library/characterization/ramsey_xy.py b/qiskit_experiments/library/characterization/ramsey_xy.py index a2d056a605..b4b48c2c32 100644 --- a/qiskit_experiments/library/characterization/ramsey_xy.py +++ b/qiskit_experiments/library/characterization/ramsey_xy.py @@ -16,9 +16,8 @@ import numpy as np from qiskit.circuit import QuantumCircuit, Parameter from qiskit.providers.backend import Backend -from qiskit.qobj.utils import MeasLevel -from qiskit_experiments.framework import BaseExperiment, Options, BackendTiming +from qiskit_experiments.framework import BaseExperiment, MeasLevel, Options, BackendTiming from qiskit_experiments.library.characterization.analysis import RamseyXYAnalysis diff --git a/qiskit_experiments/library/characterization/readout_angle.py b/qiskit_experiments/library/characterization/readout_angle.py index bce173b705..43649797c4 100644 --- a/qiskit_experiments/library/characterization/readout_angle.py +++ b/qiskit_experiments/library/characterization/readout_angle.py @@ -16,10 +16,9 @@ from typing import List, Optional, Sequence from qiskit.circuit import QuantumCircuit -from qiskit.qobj.utils import MeasLevel from qiskit.providers.backend import Backend -from qiskit_experiments.framework import BaseExperiment, Options +from qiskit_experiments.framework import BaseExperiment, MeasLevel, Options from qiskit_experiments.library.characterization.analysis.readout_angle_analysis import ( ReadoutAngleAnalysis, ) diff --git a/qiskit_experiments/library/quantum_volume/qv_experiment.py b/qiskit_experiments/library/quantum_volume/qv_experiment.py index ae8406c928..8e30d21fb3 100644 --- a/qiskit_experiments/library/quantum_volume/qv_experiment.py +++ b/qiskit_experiments/library/quantum_volume/qv_experiment.py @@ -146,35 +146,41 @@ def _default_experiment_options(cls) -> Options: return options - def _get_ideal_data(self, circuit: QuantumCircuit, **run_options) -> List[float]: + def _get_ideal_data(self, circuits: List[QuantumCircuit], **run_options) -> List[List[float]]: """Return ideal measurement probabilities. In case the user does not have Aer installed, use Qiskit's quantum info module to calculate the ideal state. Args: - circuit: the circuit to extract the ideal data from + circuits: the circuits to extract the ideal data from run_options: backend run options. Returns: - list: list of the probabilities for each state in the circuit. + list: list of lists of the probabilities for each state in each circuit. """ - ideal_circuit = circuit.remove_final_measurements(inplace=False) + # NOTE: this code used to process a single circuit at a time but + # AerSimulator() generates a backend object that regenerates its Target + # on the fly each time and the overhead of regenerating the target for + # each transpile call was significant. if self._simulation_backend: - ideal_circuit.save_probabilities() + circuits = [c.copy() for c in circuits] + for circuit in circuits: + circuit.save_probabilities() # always transpile with optimization_level 0, even if the non ideal circuits will run # with different optimization level, because we need to compare the results to the # exact generated probabilities - ideal_circuit = transpile(ideal_circuit, self._simulation_backend, optimization_level=0) + t_circuits = transpile(circuits, self._simulation_backend, optimization_level=0) - ideal_result = self._simulation_backend.run(ideal_circuit, **run_options).result() - probabilities = ideal_result.data().get("probabilities") + result = self._simulation_backend.run(t_circuits, **run_options).result() + probabilities = [ + result.data(i).get("probabilities").tolist() for i, _ in enumerate(t_circuits) + ] else: from qiskit.quantum_info import Statevector - state_vector = Statevector(ideal_circuit) - probabilities = state_vector.probabilities() - return list(probabilities) + probabilities = [Statevector(c).probabilities().tolist() for c in circuits] + return probabilities def circuits(self) -> List[QuantumCircuit]: """Return a list of Quantum Volume circuits. @@ -189,11 +195,14 @@ def circuits(self) -> List[QuantumCircuit]: # Note: the trials numbering in the metadata is starting from 1 for each new experiment run for trial in range(1, self.experiment_options.trials + 1): qv_circ = QuantumVolumeCircuit(depth, depth, seed=rng) - qv_circ.measure_active() qv_circ.metadata = { "depth": depth, "trial": trial, - "ideal_probabilities": self._get_ideal_data(qv_circ), } circuits.append(qv_circ) + all_probs = self._get_ideal_data(circuits) + for qv_circ, probs in zip(circuits, all_probs): + qv_circ.measure_active() + qv_circ.metadata["ideal_probabilities"] = probs + return circuits diff --git a/qiskit_experiments/library/randomized_benchmarking/layer_fidelity.py b/qiskit_experiments/library/randomized_benchmarking/layer_fidelity.py index cfa6732b85..e6e07b145f 100644 --- a/qiskit_experiments/library/randomized_benchmarking/layer_fidelity.py +++ b/qiskit_experiments/library/randomized_benchmarking/layer_fidelity.py @@ -22,10 +22,16 @@ from qiskit.circuit import QuantumCircuit, CircuitInstruction, Barrier, Gate from qiskit.circuit.library import get_standard_gate_name_mapping from qiskit.exceptions import QiskitError -from qiskit.providers import BackendV2Converter -from qiskit.providers.backend import Backend, BackendV1 +from qiskit.providers.backend import Backend from qiskit.quantum_info import Clifford +try: + from qiskit.providers import BackendV2Converter + from qiskit.providers.backend import BackendV1 +except ImportError: + BackendV1 = None + BackendV2Converter = None + from qiskit_experiments.framework import BaseExperiment, Options from qiskit_experiments.framework.configs import ExperimentConfig @@ -287,10 +293,8 @@ def set_transpile_options(self, **fields): ) def _set_backend(self, backend: Backend): - """Set the backend V2 for RB experiments since RB experiments only support BackendV2. - If BackendV1 is provided, it is converted to V2 and stored. - """ - if isinstance(backend, BackendV1): + """Set the backend V2 for RB experiments since RB experiments only support BackendV2.""" + if BackendV1 is not None and isinstance(backend, BackendV1): super()._set_backend(BackendV2Converter(backend, add_delay=True)) else: super()._set_backend(backend) diff --git a/qiskit_experiments/library/randomized_benchmarking/standard_rb.py b/qiskit_experiments/library/randomized_benchmarking/standard_rb.py index d1606e88d5..7b889da541 100644 --- a/qiskit_experiments/library/randomized_benchmarking/standard_rb.py +++ b/qiskit_experiments/library/randomized_benchmarking/standard_rb.py @@ -25,11 +25,17 @@ from qiskit.circuit import CircuitInstruction, QuantumCircuit, Instruction, Barrier, Gate from qiskit.exceptions import QiskitError -from qiskit.providers import BackendV2Converter -from qiskit.providers.backend import Backend, BackendV1, BackendV2 +from qiskit.providers.backend import Backend, BackendV2 from qiskit.quantum_info import Clifford from qiskit.quantum_info.random import random_clifford from qiskit.transpiler import CouplingMap + +try: + from qiskit.providers import BackendV2Converter + from qiskit.providers.backend import BackendV1 +except ImportError: + BackendV1 = None + BackendV2Converter = None from qiskit_experiments.framework import BaseExperiment, Options from .clifford_utils import ( CliffordUtils, @@ -215,7 +221,11 @@ def _set_backend(self, backend: Backend): """Set the backend V2 for RB experiments since RB experiments only support BackendV2 except for simulators. If BackendV1 is provided, it is converted to V2 and stored. """ - if isinstance(backend, BackendV1) and "simulator" not in backend.name(): + if ( + BackendV1 is not None + and isinstance(backend, BackendV1) + and "simulator" not in backend.name() + ): super()._set_backend(BackendV2Converter(backend, add_delay=True)) else: super()._set_backend(backend) @@ -297,7 +307,7 @@ def _get_synthesis_options(self) -> Dict[str, Optional[Any]]: break basis_gates = basis_gates if basis_gates else backend_basis_gates coupling_map = coupling_map if coupling_map else backend_cmap - elif isinstance(self.backend, BackendV1): + elif BackendV1 is not None and isinstance(self.backend, BackendV1): backend_basis_gates = self.backend.configuration().basis_gates backend_cmap = self.backend.configuration().coupling_map if backend_cmap: diff --git a/qiskit_experiments/test/fake_backend.py b/qiskit_experiments/test/fake_backend.py index d67a259ab4..88602210fd 100644 --- a/qiskit_experiments/test/fake_backend.py +++ b/qiskit_experiments/test/fake_backend.py @@ -13,7 +13,6 @@ """Fake backend class for tests.""" import uuid from qiskit.circuit.library import Measure -from qiskit.providers import ProviderV1 from qiskit.providers.backend import BackendV2 from qiskit.providers.options import Options from qiskit.transpiler import Target @@ -23,14 +22,6 @@ from qiskit_experiments.test.utils import FakeJob -class FakeProvider(ProviderV1): - """Fake provider with no backends for testing""" - - def backends(self, name=None, **kwargs): - """List of available backends. Empty in this case""" - return [] - - class FakeBackend(BackendV2): """ Fake backend for test purposes only. @@ -38,13 +29,12 @@ class FakeBackend(BackendV2): def __init__( self, - provider=FakeProvider(), backend_name="fake_backend", num_qubits=1, max_experiments=100, ): self.simulator = True - super().__init__(provider=provider, name=backend_name) + super().__init__(name=backend_name) self._target = Target(num_qubits=num_qubits) # Add a measure for each qubit so a simple measure circuit works self.target.add_instruction(Measure()) diff --git a/qiskit_experiments/test/mock_iq_backend.py b/qiskit_experiments/test/mock_iq_backend.py index 905ab936a4..bf038653ee 100644 --- a/qiskit_experiments/test/mock_iq_backend.py +++ b/qiskit_experiments/test/mock_iq_backend.py @@ -18,13 +18,13 @@ from qiskit import QuantumCircuit from qiskit.circuit import Gate +from qiskit.circuit.library import get_standard_gate_name_mapping from qiskit.result import Result -from qiskit.providers import BackendV2, Provider, convert_to_target -from qiskit.providers.fake_provider import FakeOpenPulse2Q -from qiskit.qobj.utils import MeasLevel +from qiskit.providers import BackendV2, QubitProperties +from qiskit.transpiler import InstructionProperties, Target from qiskit_experiments.exceptions import QiskitError -from qiskit_experiments.framework import Options +from qiskit_experiments.framework import MeasLevel, Options, Provider from qiskit_experiments.test.utils import FakeJob from qiskit_experiments.test.mock_iq_helpers import ( MockIQExperimentHelper, @@ -33,8 +33,8 @@ ) -class FakeOpenPulse2QV2(BackendV2): - """BackendV2 conversion of qiskit.providers.fake_provider.FakeOpenPulse2Q""" +class BaseMockBackend(BackendV2): + """Simple two qubit BackendV2 implementation""" def __init__( self, @@ -47,25 +47,36 @@ def __init__( ): super().__init__(provider, name, description, online_date, backend_version, **fields) - backend_v1 = FakeOpenPulse2Q() - # convert_to_target requires the description attribute - backend_v1._configuration.description = "A fake test backend with pulse defaults" - - self._target = convert_to_target( - backend_v1.configuration(), - backend_v1.properties(), - backend_v1.defaults(), - add_delay=True, + self._target = Target( + num_qubits=2, + dt=1e-9, + qubit_properties=[ + QubitProperties(t1=70e-6, t2=80e-6), + QubitProperties(t1=85e-6, t2=90e-6), + ], ) - # See commented out defaults() method below - self._defaults = backend_v1._defaults - # This method is not defined in the base class as we would like to avoid - # relying on it as much as necessary. Individual tests should add it when - # necessary. - # def defaults(self): - # """Pulse defaults""" - # return self._defaults + gate_map = get_standard_gate_name_mapping() + inst_props = { + "cx": {"duration": 100e-9, "error": 3e-3}, + "id": {"duration": 30e-9, "error": 4e-4}, + "rz": {"duration": 0, "error": 0}, + "sx": {"duration": 30e-9, "error": 4e-4}, + "x": {"duration": 30e-9, "error": 4e-4}, + "reset": {"duration": None, "error": None}, + "delay": {"duration": None, "error": None}, + "measure": {"duration": 700e-9, "error": 1e-2}, + } + + for iname, iprops in inst_props.items(): + gate = gate_map[iname] + if gate.num_qubits == 2: + properties = {(0, 1): InstructionProperties(**iprops)} + else: + properties = { + (q,): InstructionProperties(**iprops) for q in range(self._target.num_qubits) + } + self._target.add_instruction(gate, properties=properties, name=iname) @property def max_circuits(self): @@ -76,7 +87,7 @@ def target(self): return self._target -class MockIQBackend(FakeOpenPulse2QV2): +class MockIQBackend(BaseMockBackend): """A mock backend for testing with IQ data.""" def __init__( @@ -629,7 +640,7 @@ def run(self, run_input: List[QuantumCircuit], **run_options) -> FakeJob: return FakeJob(self, Result.from_dict(result)) -class MockMultiStateBackend(FakeOpenPulse2QV2): +class MockMultiStateBackend(BaseMockBackend): """A mock backend for testing with multi-state IQ data. .. note:: diff --git a/qiskit_experiments/test/pulse_backend.py b/qiskit_experiments/test/pulse_backend.py index cbda46d788..91b2598429 100644 --- a/qiskit_experiments/test/pulse_backend.py +++ b/qiskit_experiments/test/pulse_backend.py @@ -27,13 +27,13 @@ from qiskit.exceptions import QiskitError from qiskit.providers import BackendV2, QubitProperties from qiskit.providers.options import Options -from qiskit.qobj.utils import MeasLevel, MeasReturnType from qiskit.quantum_info.states import DensityMatrix, Statevector from qiskit.result import Result, Counts from qiskit.transpiler import InstructionProperties, Target -from qiskit.utils import deprecate_func from qiskit_experiments.data_processing.discriminator import BaseDiscriminator +from qiskit_experiments.framework import MeasLevel, MeasReturnType +from qiskit_experiments.framework.deprecation import deprecate_func from qiskit_experiments.framework.package_deps import HAS_DYNAMICS, version_is_at_least from qiskit_experiments.test.utils import FakeJob diff --git a/qiskit_experiments/test/utils.py b/qiskit_experiments/test/utils.py index d76d1463cc..71515ac987 100644 --- a/qiskit_experiments/test/utils.py +++ b/qiskit_experiments/test/utils.py @@ -18,7 +18,7 @@ from qiskit.providers.job import JobV1 as Job from qiskit.providers.jobstatus import JobStatus -from qiskit.providers.backend import BackendV1 as Backend +from qiskit.providers.backend import BackendV2 as Backend from qiskit.result import Result diff --git a/releasenotes/notes/qiskit2-removals-ee52d83a3eaffd06.yaml b/releasenotes/notes/qiskit2-removals-ee52d83a3eaffd06.yaml new file mode 100644 index 0000000000..9aac41089f --- /dev/null +++ b/releasenotes/notes/qiskit2-removals-ee52d83a3eaffd06.yaml @@ -0,0 +1,39 @@ +--- +features: + - | + Classes :class:`.MeasLevel` and :class:`.MeasReturnType` were added to + enumerate the expected measurement level (kerneled or classified) and + return type (averaged or not). Previously, private classes of Qiskit were + inadvertently used for these purposes but they were removed in Qiskit + 2.0. +fixes: + - | + In preparation for Qiskit 2.0, references to ``BackendV1`` have been + guarded so that they do not cause import errors. ``BackendV1`` is still + supported for now, but support will be removed in a future release. + - | + :class:`.LocalReadouMitigator` now provides a more specific warning if it + is used with a backend without a ``properties()`` method or with a backend + that does not the required readout error properties. + - | + :class:`.ExperimentData` was updated to handle changes to + :class:`qiskit.result.Result` in Qiskit 2.0. The result header is now a + dict instead of a custom class. Qiskit Experiments uses this header to + store metadata about experiments' circuits. + - | + A performance issue in :class:`.QuantumVolume` was addressed. When Qiskit + Aer is installed, :class:`.QuantumVolume` uses + :class:`qiskit_aer.AerSimulator` to simulate the outcomes of the quantum + volume circuits to determine the heavy output probabilities. In its default + instantiation, ``AerSimulator`` regenerates its + :class:`qiskit.transpiler.Target` every time it is accessed and this + generation takes an appreciable amount of time. Because the target was + being accessed separately for every circuit, this overhead could accumulate + to over a minute in the standard 100 circuit experiment. This overhead was + reduced by processing all the circuits in one pass. +deprecations: + - | + Support for using ``BackendV1`` with Qiskit Experiments is deprecated. Some + code paths will generate warnings when using ``BackendV1`` but not all + cases are checked. Support is planned to be removed along all support for + Qiskit 1 in a future release near the end of support for Qiskit 1.4. diff --git a/test/base.py b/test/base.py index f373b7488f..b4c44e8c02 100644 --- a/test/base.py +++ b/test/base.py @@ -23,8 +23,6 @@ import fixtures import testtools import uncertainties -from qiskit.utils.deprecation import deprecate_func -import qiskit_aer.backends.aerbackend from qiskit_experiments.framework import ( ExperimentDecoder, @@ -32,15 +30,10 @@ ExperimentData, ) from qiskit_experiments.framework.experiment_data import ExperimentStatus +from qiskit_experiments.framework.deprecation import deprecate_func from .extended_equality import is_equivalent -# Workaround until https://github.com/Qiskit/qiskit-aer/pull/2142 is released -try: - del qiskit_aer.backends.aerbackend.AerBackend.get_translation_stage_plugin -except AttributeError: - pass - # Fail tests that take longer than this TEST_TIMEOUT = int(os.environ.get("TEST_TIMEOUT", 60)) # Use testtools by default as a (mostly) drop in replacement for @@ -111,56 +104,12 @@ def setUpClass(cls): # assertWarns or warnings.catch_warnings. warnings.filterwarnings("error", module="qiskit_experiments") warnings.filterwarnings("error", module=r"test\.") - # Ideally, changes introducing pending deprecations should include - # alternative code paths and not need to generate warnings in the - # tests but until this exception is necessary until the use of the - # deprecated ScatterTable methods are removed. - warnings.filterwarnings( - "default", - module="qiskit_experiments", - message=".*Curve data uses dataframe representation.*", - category=PendingDeprecationWarning, - ) - warnings.filterwarnings( - "default", - module="qiskit_experiments", - message=".*The curve data representation has been replaced by the `DataFrame` format.*", - category=PendingDeprecationWarning, - ) warnings.filterwarnings( "default", module="qiskit_experiments", message=".*Could not determine job completion time.*", category=UserWarning, ) - # All of the pulse related code is going to be removed, so we just - # ignore its warnings for now. - warnings.filterwarnings( - "default", - message=".*Due to the deprecation of Qiskit Pulse.*", - category=DeprecationWarning, - ) - - # Some functionality may be deprecated in Qiskit Experiments. If - # the deprecation warnings aren't filtered, the tests will fail as - # ``QiskitTestCase`` sets all warnings to be treated as an error by - # default. - # pylint: disable=invalid-name - allow_deprecationwarning_message = [ - ".*qiskit.providers.models.backendconfiguration.GateConfig.*", - ".*qiskit.qobj.pulse_qobj.PulseLibraryItem.*", - ".*qiskit.providers.models.backendconfiguration.UchannelLO.*", - ".*qiskit.providers.models.backendconfiguration.PulseBackendConfiguration.*", - ".*qiskit.qobj.pulse_qobj.PulseQobjInstruction.*", - ".*qiskit.providers.models.backendconfiguration.QasmBackendConfiguration.*", - ".*qiskit.qobj.common.QobjDictField.*", - ".*qiskit.providers.models.backendproperties.BackendProperties.*", - ".*qiskit.providers.fake_provider.fake_backend.FakeBackend.*", - ".*qiskit.providers.backend.BackendV1.*", - ".*The entire Qiskit Pulse package is being deprecated.*", - ] - for msg in allow_deprecationwarning_message: - warnings.filterwarnings("default", category=DeprecationWarning, message=msg) def assertExperimentDone( self, diff --git a/test/curve_analysis/test_baseclass.py b/test/curve_analysis/test_baseclass.py index 0e03b56a67..98e0e55406 100644 --- a/test/curve_analysis/test_baseclass.py +++ b/test/curve_analysis/test_baseclass.py @@ -22,7 +22,6 @@ from ddt import data, ddt, unpack from lmfit.models import ExpressionModel -from qiskit.qobj.utils import MeasLevel from qiskit_experiments.curve_analysis import CurveAnalysis, CompositeCurveAnalysis from qiskit_experiments.curve_analysis.curve_data import ( @@ -32,7 +31,12 @@ ) from qiskit_experiments.data_processing import DataProcessor, Probability from qiskit_experiments.exceptions import AnalysisError -from qiskit_experiments.framework import ExperimentData, AnalysisResultData, CompositeAnalysis +from qiskit_experiments.framework import ( + AnalysisResultData, + CompositeAnalysis, + ExperimentData, + MeasLevel, +) class CurveAnalysisTestCase(QiskitExperimentsTestCase): diff --git a/test/curve_analysis/test_curve_fitting.py b/test/curve_analysis/test_curve_fitting.py index d58382bb5c..6e31362f2b 100644 --- a/test/curve_analysis/test_curve_fitting.py +++ b/test/curve_analysis/test_curve_fitting.py @@ -80,7 +80,12 @@ def test_process_curve_data(self): """Test version string generation.""" thetas = thetas = np.linspace(0.5, 4 * np.pi - 0.5, 20) data = self.simulate_experiment_data(thetas) - xdata, ydata, _ = process_curve_data(data, data_processor=self.data_processor_p0) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message=".*The curve data representation has been replaced.*", + ) + xdata, ydata, _ = process_curve_data(data, data_processor=self.data_processor_p0) xdiff = thetas - xdata ydiff = self.objective0(xdata, 0.5) - ydata diff --git a/test/curve_analysis/test_oscillation_analysis.py b/test/curve_analysis/test_oscillation_analysis.py index 8ef979530e..baaf56ec6d 100644 --- a/test/curve_analysis/test_oscillation_analysis.py +++ b/test/curve_analysis/test_oscillation_analysis.py @@ -15,15 +15,14 @@ import numpy as np from qiskit import QuantumCircuit, transpile -from qiskit.qobj.utils import MeasLevel from qiskit_aer import AerSimulator -from qiskit_experiments.framework import ExperimentData +from qiskit_experiments.curve_analysis import ParameterRepr from qiskit_experiments.curve_analysis.standard_analysis.oscillation import OscillationAnalysis from qiskit_experiments.data_processing.data_processor import DataProcessor from qiskit_experiments.data_processing.nodes import Probability -from qiskit_experiments.curve_analysis import ParameterRepr +from qiskit_experiments.framework import ExperimentData, MeasLevel class TestOscillationAnalysis(QiskitExperimentsTestCase): diff --git a/test/data_processing/base_data_processor_test.py b/test/data_processing/base_data_processor_test.py index 6996fca4a6..7ffc0b1f85 100644 --- a/test/data_processing/base_data_processor_test.py +++ b/test/data_processing/base_data_processor_test.py @@ -14,11 +14,12 @@ from test.base import QiskitExperimentsTestCase from test.fake_experiment import FakeExperiment +import warnings from typing import Any, List +import qiskit.version from qiskit.result import Result -from qiskit.qobj.common import QobjExperimentHeader from qiskit.result.models import ExperimentResultData, ExperimentResult from qiskit_experiments.framework import ExperimentData @@ -38,10 +39,24 @@ def setUp(self): "success": True, } - self.header = QobjExperimentHeader( - memory_slots=2, - metadata={"experiment_type": "fake_test_experiment"}, - ) + if qiskit.version.get_version_info().partition(".")[0] in ("0", "1"): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message=".*QobjDictField.*", + category=DeprecationWarning, + ) + from qiskit.qobj.common import QobjExperimentHeader + + self.header = QobjExperimentHeader( + memory_slots=2, + metadata={"experiment_type": "fake_test_experiment"}, + ) + else: + self.header = { + "memory_slots": 2, + "metadata": {"experiment_type": "fake_test_experiment"}, + } def create_experiment_data(self, iq_data: List[Any], single_shot: bool = False): """Populate avg_iq_data to use it for testing. diff --git a/test/data_processing/test_mitigators.py b/test/data_processing/test_mitigators.py index b75f7fde12..07ad438391 100644 --- a/test/data_processing/test_mitigators.py +++ b/test/data_processing/test_mitigators.py @@ -24,7 +24,7 @@ from qiskit.quantum_info.operators.predicates import matrix_equal from qiskit.result import Counts from qiskit.result.utils import marginal_counts -from qiskit.providers.fake_provider import Fake5QV1 +from qiskit.providers.fake_provider import GenericBackendV2 from qiskit_experiments.data_processing import ( CorrelatedReadoutMitigator, @@ -38,13 +38,60 @@ ) +class Property: + """Class representing a single backend property with name and value""" + + def __init__(self, name, value): + self.name = name + self.value = value + + +class Properties: + """Class holding backend properties in the qubits attribute""" + + def __init__(self, probs): + self.probs = probs + self.qubits = [ + [ + Property("prob_meas1_prep0", q_probs[0]), + Property("prob_meas0_prep1", q_probs[1]), + ] + for q_probs in probs + ] + + +class MitigatorTestBackend(GenericBackendV2): + """Custom backend with a .properties().qubits with readout error values + + This backend's properties are formatted as qiskit-ibm-runtime is as of March 2025 + """ + + def __init__(self, num_qubits: int, probs: list[tuple[float, float]]): + self.probs = probs + super().__init__(num_qubits=num_qubits) + + def properties(self): + """Return backend properties""" + return Properties(self.probs) + + class TestReadoutMitigation(QiskitExperimentsTestCase): """Tests for correlated and local readout mitigation.""" - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.rng = np.random.default_rng(42) + def setUp(self): + super().setUp() + self.rng = np.random.default_rng(42) + + # Probabilities copied from Qiskit 1's qiskit.providers.fake_provider.Fake5QV1 + probs = [ + [0.049000000000000044, 0.0776], + [0.021399999999999975, 0.0408], + [0.09160000000000001, 0.1388], + [0.013, 0.04239999999999999], + [0.086, 0.49860000000000004], + ] + + self.backend = MitigatorTestBackend(num_qubits=5, probs=probs) @staticmethod def compare_results(res1, res2): @@ -70,13 +117,12 @@ def mitigators(assignment_matrices, qubits=None): mitigators = [CRM, LRM] return mitigators - @staticmethod - def simulate_circuit(circuit, assignment_matrix, num_qubits, shots=1024): + def simulate_circuit(self, circuit, assignment_matrix, num_qubits, shots=1024): """Simulates the given circuit under the given readout noise""" probs = Statevector.from_instruction(circuit).probabilities() noisy_probs = assignment_matrix @ probs labels = [bin(a)[2:].zfill(num_qubits) for a in range(2**num_qubits)] - results = TestReadoutMitigation.rng.choice(labels, size=shots, p=noisy_probs) + results = self.rng.choice(labels, size=shots, p=noisy_probs) return Counts(dict(Counter(results))) @staticmethod @@ -95,36 +141,26 @@ def first_qubit_h_3_circuit(): c.h(0) return (c, "first_qubit_h_3_circuit", 3) - @staticmethod - def assignment_matrices(): + def assignment_matrices(self): """A 3-qubit readout noise assignment matrices""" - return LocalReadoutMitigator(backend=Fake5QV1())._assignment_mats[0:3] + return LocalReadoutMitigator(backend=self.backend)._assignment_mats[0:3] - @staticmethod - def counts_data(circuit, assignment_matrices, shots=1024): + def counts_data(self, circuit, assignment_matrices, shots=1024): """Generates count data for the noisy and noiseless versions of the circuit simulation""" full_assignment_matrix = assignment_matrices[0] for m in assignment_matrices[1:]: full_assignment_matrix = np.kron(full_assignment_matrix, m) num_qubits = len(assignment_matrices) ideal_assignment_matrix = np.eye(2**num_qubits) - counts_ideal = TestReadoutMitigation.simulate_circuit( - circuit, ideal_assignment_matrix, num_qubits, shots - ) - counts_noise = TestReadoutMitigation.simulate_circuit( - circuit, full_assignment_matrix, num_qubits, shots - ) + counts_ideal = self.simulate_circuit(circuit, ideal_assignment_matrix, num_qubits, shots) + counts_noise = self.simulate_circuit(circuit, full_assignment_matrix, num_qubits, shots) probs_noise = {key: value / shots for key, value in counts_noise.items()} return counts_ideal, counts_noise, probs_noise def test_mitigation_improvement(self): """Test whether readout mitigation led to more accurate results""" shots = 1024 - with self.assertWarns(DeprecationWarning): - # TODO self.assignment_matrices calls LocalReadoutMitigator, - # which only supports BackendV1 at the moment: - # https://github.com/Qiskit/qiskit/issues/12832 - assignment_matrices = self.assignment_matrices() + assignment_matrices = self.assignment_matrices() mitigators = self.mitigators(assignment_matrices) circuit, circuit_name, num_qubits = self.ghz_3_circuit() counts_ideal, counts_noise, probs_noise = self.counts_data( @@ -160,8 +196,7 @@ def test_expectation_improvement(self): """Test whether readout mitigation led to more accurate results and that its standard deviation is increased""" shots = 1024 - with self.assertWarns(DeprecationWarning): - assignment_matrices = self.assignment_matrices() + assignment_matrices = self.assignment_matrices() mitigators = self.mitigators(assignment_matrices) num_qubits = len(assignment_matrices) diagonals = [] @@ -203,8 +238,7 @@ def test_expectation_improvement(self): def test_clbits_parameter(self): """Test whether the clbits parameter is handled correctly""" shots = 10000 - with self.assertWarns(DeprecationWarning): - assignment_matrices = self.assignment_matrices() + assignment_matrices = self.assignment_matrices() mitigators = self.mitigators(assignment_matrices) circuit, _, _ = self.first_qubit_h_3_circuit() counts_ideal, counts_noise, _ = self.counts_data(circuit, assignment_matrices, shots) @@ -239,8 +273,7 @@ def test_clbits_parameter(self): def test_qubits_parameter(self): """Test whether the qubits parameter is handled correctly""" shots = 10000 - with self.assertWarns(DeprecationWarning): - assignment_matrices = self.assignment_matrices() + assignment_matrices = self.assignment_matrices() mitigators = self.mitigators(assignment_matrices) circuit, _, _ = self.first_qubit_h_3_circuit() counts_ideal, counts_noise, _ = self.counts_data(circuit, assignment_matrices, shots) @@ -288,8 +321,7 @@ def test_qubits_parameter(self): def test_repeated_qubits_parameter(self): """Tests the order of mitigated qubits.""" shots = 10000 - with self.assertWarns(DeprecationWarning): - assignment_matrices = self.assignment_matrices() + assignment_matrices = self.assignment_matrices() mitigators = self.mitigators(assignment_matrices, qubits=[0, 1, 2]) circuit, _, _ = self.first_qubit_h_3_circuit() counts_ideal, counts_noise, _ = self.counts_data(circuit, assignment_matrices, shots) @@ -327,8 +359,7 @@ def test_qubits_subset_parameter(self): """Tests mitigation on a subset of the initial set of qubits.""" shots = 10000 - with self.assertWarns(DeprecationWarning): - assignment_matrices = self.assignment_matrices() + assignment_matrices = self.assignment_matrices() mitigators = self.mitigators(assignment_matrices, qubits=[2, 4, 6]) circuit, _, _ = self.first_qubit_h_3_circuit() counts_ideal, counts_noise, _ = self.counts_data(circuit, assignment_matrices, shots) @@ -373,20 +404,12 @@ def test_qubits_subset_parameter(self): def test_from_backend(self): """Test whether a local mitigator can be created directly from backend properties""" - with self.assertWarns(DeprecationWarning): - backend = Fake5QV1() - num_qubits = len(backend.properties().qubits) - probs = TestReadoutMitigation.rng.random((num_qubits, 2)) - for qubit_idx, qubit_prop in enumerate(backend.properties().qubits): - for prop in qubit_prop: - if prop.name == "prob_meas1_prep0": - prop.value = probs[qubit_idx][0] - if prop.name == "prob_meas0_prep1": - prop.value = probs[qubit_idx][1] - LRM_from_backend = LocalReadoutMitigator(backend=backend) + + LRM_from_backend = LocalReadoutMitigator(backend=self.backend) mats = [] - for qubit_idx in range(num_qubits): + probs = self.backend.probs + for qubit_idx in range(self.backend.num_qubits): mat = np.array( [ [1 - probs[qubit_idx][0], probs[qubit_idx][1]], @@ -432,8 +455,7 @@ def test_error_handling(self): def test_expectation_value_endian(self): """Test that endian for expval is little.""" - with self.assertWarns(DeprecationWarning): - assignment_matrices = self.assignment_matrices() + assignment_matrices = self.assignment_matrices() mitigators = self.mitigators(assignment_matrices) counts = Counts({"10": 3, "11": 24, "00": 74, "01": 923}) for mitigator in mitigators: @@ -454,16 +476,10 @@ def test_quasi_probabilities_shots_passing(self): quasi_dist = mitigator.quasi_probabilities(counts, shots=1025) self.assertEqual(quasi_dist.shots, 1025) - -class TestLocalReadoutMitigation(QiskitExperimentsTestCase): - """Tests specific to the local readout mitigator""" - - def test_assignment_matrix(self): + def test_local_readout_assignment_matrix(self): """Tests that the local mitigator generates the full assignment matrix correctly""" qubits = [7, 2, 3] - with self.assertWarns(DeprecationWarning): - backend = Fake5QV1() - assignment_matrices = LocalReadoutMitigator(backend=backend)._assignment_mats[0:3] + assignment_matrices = LocalReadoutMitigator(backend=self.backend)._assignment_mats[0:3] expected_assignment_matrix = np.kron( np.kron(assignment_matrices[2], assignment_matrices[1]), assignment_matrices[0] ) diff --git a/test/framework/test_mock_iq_backend.py b/test/framework/test_mock_iq_backend.py index 6c2f788cad..88b86032a3 100644 --- a/test/framework/test_mock_iq_backend.py +++ b/test/framework/test_mock_iq_backend.py @@ -17,7 +17,7 @@ from itertools import product import numpy as np from qiskit import QuantumCircuit -from qiskit.qobj.utils import MeasLevel, MeasReturnType +from qiskit_experiments.framework import MeasLevel, MeasReturnType from qiskit_experiments.test.mock_iq_backend import MockIQBackend from qiskit_experiments.test.mock_iq_helpers import MockIQExperimentHelper diff --git a/test/library/characterization/test_fine_amplitude.py b/test/library/characterization/test_fine_amplitude.py index d8f866a783..40bd17be02 100644 --- a/test/library/characterization/test_fine_amplitude.py +++ b/test/library/characterization/test_fine_amplitude.py @@ -42,8 +42,6 @@ def test_end_to_end_under_rotation(self, pi_ratio): error = -np.pi * pi_ratio backend = MockIQBackend(FineAmpHelper(error, np.pi, "x")) - backend.target.add_instruction(XGate(), properties={(0,): None}) - backend.target.add_instruction(SXGate(), properties={(0,): None}) expdata = amp_exp.run(backend) self.assertExperimentDone(expdata) @@ -63,8 +61,6 @@ def test_end_to_end_over_rotation(self, pi_ratio): error = np.pi * pi_ratio backend = MockIQBackend(FineAmpHelper(error, np.pi, "x")) - backend.target.add_instruction(XGate(), properties={(0,): None}) - backend.target.add_instruction(SXGate(), properties={(0,): None}) expdata = amp_exp.run(backend) self.assertExperimentDone(expdata) result = expdata.analysis_results("d_theta") diff --git a/test/library/characterization/test_fine_frequency.py b/test/library/characterization/test_fine_frequency.py index 9ddc9f00ac..3334677976 100644 --- a/test/library/characterization/test_fine_frequency.py +++ b/test/library/characterization/test_fine_frequency.py @@ -17,36 +17,27 @@ from ddt import ddt, data from qiskit_experiments.library import FineFrequency -from qiskit_experiments.framework import BackendData -from qiskit_experiments.test.mock_iq_backend import MockIQBackend -from qiskit_experiments.test.mock_iq_helpers import MockIQFineFreqHelper as FineFreqHelper +from qiskit_experiments.test import T2HahnBackend @ddt class TestFineFreqEndToEnd(QiskitExperimentsTestCase): """Test the fine freq experiment.""" - def setUp(self): - """Setup for the test.""" - super().setUp() - - self.sx_duration = 160 - @data(-0.5e6, -0.1e6, 0.1e6, 0.5e6) def test_end_to_end(self, freq_shift): """Test the experiment end to end.""" - exp_helper = FineFreqHelper(sx_duration=self.sx_duration, freq_shift=freq_shift) - backend = MockIQBackend(exp_helper) - exp_helper.dt = BackendData(backend).dt + backend = T2HahnBackend(frequency=freq_shift, dt=1e-9) + # Set delay to be 1% of a period of freqeuncy error + delay_dt = int(0.01 / abs(freq_shift) / backend.dt) - freq_exp = FineFrequency([0], 160, backend) + freq_exp = FineFrequency([0], delay_dt, backend) expdata = freq_exp.run(shots=100) self.assertExperimentDone(expdata) result = expdata.analysis_results("d_theta") d_theta = result.value.n - dt = BackendData(backend).dt - d_freq = d_theta / (2 * np.pi * self.sx_duration * dt) + d_freq = d_theta / (2 * np.pi * (delay_dt * backend.dt)) tol = 0.01e6 diff --git a/test/library/characterization/test_readout_angle.py b/test/library/characterization/test_readout_angle.py index be9b4057bc..b1e5f95bca 100644 --- a/test/library/characterization/test_readout_angle.py +++ b/test/library/characterization/test_readout_angle.py @@ -16,7 +16,7 @@ from test.base import QiskitExperimentsTestCase import numpy as np -from qiskit.qobj.utils import MeasLevel +from qiskit_experiments.framework import MeasLevel from qiskit_experiments.library import ReadoutAngle from qiskit_experiments.test.mock_iq_backend import MockIQBackend from qiskit_experiments.test.mock_iq_helpers import MockIQReadoutAngleHelper diff --git a/test/library/characterization/test_t1.py b/test/library/characterization/test_t1.py index 29c3ac2aca..d7da33de4d 100644 --- a/test/library/characterization/test_t1.py +++ b/test/library/characterization/test_t1.py @@ -17,12 +17,11 @@ import numpy as np from qiskit.circuit import Delay, Parameter from qiskit.circuit.library import CXGate, Measure, RXGate -from qiskit.qobj.utils import MeasLevel from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.transpiler import InstructionProperties, Target from qiskit_ibm_runtime.fake_provider import FakeAthensV2 from qiskit_experiments.test.noisy_delay_aer_simulator import NoisyDelayAerBackend -from qiskit_experiments.framework import ExperimentData, ParallelExperiment +from qiskit_experiments.framework import ExperimentData, MeasLevel, ParallelExperiment from qiskit_experiments.library import T1 from qiskit_experiments.library.characterization import T1Analysis, T1KerneledAnalysis from qiskit_experiments.test.mock_iq_backend import MockIQBackend, MockIQParallelBackend diff --git a/test/library/quantum_volume/qv_ideal_probabilities_qiskit_2_0.json b/test/library/quantum_volume/qv_ideal_probabilities_qiskit_2_0.json new file mode 100644 index 0000000000..f00bdc1034 --- /dev/null +++ b/test/library/quantum_volume/qv_ideal_probabilities_qiskit_2_0.json @@ -0,0 +1 @@ +[[0.4354534198676485, 0.22262891512793948, 0.10408983081803903, 0.008528240130548951, 0.013343796818990183, 0.12379653457767027, 0.0836079509124482, 0.008551311746715808], [0.07788043805758058, 0.2530303344204691, 0.058262742578192084, 0.06482442429506201, 0.3128253455993438, 0.11426100908573869, 0.09048658679511766, 0.02842911916849869], [0.2186719664893615, 0.3815922247599302, 0.000583843694713377, 0.12920911563666243, 0.0858198387152911, 0.06097582184031965, 0.0673984561093236, 0.0557487327543985], [0.003897068822837867, 0.0016095484703179488, 0.018829897322986747, 0.25219987203921546, 0.09522049447881334, 0.04652861779291838, 0.2874781744055805, 0.29423632666733174], [0.6229736498210389, 0.07847685679165327, 0.0, 0.0, 0.046834240043179785, 0.25171525334412953, 0.0, 0.0], [0.151149944913878, 0.1249651244583814, 0.1946794863797365, 0.1066019725137271, 0.08909116082079165, 0.00884228190584737, 0.14286412954005792, 0.18180589946758033], [0.17551340168478133, 0.03755399630414072, 0.25083249661274554, 0.13863511043235066, 0.048824430353276355, 0.042536698807117156, 0.008468791654733607, 0.29763507415085605], [0.027465028513495082, 0.03839194810323577, 0.1253280113115479, 0.33054254126671456, 0.05104576614516026, 0.21266097447467758, 0.13263351575345472, 0.08193221443171579], [0.04352382635168621, 0.07075317810174925, 0.3652000687337914, 0.23765921217061362, 0.011134250434853337, 0.01665523240437386, 0.12971657157544103, 0.1253576602274936], [0.373562389099596, 0.2653053261621785, 0.03172560521407652, 0.024564074822944322, 0.09445121921831463, 0.053343327915518254, 0.10809206464582552, 0.04895599292154718], [0.16674207926924434, 0.013619484681005937, 0.0922761510450282, 0.04529725529661201, 0.041846753872960996, 0.02453221246142707, 0.5889092312724002, 0.026776832101321476], [0.267586988288187, 0.36565616506047605, 0.0, 0.0, 0.22689159435360165, 0.1398652522977358, 0.0, 0.0], [0.0186995904784679, 0.12997192426309473, 0.17721607321420338, 0.048586421019253895, 0.01409331164574642, 0.43456029397774326, 0.006096818492149609, 0.17077556690934154], [0.25225456157179704, 0.0019066518146095036, 0.06050340774200541, 0.10312608082578481, 0.2831290929651657, 0.12413269298041205, 0.13848913529739962, 0.03645837680282536], [0.36617477405944787, 0.021560748638344195, 0.6115895573416229, 0.0006749199605860473, 0.0, 0.0, 0.0, 0.0], [0.04807485840414659, 0.12981320326584686, 0.21984114586795064, 0.09430255533974663, 0.016828422624588068, 0.36567010434282027, 0.052647618089929495, 0.0728220920649725], [0.10427286449119794, 0.07898578330295439, 0.06933208827268615, 0.12162877295526542, 0.15498965998385178, 0.07423177857764378, 0.1379282473571835, 0.25863080505921904], [0.10741335631181385, 0.15462884700946977, 0.1952802011611909, 0.21643706118042194, 0.028393707781655123, 0.15392469880069715, 0.020345141379612724, 0.12357698637513952], [0.22808509801013765, 0.052647076972947515, 0.0715107180006456, 0.03262956906864945, 0.01721859076861818, 0.04953075016771909, 0.0908571295934774, 0.4575210674178066], [0.1352085352725553, 0.14307526572917453, 0.03778661347377269, 0.031309737384210494, 0.1057246324597863, 0.2614179867904946, 0.20733802596334325, 0.07813920292666376]] \ No newline at end of file diff --git a/test/library/quantum_volume/test_qv.py b/test/library/quantum_volume/test_qv.py index 7d6d0e24a1..b7aa0e4ffd 100644 --- a/test/library/quantum_volume/test_qv.py +++ b/test/library/quantum_volume/test_qv.py @@ -89,9 +89,11 @@ def test_qv_ideal_probabilities(self): ) # compare to pre-calculated probabilities dir_name = os.path.dirname(os.path.abspath(__file__)) - if version_is_at_least("qiskit", 1.3): + if version_is_at_least("qiskit", "2.0"): + probabilities_json_file = "qv_ideal_probabilities_qiskit_2_0.json" + elif version_is_at_least("qiskit", "1.3"): probabilities_json_file = "qv_ideal_probabilities_qiskit_1_3.json" - elif version_is_at_least("qiskit", 1.1): + elif version_is_at_least("qiskit", "1.1"): probabilities_json_file = "qv_ideal_probabilities_qiskit_1_1.json" else: probabilities_json_file = "qv_ideal_probabilities.json"