diff --git a/qiskit/compiler/assemble.py b/qiskit/compiler/assemble.py index 9e4d6434d10d..52b1db59f1a5 100644 --- a/qiskit/compiler/assemble.py +++ b/qiskit/compiler/assemble.py @@ -26,6 +26,7 @@ from qiskit.qobj.utils import MeasLevel, MeasReturnType from qiskit.validation.jsonschema import SchemaValidationError from qiskit.providers import BaseBackend +from qiskit.providers.backend import Backend from qiskit.pulse.channels import PulseChannel from qiskit.pulse import Schedule @@ -39,7 +40,7 @@ def _log_assembly_time(start_time, end_time): # TODO: parallelize over the experiments (serialize each separately, then add global header/config) def assemble(experiments: Union[QuantumCircuit, List[QuantumCircuit], Schedule, List[Schedule]], - backend: Optional[BaseBackend] = None, + backend: Optional[Union[Backend, BaseBackend]] = None, qobj_id: Optional[str] = None, qobj_header: Optional[Union[QobjHeader, Dict]] = None, shots: Optional[int] = None, memory: Optional[bool] = False, diff --git a/qiskit/compiler/schedule.py b/qiskit/compiler/schedule.py index fde538413d50..194583ae4c36 100644 --- a/qiskit/compiler/schedule.py +++ b/qiskit/compiler/schedule.py @@ -23,6 +23,7 @@ from qiskit.exceptions import QiskitError from qiskit.pulse import InstructionScheduleMap, Schedule from qiskit.providers import BaseBackend +from qiskit.providers.backend import Backend from qiskit.scheduler import ScheduleConfig from qiskit.scheduler.schedule_circuit import schedule_circuit @@ -35,7 +36,7 @@ def _log_schedule_time(start_time, end_time): def schedule(circuits: Union[QuantumCircuit, List[QuantumCircuit]], - backend: Optional[BaseBackend] = None, + backend: Optional[Union[Backend, BaseBackend]] = None, inst_map: Optional[InstructionScheduleMap] = None, meas_map: Optional[List[List[int]]] = None, dt: Optional[float] = None, diff --git a/qiskit/compiler/sequence.py b/qiskit/compiler/sequence.py index 2ac45d5b6944..0942cdfced2a 100644 --- a/qiskit/compiler/sequence.py +++ b/qiskit/compiler/sequence.py @@ -18,13 +18,14 @@ from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.exceptions import QiskitError from qiskit.providers import BaseBackend +from qiskit.providers.backend import Backend from qiskit.pulse import InstructionScheduleMap, Schedule from qiskit.scheduler import ScheduleConfig from qiskit.scheduler.sequence import sequence as _sequence def sequence(scheduled_circuits: Union[QuantumCircuit, List[QuantumCircuit]], - backend: Optional[BaseBackend] = None, + backend: Optional[Union[Backend, BaseBackend]] = None, inst_map: Optional[InstructionScheduleMap] = None, meas_map: Optional[List[List[int]]] = None, dt: Optional[float] = None) -> Union[Schedule, List[Schedule]]: diff --git a/qiskit/compiler/transpile.py b/qiskit/compiler/transpile.py index 59bdc2edd844..058e38db0747 100644 --- a/qiskit/compiler/transpile.py +++ b/qiskit/compiler/transpile.py @@ -22,6 +22,7 @@ from qiskit.converters import isinstanceint, isinstancelist, dag_to_circuit, circuit_to_dag from qiskit.dagcircuit import DAGCircuit from qiskit.providers import BaseBackend +from qiskit.providers.backend import Backend from qiskit.providers.models import BackendProperties from qiskit.providers.models.backendproperties import Gate from qiskit.pulse import Schedule @@ -41,7 +42,7 @@ def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]], - backend: Optional[BaseBackend] = None, + backend: Optional[Union[Backend, BaseBackend]] = None, basis_gates: Optional[List[str]] = None, coupling_map: Optional[Union[CouplingMap, List[List[int]]]] = None, backend_properties: Optional[BackendProperties] = None, diff --git a/qiskit/execute.py b/qiskit/execute.py index fc26a0e84c8b..4db71bffe5a1 100644 --- a/qiskit/execute.py +++ b/qiskit/execute.py @@ -23,6 +23,8 @@ from time import time from qiskit.circuit import QuantumCircuit from qiskit.compiler import transpile, assemble, schedule +from qiskit.providers import BaseBackend +from qiskit.providers.backend import Backend from qiskit.qobj.utils import MeasLevel, MeasReturnType from qiskit.pulse import Schedule from qiskit.exceptions import QiskitError @@ -58,7 +60,7 @@ def execute(experiments, backend, experiments (QuantumCircuit or list[QuantumCircuit] or Schedule or list[Schedule]): Circuit(s) or pulse schedule(s) to execute - backend (BaseBackend): + backend (BaseBackend or Backend): Backend to execute circuits on. Transpiler options are automatically grabbed from backend.configuration() and backend.properties(). @@ -272,33 +274,56 @@ def execute(experiments, backend, meas_map=meas_map, method=scheduling_method) - # assembling the circuits into a qobj to be run on the backend - qobj = assemble(experiments, - qobj_id=qobj_id, - qobj_header=qobj_header, - shots=shots, - memory=memory, - max_credits=max_credits, - seed_simulator=seed_simulator, - default_qubit_los=default_qubit_los, - default_meas_los=default_meas_los, - schedule_los=schedule_los, - meas_level=meas_level, - meas_return=meas_return, - memory_slots=memory_slots, - memory_slot_size=memory_slot_size, - rep_time=rep_time, - rep_delay=rep_delay, - parameter_binds=parameter_binds, - backend=backend, - init_qubits=init_qubits, - **run_config) - - # executing the circuits on the backend and returning the job - start_time = time() - job = backend.run(qobj, **run_config) - end_time = time() - _log_submission_time(start_time, end_time) + if isinstance(backend, BaseBackend): + # assembling the circuits into a qobj to be run on the backend + qobj = assemble(experiments, + qobj_id=qobj_id, + qobj_header=qobj_header, + shots=shots, + memory=memory, + max_credits=max_credits, + seed_simulator=seed_simulator, + default_qubit_los=default_qubit_los, + default_meas_los=default_meas_los, + schedule_los=schedule_los, + meas_level=meas_level, + meas_return=meas_return, + memory_slots=memory_slots, + memory_slot_size=memory_slot_size, + rep_time=rep_time, + rep_delay=rep_delay, + parameter_binds=parameter_binds, + backend=backend, + init_qubits=init_qubits, + **run_config) + + # executing the circuits on the backend and returning the job + start_time = time() + job = backend.run(qobj, **run_config) + end_time = time() + _log_submission_time(start_time, end_time) + elif isinstance(backend, Backend): + start_time = time() + job = backend.run(experiments, + shots=shots, + memory=memory, + seed_simulator=seed_simulator, + default_qubit_los=default_qubit_los, + default_meas_los=default_meas_los, + schedule_los=schedule_los, + meas_level=meas_level, + meas_return=meas_return, + memory_slots=memory_slots, + memory_slot_size=memory_slot_size, + rep_time=rep_time, + rep_delay=rep_delay, + parameter_binds=parameter_binds, + init_qubits=init_qubits, + **run_config) + end_time = time() + _log_submission_time(start_time, end_time) + else: + raise QiskitError("Invalid backend type %s" % type(backend)) return job diff --git a/qiskit/providers/__init__.py b/qiskit/providers/__init__.py index fd53fbcbe3e3..643474f76e84 100644 --- a/qiskit/providers/__init__.py +++ b/qiskit/providers/__init__.py @@ -11,9 +11,109 @@ # that they have been altered from the originals. """ -====================================== -Base Objects (:mod:`qiskit.providers`) -====================================== +================================================ +Providers Interface (:mod:`qiskit.providers`) +================================================ + +.. currentmodule:: qiskit.providers + +This module contains the classes used to build external providers for Terra. A +provider is anything that provides an external service to Terra. The typical +example of this is a Backend provider which provides +:class:`~qiskit.providers.Backend` objects which can be used for executing +:class:`~qiskit.circuits.QuantumCircuit` and/or :class:`~qiskit.pulse.Schedule` +objects. This module contains the abstract classes which are used to define the +interface between a provider and terra. + +Version Support +=============== + +Each providers interface abstract class is individually versioned. When we +need to make a change to an interface a new abstract class will be created to +define the new interface. These interface changes are not guaranteed to be +backwards compatible between versions. + +Version Changes +---------------- + +Each minor version release of qiskit-terra **may** increment the version of any +providers interface a single version number. It will be an aggreagate of all +the interface changes for that release on that interface. + +Version Support Policy +---------------------- + +To enable providers to have time to adjust to changes in this interface +Terra will support support multiple versions of each class at once. Given the +nature of one version per release the version deprecation policy is a bit +more conservative than the standard deprecation policy. Terra will support a +provider interface version for a minimum of 3 minor releases or the first +release after 6 months from the release that introduced a version, whichever is +longer, prior to a potential deprecation. After that the standard deprecation +policy will apply to that interface version. This will give providers and users +sufficient time to adapt to potential breaking changes in the interface. So for +example lets say in 0.19.0 ``BackendV2`` is introduced and in the 3 months after +the release of 0.19.0 we release 0.20.0, 0.21.0, and 0.22.0, then 7 months after +0.19.0 we release 0.23.0. In 0.23.0 we can deprecate BackendV2, and it needs to +still be supported and can't be removed until the deprecation policy completes. + +It's worth pointing out that Terra's version support policy doesn't mean +providers themselves will have the same support story, they can (and arguably +should) update to newer versions as soon as they can, the support window is +just for Terra's supported versions. Part of this lengthy window prior to +deprecation is to give providers enough time to do their own deprecation of a +potential end user impacting change in a user facing part of the interface +prior to bumping their version. For example, lets say we changed the signature +to `Backend.run()` in ``BackendV34`` in a backwards incompatible way, before +Aer could update its ``AerBackend`` class to use version 34 they'd need to +deprecate the old signature prior to switching over. The changeover for Aer is +not guaranteed to be lockstep with Terra so we need to ensure there is a +sufficient amount of time for Aer to complete it's deprecation cycle prior to +removing version 33 (ie making version 34 mandatory/the minimum version). + + +Abstract Classes +================ + +Provider +-------- + +.. autosummary:: + :toctree: ../stubs/ + + Provider + ProviderV1 + +Backend +------- + +.. autosummary:: + :toctree: ../stubs/ + + Backend + BackendV1 + +Options +------- + +.. autosummary:: + :toctree: ../stubs/ + + Options + +Job +--- + +.. autosummary:: + :toctree: ../stubs/ + + Job + JobV1 + + +================================================================ +Legacy Provider Interface Base Objects (:mod:`qiskit.providers`) +================================================================ .. currentmodule:: qiskit.providers @@ -49,12 +149,21 @@ import pkgutil -from .basebackend import BaseBackend -from .baseprovider import BaseProvider -from .basejob import BaseJob -from .exceptions import (JobError, JobTimeoutError, QiskitBackendNotFoundError, - BackendPropertyError, BackendConfigurationError) -from .jobstatus import JobStatus +# Providers interface +from qiskit.providers.provider import Provider +from qiskit.providers.provider import ProviderV1 +from qiskit.providers.backend import Backend +from qiskit.providers.backend import BackendV1 +from qiskit.providers.options import Options +from qiskit.providers.job import Job +from qiskit.providers.job import JobV1 +# Legacy providers interface +from qiskit.providers.basebackend import BaseBackend +from qiskit.providers.baseprovider import BaseProvider +from qiskit.providers.basejob import BaseJob +from qiskit.providers.exceptions import (JobError, JobTimeoutError, QiskitBackendNotFoundError, + BackendPropertyError, BackendConfigurationError) +from qiskit.providers.jobstatus import JobStatus # Allow extending this namespace. diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py new file mode 100644 index 000000000000..e51847ecf865 --- /dev/null +++ b/qiskit/providers/backend.py @@ -0,0 +1,220 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""Backend abstract interface for providers.""" + + +from abc import ABC +from abc import abstractmethod + +from qiskit.providers.models.backendstatus import BackendStatus + + +class Backend: + """Base common type for all versioned Backend abstract classes. + + 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 + + +class BackendV1(Backend, ABC): + """Abstract class for Backends + + This abstract class is to be used for all Backend objects created by a + provider. There are several classes of information contained in a Backend. + The first are the attributes of the class itself. These should be used to + defined the immutable characteristics of the backend. The ``options`` + attribute of the backend is used to contain the dynamic user configurable + options of the backend. It should be used more for runtime options + that configure how the backend is used. For example, something like a + ``shots`` field for a backend that runs experiments which would contain an + int for how many shots to execute. The ``properties`` attribute is + optionally defined :class:`~qiskit.providers.models.BackendProperties` + object and is used to return measured properties, or properties + of a backend that may change over time. The simplest example of this would + be a version string, which will change as a backend is updated, but also + could be something like noise parameters for backends that run experiments. + + This first version of the Backend abstract class is written to be mostly + backwards compatible with the legacy providers interface. This includes reusing + the model objects :class:`~qiskit.providers.models.BackendProperties` and + :class:`~qiskit.providers.models.BackendConfiguration`. This was done to + ease the transition for users and provider maintainers to the new versioned providers. + Expect, future versions of this abstract class to change the data model and + interface. + """ + version = 1 + + def __init__(self, configuration, provider=None, **fields): + """Initialize a backend class + + Args: + configuration (BackendConfiguration): A backend configuration + object for the backend object. + provider (qiskit.providers.Provider): Optionally, the provider + object that this Backend comes from. + fields: kwargs for the values to use to override the default + options. + Raises: + AttributeError: if input field not a valid options + """ + self._configuration = configuration + self._options = self._default_options() + self._provider = provider + if fields: + for field in fields: + if field not in self._options.data: + raise AttributeError( + "Options field %s is not valid for this backend" % field) + self._options.update_config(**fields) + + @classmethod + @abstractmethod + def _default_options(cls): + """Return the default options + + This method will return a :class:`qiskit.providers.Options` + subclass object that will be used for the default options. These + should be the default parameters to use for the options of the + backend. + + Returns: + qiskit.providers.Options: A options object with + default values set + """ + pass + + def set_options(self, **fields): + """Set the options fields for the backend + + This method is used to update the options of a backend. If + you need to change any of the options prior to running just + pass in the kwarg with the new value for the options. + + Args: + fields: The fields to update the options + + Raises: + AttributeError: If the field passed in is not part of the + options + """ + for field in fields: + if not hasattr(field, self._options): + raise AttributeError( + "Options field %s is not valid for this " + "backend" % field) + self._options.update_options(**fields) + + def configuration(self): + """Return the backend configuration. + + Returns: + BackendConfiguration: the configuration for the backend. + """ + return self._configuration + + def properties(self): + """Return the backend properties. + + Returns: + BackendProperties: the configuration for the backend. If the backend + does not support properties, it returns ``None``. + """ + return None + + def provider(self): + """Return the backend Provider. + + Returns: + Provider: the Provider responsible for the backend. + """ + return self._provider + + def status(self): + """Return the backend status. + + Returns: + BackendStatus: the status of the backend. + """ + return BackendStatus(backend_name=self.name(), + backend_version='1', + operational=True, + pending_jobs=0, + status_msg='') + + def name(self): + """Return the backend name. + + Returns: + str: the name of the backend. + """ + return self._configuration.backend_name + + def __str__(self): + return self.name() + + def __repr__(self): + """Official string representation of a Backend. + + Note that, by Qiskit convention, it is consciously *not* a fully valid + Python expression. Subclasses should provide 'a string of the form + <...some useful description...>'. [0] + + [0] https://docs.python.org/3/reference/datamodel.html#object.__repr__ + """ + return "<{}('{}')>".format(self.__class__.__name__, self.name()) + + @property + def options(self): + """Return the options for the backend + + The options of a backend are the dynamic parameters defining + how the backend is used. These are used to control the :meth:`run` + method. + """ + return self._options + + @abstractmethod + def run(self, run_input, **options): + """Run on the backend. + + This method that will return a :class:`~qiskit.providers.Job` object + that run circuits. Depending on the backend this may be either an async + or sync call. It is the discretion of the provider to decide whether + running should block until the execution is finished or not. The Job + class can handle either situation. + + Args: + run_input (QuantumCircuit or Schedule or list): An individual or a + list of :class:`~qiskit.circuits.QuantumCircuit` or + :class:`~qiskit.pulse.Schedule` objects to run on the backend. + For legacy providers migrating to the new versioned providers, + provider interface a :class:`~qiskit.qobj.QasmQobj` or + :class:`~qiskit.qobj.PulseQobj` objects should probably be + supported too (but deprecated) for backwards compatibility. Be + sure to update the docstrings of subclasses implementing this + method to document that. New provider implementations should not + do this though as :mod:`qiskit.qobj` will be deprecated and + removed along with the legacy providers interface. + options: Any kwarg options to pass to the backend for running the + config. If a key is also present in the options + attribute/object then the expectation is that the value + specified will be used instead of what's set in the options + object. + Returns: + Job: The job object for the run + """ + pass diff --git a/qiskit/providers/basebackend.py b/qiskit/providers/basebackend.py index d52a89ac3853..6b98782edad2 100644 --- a/qiskit/providers/basebackend.py +++ b/qiskit/providers/basebackend.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""This module implements the abstract base class for backend modules. +"""This module implements the legacy abstract base class for backend modules. To create add-on backend modules subclass the Backend class in this module. Doing so requires that the required backend interface is implemented. @@ -23,7 +23,7 @@ class BaseBackend(ABC): - """Base class for backends.""" + """Legacy Base class for backends.""" @abstractmethod def __init__(self, configuration, provider=None): diff --git a/qiskit/providers/basejob.py b/qiskit/providers/basejob.py index f39f3b291380..64f7a3729d76 100644 --- a/qiskit/providers/basejob.py +++ b/qiskit/providers/basejob.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""This module implements the abstract base class for backend jobs. +"""This module implements the legacy abstract base class for backend jobs. When creating a new backend module it is also necessary to implement this job interface. @@ -26,7 +26,7 @@ class BaseJob(ABC): - """Class to handle asynchronous jobs""" + """Legacy Class to handle asynchronous jobs""" def __init__(self, backend: BaseBackend, job_id: str) -> None: """Initializes the asynchronous job. diff --git a/qiskit/providers/job.py b/qiskit/providers/job.py new file mode 100644 index 000000000000..78d90284ca3e --- /dev/null +++ b/qiskit/providers/job.py @@ -0,0 +1,141 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017. +# +# 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. + +"""Job abstract interface.""" + +from abc import ABC, abstractmethod +from typing import Callable, Optional +import time + +from qiskit.providers.jobstatus import JobStatus, JOB_FINAL_STATES +from qiskit.providers.exceptions import JobTimeoutError +from qiskit.providers.backend import Backend + + +class Job: + """Base common type for all versioned Job abstract classes. + + 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 + + +class JobV1(Job, ABC): + """Class to handle jobs + + This first version of the Backend abstract class is written to be mostly + backwards compatible with the legacy providers interface. This was done to ease + the transition for users and provider maintainers to the new versioned providers. Expect, + future versions of this abstract class to change the data model and + interface. + """ + version = 1 + _async = True + + def __init__(self, backend: Backend, job_id: str, **kwargs) -> None: + """Initializes the asynchronous job. + + Args: + backend: the backend used to run the job. + job_id: a unique id in the context of the backend used to run + the job. + kwargs: Any key value metadata to associate with this job. + """ + self._job_id = job_id + self._backend = backend + self.metadata = kwargs + + def job_id(self) -> str: + """Return a unique id identifying the job.""" + return self._job_id + + def backend(self) -> Backend: + """Return the backend where this job was executed.""" + return self._backend + + def done(self) -> bool: + """Return whether the job has successfully run.""" + return self.status() == JobStatus.DONE + + def running(self) -> bool: + """Return whether the job is actively running.""" + return self.status() == JobStatus.RUNNING + + def cancelled(self) -> bool: + """Return whether the job has been cancelled.""" + return self.status() == JobStatus.CANCELLED + + def in_final_state(self) -> bool: + """Return whether the job is in a final job state such as ``DONE`` or ``ERROR``.""" + return self.status() in JOB_FINAL_STATES + + def wait_for_final_state( + self, + timeout: Optional[float] = None, + wait: float = 5, + callback: Optional[Callable] = None + ) -> None: + """Poll the job status until it progresses to a final state such as ``DONE`` or ``ERROR``. + + Args: + timeout: Seconds to wait for the job. If ``None``, wait indefinitely. + wait: Seconds between queries. + callback: Callback function invoked after each query. + The following positional arguments are provided to the callback function: + + * job_id: Job ID + * job_status: Status of the job from the last query + * job: This BaseJob instance + + Note: different subclass might provide different arguments to + the callback function. + + Raises: + JobTimeoutError: If the job does not reach a final state before the + specified timeout. + """ + if not self._async: + return + start_time = time.time() + status = self.status() + while status not in JOB_FINAL_STATES: + elapsed_time = time.time() - start_time + if timeout is not None and elapsed_time >= timeout: + raise JobTimeoutError( + 'Timeout while waiting for job {}.'.format(self.job_id())) + if callback: + callback(self.job_id(), status, self) + time.sleep(wait) + status = self.status() + return + + @abstractmethod + def submit(self): + """Submit the job to the backend for execution.""" + pass + + @abstractmethod + def result(self): + """Return the results of the job.""" + pass + + def cancel(self): + """Attempt to cancel the job.""" + raise NotImplementedError + + @abstractmethod + def status(self): + """Return the status of the job, among the values of ``JobStatus``.""" + pass diff --git a/qiskit/providers/models/backendstatus.py b/qiskit/providers/models/backendstatus.py index 2855a4ca6dd7..57b7fcbb4526 100644 --- a/qiskit/providers/models/backendstatus.py +++ b/qiskit/providers/models/backendstatus.py @@ -12,14 +12,11 @@ """Class for backend status.""" -import re - from qiskit.exceptions import QiskitError class BackendStatus: """Class representing Backend Status.""" - version_regex = re.compile("[0-9]+.[0-9]+.[0-9]+$") def __init__(self, backend_name, backend_version, operational, pending_jobs, status_msg): @@ -36,8 +33,6 @@ def __init__(self, backend_name, backend_version, operational, QiskitError: If the backend version is in an invalid format """ self.backend_name = backend_name - if not self.version_regex.match(backend_version): - raise QiskitError('Backend version is invalid') self.backend_version = backend_version self.operational = operational if pending_jobs < 0: diff --git a/qiskit/providers/options.py b/qiskit/providers/options.py new file mode 100644 index 000000000000..b5d2469d16c4 --- /dev/null +++ b/qiskit/providers/options.py @@ -0,0 +1,35 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# 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. + +"""Container class for backend options.""" + +import types + + +class Options(types.SimpleNamespace): + """Base options object + + This class is the abstract class that all backend options are based + on. The properties of the class are intended to be all dynamically + adjustable so that a user can reconfigure the backend on demand. If a + property is immutable to the user (eg something like number of qubits) + that should be a configuration of the backend class itself instead of the + options. + """ + + def update_options(self, **fields): + """Update options with kwargs""" + self.__dict__.update(fields) + + def get(self, field, default=None): + """Get an option value for a given key.""" + return getattr(self, field, default) diff --git a/qiskit/providers/provider.py b/qiskit/providers/provider.py new file mode 100644 index 000000000000..dfa1ab45a199 --- /dev/null +++ b/qiskit/providers/provider.py @@ -0,0 +1,77 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# 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. + +"""Base class for a provider.""" + +from abc import ABC, abstractmethod + +from qiskit.providers.exceptions import QiskitBackendNotFoundError + + +class Provider: + """Base common type for all versioned Provider abstract classes. + + 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 + + +class ProviderV1(Provider, ABC): + """Base class for a Backend Provider.""" + version = 1 + + def get_backend(self, name=None, **kwargs): + """Return a single backend matching the specified filtering. + + Args: + name (str): name of the backend. + **kwargs: dict used for filtering. + + Returns: + Backend: a backend matching the filtering. + + Raises: + QiskitBackendNotFoundError: if no backend could be found or + more than one backend matches the filtering criteria. + """ + backends = self.backends(name, **kwargs) + if len(backends) > 1: + raise QiskitBackendNotFoundError('More than one backend matches the criteria') + if not backends: + raise QiskitBackendNotFoundError('No backend matches the criteria') + + return backends[0] + + @abstractmethod + def backends(self, name=None, **kwargs): + """Return a list of backends matching the specified filtering. + + Args: + name (str): name of the backend. + **kwargs: dict used for filtering. + + Returns: + list[Backend]: a list of Backends that match the filtering + criteria. + """ + pass + + def __eq__(self, other): + """Equality comparison. + + By default, it is assumed that two `Providers` from the same class are + equal. Subclassed providers can override this behavior. + """ + return type(self).__name__ == type(other).__name__ diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index 84d15df7142d..9cca86cca090 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -282,8 +282,8 @@ def __init__(self, duplication. Args: - backend (BaseBackend): Input backend to use in builder. If not set - certain functionality will be unavailable. + backend (Union[Backend, BaseBackend]): Input backend to use in + builder. If not set certain functionality will be unavailable. schedule: Initital schedule to build on. If not supplied a schedule will be created. name: Name of pulse program to be built. Only used if `schedule` @@ -345,7 +345,7 @@ def backend(self): """Returns the builder backend if set. Returns: - Optional[BaseBackend]: The builder's backend. + Optional[Union[Backend, BaseBackend]]: The builder's backend. """ return self._backend @@ -543,7 +543,7 @@ def build(backend=None, qiskit.execute(pulse_prog, backend) Args: - backend (BaseBackend): A Qiskit backend. If not supplied certain + backend (Union[Backend, BaseBackend]): A Qiskit backend. If not supplied certain builder functionality will be unavailable. schedule: a *mutable* pulse Schedule in which your pulse program will be built. @@ -592,7 +592,7 @@ def active_backend(): """Get the backend of the currently active builder context. Returns: - BaseBackend: The active backend in the currently active + Union[Backend, BaseBackend]: The active backend in the currently active builder context. Raises: diff --git a/qiskit/pulse/macros.py b/qiskit/pulse/macros.py index 1b1c6129fa09..9311fbfe59cf 100644 --- a/qiskit/pulse/macros.py +++ b/qiskit/pulse/macros.py @@ -34,8 +34,8 @@ def measure(qubits: List[int], Args: qubits: List of qubits to be measured. - backend (BaseBackend): A backend instance, which contains hardware-specific data - required for scheduling. + backend (Union[Backend, BaseBackend]): A backend instance, which contains + hardware-specific data required for scheduling. inst_map: Mapping of circuit operations to pulse schedules. If None, defaults to the ``instruction_schedule_map`` of ``backend``. meas_map: List of sets of qubits that must be measured together. If None, defaults to @@ -95,8 +95,8 @@ def measure_all(backend) -> Schedule: Return a Schedule which measures all qubits of the given backend. Args: - backend (BaseBackend): A backend instance, which contains hardware-specific data - required for scheduling. + backend (Union[Backend, BaseBackend]): A backend instance, which contains + hardware-specific data required for scheduling. Returns: A schedule corresponding to the inputs provided. diff --git a/releasenotes/notes/add-v2-interface-a993d673fd77e02e.yaml b/releasenotes/notes/add-v2-interface-a993d673fd77e02e.yaml new file mode 100644 index 000000000000..a6a4b0b3982d --- /dev/null +++ b/releasenotes/notes/add-v2-interface-a993d673fd77e02e.yaml @@ -0,0 +1,20 @@ +--- +features: + - | + A new version of the providers interface has been added. This new interface, + which can be found in :mod:`qiskit.providers`, provides a new versioning + mechanism that will enable changes to the interface to happen in a + compatible manner over time. The new interface should be simple to migrate + existing providers to. Besides having explicitly versioned abstract classes + the key changes for the new interface are that the + :class:`qiskit.providers.backend.BackendV1` method + :meth:`~qiskit.providers.backend.BackendV1.run` can now + take a :class:`~qiskit.circuits.QuantumCircuit` or + :class:`~qiskit.pulse.Schedule` object as inputs instead of ``Qobj`` + objects. To go along with that options are now part of a backend class + so that users can configure run time options when running with a circuit. + The final change is that :class:`qiskit.providers.JobV1` can now be + synchronous or asynchronous, the exact configuration and method for + configuring this is up to the provider, but there are interface hook + points to make it explicit which execution model a job is running under + in the ``JobV1`` abstract class.