diff --git a/qiskit/algorithms/amplitude_amplifiers/grover.py b/qiskit/algorithms/amplitude_amplifiers/grover.py index f8e85918a136..36bba45360c5 100644 --- a/qiskit/algorithms/amplitude_amplifiers/grover.py +++ b/qiskit/algorithms/amplitude_amplifiers/grover.py @@ -24,7 +24,7 @@ from qiskit import ClassicalRegister, QuantumCircuit from qiskit.algorithms.exceptions import AlgorithmError from qiskit.primitives import BaseSampler -from qiskit.providers import Backend +from qiskit.providers import Backend, is_backend from qiskit.quantum_info import partial_trace, Statevector from qiskit.utils import QuantumInstance, algorithm_globals from qiskit.utils.deprecation import deprecate_arg, deprecate_func @@ -221,7 +221,7 @@ def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: Args: quantum_instance: The quantum instance used to run this algorithm. """ - if isinstance(quantum_instance, Backend): + if is_backend(quantum_instance): quantum_instance = QuantumInstance(quantum_instance) self._quantum_instance = quantum_instance diff --git a/qiskit/algorithms/amplitude_estimators/ae.py b/qiskit/algorithms/amplitude_estimators/ae.py index d9aced5d9fe2..abd556e40981 100644 --- a/qiskit/algorithms/amplitude_estimators/ae.py +++ b/qiskit/algorithms/amplitude_estimators/ae.py @@ -20,7 +20,7 @@ from scipy.optimize import bisect from qiskit import QuantumCircuit, ClassicalRegister -from qiskit.providers import Backend +from qiskit.providers import Backend, is_backend from qiskit.primitives import BaseSampler from qiskit.utils import QuantumInstance from qiskit.utils.deprecation import deprecate_arg, deprecate_func @@ -157,7 +157,7 @@ def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: Args: quantum_instance: The quantum instance used to run this algorithm. """ - if isinstance(quantum_instance, Backend): + if is_backend(quantum_instance): quantum_instance = QuantumInstance(quantum_instance) self._quantum_instance = quantum_instance diff --git a/qiskit/algorithms/amplitude_estimators/fae.py b/qiskit/algorithms/amplitude_estimators/fae.py index 08fedaffa34a..8b9b2f0de341 100644 --- a/qiskit/algorithms/amplitude_estimators/fae.py +++ b/qiskit/algorithms/amplitude_estimators/fae.py @@ -17,7 +17,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit, ClassicalRegister -from qiskit.providers import Backend +from qiskit.providers import Backend, is_backend from qiskit.primitives import BaseSampler from qiskit.utils import QuantumInstance from qiskit.utils.deprecation import deprecate_arg, deprecate_func @@ -135,7 +135,7 @@ def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: Args: quantum_instance: The quantum instance used to run this algorithm. """ - if isinstance(quantum_instance, Backend): + if is_backend(quantum_instance): quantum_instance = QuantumInstance(quantum_instance) self._quantum_instance = quantum_instance diff --git a/qiskit/algorithms/amplitude_estimators/iae.py b/qiskit/algorithms/amplitude_estimators/iae.py index e420b76e6a4e..d4197d82428d 100644 --- a/qiskit/algorithms/amplitude_estimators/iae.py +++ b/qiskit/algorithms/amplitude_estimators/iae.py @@ -19,7 +19,7 @@ from scipy.stats import beta from qiskit import ClassicalRegister, QuantumCircuit -from qiskit.providers import Backend +from qiskit.providers import Backend, is_backend from qiskit.primitives import BaseSampler from qiskit.utils import QuantumInstance from qiskit.utils.deprecation import deprecate_arg, deprecate_func @@ -158,7 +158,7 @@ def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: Args: quantum_instance: The quantum instance used to run this algorithm. """ - if isinstance(quantum_instance, Backend): + if is_backend(quantum_instance): quantum_instance = QuantumInstance(quantum_instance) self._quantum_instance = quantum_instance diff --git a/qiskit/algorithms/amplitude_estimators/mlae.py b/qiskit/algorithms/amplitude_estimators/mlae.py index aa9f600b700e..21758580806a 100644 --- a/qiskit/algorithms/amplitude_estimators/mlae.py +++ b/qiskit/algorithms/amplitude_estimators/mlae.py @@ -21,7 +21,7 @@ from scipy.optimize import brute from scipy.stats import norm, chi2 -from qiskit.providers import Backend +from qiskit.providers import Backend, is_backend from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit from qiskit.utils import QuantumInstance from qiskit.primitives import BaseSampler @@ -162,7 +162,7 @@ def quantum_instance(self, quantum_instance: QuantumInstance | Backend) -> None: Args: quantum_instance: The quantum instance used to run this algorithm. """ - if isinstance(quantum_instance, Backend): + if is_backend(quantum_instance): quantum_instance = QuantumInstance(quantum_instance) self._quantum_instance = quantum_instance diff --git a/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py b/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py index 4c4af495e6ba..37822358969a 100644 --- a/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py +++ b/qiskit/algorithms/evolvers/trotterization/trotter_qrte.py @@ -31,7 +31,7 @@ OperatorBase, ) from qiskit.circuit.library import PauliEvolutionGate -from qiskit.providers import Backend +from qiskit.providers import Backend, is_backend from qiskit.synthesis import ProductFormula, LieTrotter from qiskit.utils import QuantumInstance from qiskit.utils.deprecation import deprecate_func @@ -125,7 +125,7 @@ def quantum_instance(self, quantum_instance: QuantumInstance | Backend | None) - Args: quantum_instance: The quantum instance used to run this algorithm. """ - if isinstance(quantum_instance, Backend): + if is_backend(quantum_instance): quantum_instance = QuantumInstance(quantum_instance) self._circuit_sampler = None diff --git a/qiskit/algorithms/phase_estimators/ipe.py b/qiskit/algorithms/phase_estimators/ipe.py index e8e583027a92..e0e2f0204545 100644 --- a/qiskit/algorithms/phase_estimators/ipe.py +++ b/qiskit/algorithms/phase_estimators/ipe.py @@ -20,7 +20,7 @@ import qiskit from qiskit.circuit import QuantumCircuit, QuantumRegister from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.providers import Backend +from qiskit.providers import Backend, is_backend from qiskit.utils import QuantumInstance from qiskit.utils.deprecation import deprecate_arg from qiskit.algorithms.exceptions import AlgorithmError @@ -68,7 +68,7 @@ def __init__( raise AlgorithmError( "Neither a sampler nor a quantum instance was provided. Please provide one of them." ) - if isinstance(quantum_instance, Backend): + if is_backend(quantum_instance): quantum_instance = QuantumInstance(quantum_instance) self._quantum_instance = quantum_instance if num_iterations <= 0: diff --git a/qiskit/algorithms/phase_estimators/phase_estimation.py b/qiskit/algorithms/phase_estimators/phase_estimation.py index a3bc1d59c60d..9f0cc311640c 100644 --- a/qiskit/algorithms/phase_estimators/phase_estimation.py +++ b/qiskit/algorithms/phase_estimators/phase_estimation.py @@ -21,7 +21,7 @@ import qiskit from qiskit import circuit from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.providers import Backend +from qiskit.providers import Backend, is_backend from qiskit.utils import QuantumInstance from qiskit.utils.deprecation import deprecate_arg from qiskit.result import Result @@ -114,7 +114,7 @@ def __init__( if num_evaluation_qubits is not None: self._num_evaluation_qubits = num_evaluation_qubits - if isinstance(quantum_instance, Backend): + if is_backend(quantum_instance): quantum_instance = QuantumInstance(quantum_instance) self._quantum_instance = quantum_instance self._sampler = sampler diff --git a/qiskit/execute_function.py b/qiskit/execute_function.py index e79399064e34..e4f84b8ec1c1 100644 --- a/qiskit/execute_function.py +++ b/qiskit/execute_function.py @@ -23,7 +23,7 @@ from time import time from qiskit.compiler import transpile, schedule -from qiskit.providers.backend import Backend +from qiskit.providers.backend import is_backend from qiskit.pulse import Schedule, ScheduleBlock from qiskit.exceptions import QiskitError from qiskit.utils.deprecation import deprecate_arg @@ -319,7 +319,7 @@ def execute( method=scheduling_method, ) - if isinstance(backend, Backend): + if is_backend(backend): start_time = time() run_kwargs = { "shots": shots, diff --git a/qiskit/opflow/converters/circuit_sampler.py b/qiskit/opflow/converters/circuit_sampler.py index e8e5938617e7..53ab11e22c06 100644 --- a/qiskit/opflow/converters/circuit_sampler.py +++ b/qiskit/opflow/converters/circuit_sampler.py @@ -29,7 +29,7 @@ from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn from qiskit.opflow.state_fns.dict_state_fn import DictStateFn from qiskit.opflow.state_fns.state_fn import StateFn -from qiskit.providers import Backend +from qiskit.providers import Backend, is_backend from qiskit.utils.backend_utils import is_aer_provider, is_statevector_backend from qiskit.utils.quantum_instance import QuantumInstance from qiskit.utils.deprecation import deprecate_func @@ -140,7 +140,7 @@ def quantum_instance(self, quantum_instance: Union[QuantumInstance, Backend]) -> Raises: ValueError: statevector or param_qobj are True when not supported by backend. """ - if isinstance(quantum_instance, Backend): + if is_backend(quantum_instance): quantum_instance = QuantumInstance(quantum_instance) self._quantum_instance = quantum_instance self._check_quantum_instance_and_modes_consistent() diff --git a/qiskit/providers/__init__.py b/qiskit/providers/__init__.py index f1f6d993f8e7..545f6ac14ae4 100644 --- a/qiskit/providers/__init__.py +++ b/qiskit/providers/__init__.py @@ -760,6 +760,7 @@ def status(self): from qiskit.providers.backend import Backend from qiskit.providers.backend import BackendV1 from qiskit.providers.backend import BackendV2 +from qiskit.providers.backend import is_backend_v1, is_backend_v2, is_backend from qiskit.providers.backend import QubitProperties from qiskit.providers.backend_compat import BackendV2Converter from qiskit.providers.backend_compat import convert_to_target diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index d61810f765fd..db1916aa2f87 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -15,29 +15,19 @@ """Backend abstract interface for providers.""" -from abc import ABC -from abc import abstractmethod import datetime -from typing import List, Union, Iterable, Tuple +import typing +from abc import ABC, abstractmethod +from typing import Any, Iterable, List, Tuple, Union -from qiskit.providers.provider import Provider -from qiskit.providers.models.backendstatus import BackendStatus -from qiskit.circuit.gate import Instruction - - -class Backend: - """Base common type for all versioned Backend abstract classes. +from typing_extensions import TypeAlias, TypeGuard - Note this class should not be inherited from directly, it is intended - to be used for type checking. When implementing a provider you should use - the versioned abstract classes as the parent class and not this class - directly. - """ - - version = 0 +from qiskit.circuit.gate import Instruction +from qiskit.providers.models.backendstatus import BackendStatus +from qiskit.providers.provider import Provider -class BackendV1(Backend, ABC): +class BackendV1(ABC): """Abstract class for Backends This abstract class is to be used for all Backend objects created by a @@ -265,7 +255,7 @@ def __repr__(self): return f"QubitProperties(t1={self.t1}, t2={self.t2}, " f"frequency={self.frequency})" -class BackendV2(Backend, ABC): +class BackendV2(ABC): """Abstract class for Backends This abstract class is to be used for all Backend objects created by a @@ -635,3 +625,27 @@ class can handle either situation. Job: The job object for the run """ pass + + +Backend: TypeAlias = Union[BackendV1, BackendV2] +"""Type that represents any backend type version. + +Use this type for functions that take a backend in any version. +The :func:`is_backend_v1` and :func:`is_backend_v2` functions can +be used to narrow the type appropriately. +""" + + +def is_backend(obj: Any) -> TypeGuard[Backend]: + """Type guard that narrows an object to the Backend union type.""" + return isinstance(obj, typing.get_args(Backend)) + + +def is_backend_v1(obj: Backend) -> TypeGuard[BackendV1]: + """Type guard that narrows a backend instance to the BackendV1 type.""" + return isinstance(obj, BackendV1) + + +def is_backend_v2(obj: Backend) -> TypeGuard[BackendV2]: + """Type guard that narrows a backend instance to the BackendV2 type.""" + return isinstance(obj, BackendV2) diff --git a/qiskit/utils/quantum_instance.py b/qiskit/utils/quantum_instance.py index a4bbb76e1b00..12a9ca0c870a 100644 --- a/qiskit/utils/quantum_instance.py +++ b/qiskit/utils/quantum_instance.py @@ -247,9 +247,9 @@ def __init__( # if the shots are none, try to get them from the backend if shots is None: - from qiskit.providers.backend import Backend # pylint: disable=cyclic-import + from qiskit.providers.backend import is_backend # pylint: disable=cyclic-import - if isinstance(backend, Backend): + if is_backend(backend): if hasattr(backend, "options"): # should always be true for V1 backend_shots = backend.options.get("shots", 1024) if shots != backend_shots: diff --git a/releasenotes/notes/backend-base-as-type-union-7a88be041b7fd628.yaml b/releasenotes/notes/backend-base-as-type-union-7a88be041b7fd628.yaml new file mode 100644 index 000000000000..f7c20953dfbf --- /dev/null +++ b/releasenotes/notes/backend-base-as-type-union-7a88be041b7fd628.yaml @@ -0,0 +1,21 @@ +--- +features: + - | + Define the :class:`qiskit.providers.Backend` type as a union of all + existing backend versions types instead of an empty base class. + Functions that take a backend can handle the corresponding argument in a + uniform and typed manner as: + + ```python + from qiskit.providers import Backend, BackendV2, BackendV2Converter, is_backend_v2 + from typing_extensions import backend_v2 + + def func(backend: Backend) -> None: + if is_backend_v1(backend): + backend_v2 = BackendV2Converter(backend) + else: + backend_v2 = backend + + assert_type(backend_v2, BackendV2) + print(backend_v2.name) + ```