Skip to content

Commit

Permalink
Add providers v2 interface
Browse files Browse the repository at this point in the history
This commit adds the initial implementation of the v2 providers
interface. The rfc document in Qiskit/RFCs#2 documents the rationale
beind the PR. At a high level this introduces a python based API that
provides a general interface for writing additional providers to terra
that is flexible enough for both additional types of services in the
future and also making it less specific to the IBM Quantum API.
Moving forward we will encourage existing provider to migrate to this
new interface.

Included in this commit the Basicaer provider has been converted to a
v2 provider. This will provide a good example for providers on how to
use the new model.

This is still very much in progress, prior to removing the WIP this will
be split into 3 PRs. The first will add the Counts class, the second
will add the v2 providers interface, and the 3rd will migrate the
BasicAer provider to use the v2 interface all 3 are combined for now for
ease ease of testing and development (with github's limitations around
dependencies between PRs), but each is an independent piece that can
stand on it's own (and will make it much easier for final review).

Implements Qiskit/RFCs#2
Fixes Qiskit#4105
  • Loading branch information
mtreinish committed May 21, 2020
1 parent c362226 commit 0e27947
Show file tree
Hide file tree
Showing 17 changed files with 1,074 additions and 555 deletions.
36 changes: 25 additions & 11 deletions qiskit/compiler/transpile.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.providers import BaseBackend
from qiskit.providers.models import BackendProperties
from qiskit.providers.v2.backend import Backend
from qiskit.transpiler import Layout, CouplingMap, PropertySet, PassManager
from qiskit.transpiler.basepasses import BasePass
from qiskit.dagcircuit import DAGCircuit
Expand All @@ -40,7 +41,7 @@


def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]],
backend: Optional[BaseBackend] = None,
backend: Optional[Union[BaseBackend, Backend]] = None,
basis_gates: Optional[List[str]] = None,
coupling_map: Optional[Union[CouplingMap, List[List[int]]]] = None,
backend_properties: Optional[BackendProperties] = None,
Expand Down Expand Up @@ -237,8 +238,12 @@ def _check_circuits_coupling_map(circuits, transpile_args, backend):
max_qubits = parsed_coupling_map.size()

# If coupling_map is None, the limit might be in the backend (like in 1Q devices)
elif backend is not None and not backend.configuration().simulator:
max_qubits = backend.configuration().n_qubits
elif backend is not None:
if isinstance(backend, Backend):
max_qubits = backend.target.num_qubits
else:
if not backend.configuration().simulator:
max_qubits = backend.configuration().n_qubits

if max_qubits is not None and (num_qubits > max_qubits):
raise TranspilerError('Number of qubits ({}) '.format(num_qubits) +
Expand Down Expand Up @@ -364,8 +369,11 @@ def _parse_transpile_args(circuits, backend,
def _parse_basis_gates(basis_gates, backend, circuits):
# try getting basis_gates from user, else backend
if basis_gates is None:
if getattr(backend, 'configuration', None):
basis_gates = getattr(backend.configuration(), 'basis_gates', None)
if isinstance(backend, Backend):
basis_gates = backend.target.basis_gates
else:
if getattr(backend, 'configuration', None):
basis_gates = getattr(backend.configuration(), 'basis_gates', None)
# basis_gates could be None, or a list of basis, e.g. ['u3', 'cx']
if basis_gates is None or (isinstance(basis_gates, list) and
all(isinstance(i, str) for i in basis_gates)):
Expand All @@ -377,10 +385,13 @@ def _parse_basis_gates(basis_gates, backend, circuits):
def _parse_coupling_map(coupling_map, backend, num_circuits):
# try getting coupling_map from user, else backend
if coupling_map is None:
if getattr(backend, 'configuration', None):
configuration = backend.configuration()
if hasattr(configuration, 'coupling_map') and configuration.coupling_map:
coupling_map = CouplingMap(configuration.coupling_map)
if isinstance(backend, Backend):
coupling_map = backend.target.coupling_map
else:
if getattr(backend, 'configuration', None):
configuration = backend.configuration()
if hasattr(configuration, 'coupling_map') and configuration.coupling_map:
coupling_map = CouplingMap(configuration.coupling_map)

# coupling_map could be None, or a list of lists, e.g. [[0, 1], [2, 1]]
if coupling_map is None or isinstance(coupling_map, CouplingMap):
Expand All @@ -397,8 +408,11 @@ def _parse_coupling_map(coupling_map, backend, num_circuits):
def _parse_backend_properties(backend_properties, backend, num_circuits):
# try getting backend_properties from user, else backend
if backend_properties is None:
if getattr(backend, 'properties', None):
backend_properties = backend.properties()
if isinstance(backend, Backend):
backend_properties = backend.properties
else:
if getattr(backend, 'properties', None):
backend_properties = backend.properties()
if not isinstance(backend_properties, list):
backend_properties = [backend_properties] * num_circuits
return backend_properties
Expand Down
69 changes: 41 additions & 28 deletions qiskit/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from time import time
from qiskit.compiler import transpile, assemble, schedule
from qiskit.qobj.utils import MeasLevel, MeasReturnType
from qiskit.providers.v2.backend import Backend
from qiskit.pulse import Schedule
from qiskit.exceptions import QiskitError

Expand Down Expand Up @@ -59,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|Backend):
Backend to execute circuits on.
Transpiler options are automatically grabbed from
backend.configuration() and backend.properties().
Expand Down Expand Up @@ -261,33 +262,45 @@ 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,
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)
return job
# v2 Providers Backend
if isinstance(backend, Backend):
start_time = time()
backend.set_configuration(shots=shots, **run_config)
job = backend.run(experiments)
end_time = time()
_log_submission_time(start_time, end_time)
return job

# v1 Providers Backend
else:
# 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,
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)
return job



def _check_conflicting_argument(**kargs):
Expand Down
123 changes: 10 additions & 113 deletions qiskit/providers/basicaer/basicaerjob.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,125 +14,22 @@

"""This module implements the job class used by Basic Aer Provider."""

from concurrent import futures
import sys
import functools

from qiskit.providers import BaseJob, JobStatus, JobError
from qiskit.qobj import validate_qobj_against_schema
from qiskit.providers.v2 import Job
from qiskit.result.counts import Counts


def requires_submit(func):
"""
Decorator to ensure that a submit has been performed before
calling the method.
class BasicAerJob(Job):
"""BasicAerJob class."""

Args:
func (callable): test function to be decorated.
def __init__(self, job_id, backend, result_data, time_taken):
super().__init__(job_id, backend, time_taken=time_taken)
self.result_data = result_data

Returns:
callable: the decorated function.
"""
@functools.wraps(func)
def _wrapper(self, *args, **kwargs):
if self._future is None:
raise JobError("Job not submitted yet!. You have to .submit() first!")
return func(self, *args, **kwargs)
return _wrapper


class BasicAerJob(BaseJob):
"""BasicAerJob class.
Attributes:
_executor (futures.Executor): executor to handle asynchronous jobs
"""

if sys.platform in ['darwin', 'win32']:
_executor = futures.ThreadPoolExecutor()
else:
_executor = futures.ProcessPoolExecutor()

def __init__(self, backend, job_id, fn, qobj):
super().__init__(backend, job_id)
self._fn = fn
self._qobj = qobj
self._future = None

def submit(self):
"""Submit the job to the backend for execution.
Raises:
QobjValidationError: if the JSON serialization of the Qobj passed
during construction does not validate against the Qobj schema.
JobError: if trying to re-submit the job.
"""
if self._future is not None:
raise JobError("We have already submitted the job!")

validate_qobj_against_schema(self._qobj)
self._future = self._executor.submit(self._fn, self._job_id, self._qobj)

@requires_submit
def result(self, timeout=None):
# pylint: disable=arguments-differ
"""Get job result. The behavior is the same as the underlying
concurrent Future objects,
https://docs.python.org/3/library/concurrent.futures.html#future-objects
Args:
timeout (float): number of seconds to wait for results.
Returns:
qiskit.Result: Result object
Raises:
concurrent.futures.TimeoutError: if timeout occurred.
concurrent.futures.CancelledError: if job cancelled before completed.
"""
return self._future.result(timeout=timeout)

@requires_submit
def cancel(self):
return self._future.cancel()

@requires_submit
def status(self):
"""Gets the status of the job by querying the Python's future
Returns:
qiskit.providers.JobStatus: The current JobStatus
Raises:
JobError: If the future is in unexpected state
concurrent.futures.TimeoutError: if timeout occurred.
"""
# The order is important here
if self._future.running():
_status = JobStatus.RUNNING
elif self._future.cancelled():
_status = JobStatus.CANCELLED
elif self._future.done():
_status = JobStatus.DONE if self._future.exception() is None else JobStatus.ERROR
else:
# Note: There is an undocumented Future state: PENDING, that seems to show up when
# the job is enqueued, waiting for someone to pick it up. We need to deal with this
# state but there's no public API for it, so we are assuming that if the job is not
# in any of the previous states, is PENDING, ergo INITIALIZING for us.
_status = JobStatus.INITIALIZING

return _status

def backend(self):
"""Return the instance of the backend used for this job."""
return self._backend
return 'COMPLETE'

def qobj(self):
"""Return the Qobj submitted for this job.
def wait_for_final_state(self):
return True

Returns:
Qobj: the Qobj submitted for this job.
"""
return self._qobj
36 changes: 26 additions & 10 deletions qiskit/providers/basicaer/basicaerprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
import logging

from qiskit.exceptions import QiskitError
from qiskit.providers import BaseProvider
from qiskit.providers.v2 import Provider
from qiskit.providers.exceptions import QiskitBackendNotFoundError
from qiskit.providers.providerutils import resolve_backend_name, filter_backends
from qiskit.providers.providerutils import filter_backends

from .qasm_simulator import QasmSimulatorPy
from .statevector_simulator import StatevectorSimulatorPy
Expand All @@ -37,23 +37,39 @@
]


class BasicAerProvider(BaseProvider):
def _resolve_backend_name(name, backends, deprecated, aliased):
available = [backend.name for backend in backends]

resolved_name = deprecated.get(name, aliased.get(name, name))
if isinstance(resolved_name, list):
resolved_name = next((b for b in resolved_name if b in available), "")

if resolved_name not in available:
raise LookupError("backend '{}' not found.".format(name))

if name in deprecated:
logger.warning("Backend '%s' is deprecated. Use '%s'.", name,
resolved_name)

return resolved_name


class BasicAerProvider(Provider):
"""Provider for Basic Aer backends."""

def __init__(self, *args, **kwargs):
super().__init__(args, kwargs)
super().__init__()

# Populate the list of Basic Aer backends.
self._backends = self._verify_backends()

def get_backend(self, name=None, **kwargs):
backends = self._backends.values()

# Special handling of the `name` parameter, to support alias resolution
# and deprecated names.
if name:
try:
resolved_name = resolve_backend_name(
resolved_name = _resolve_backend_name(
name, backends,
self._deprecated_backend_names(),
{}
Expand All @@ -73,13 +89,13 @@ def backends(self, name=None, filters=None, **kwargs):
# and deprecated names.
if name:
try:
resolved_name = resolve_backend_name(
resolved_name = _resolve_backend_name(
name, backends,
self._deprecated_backend_names(),
{}
)
backends = [backend for backend in backends if
backend.name() == resolved_name]
backend.name == resolved_name]
except LookupError:
return []

Expand Down Expand Up @@ -112,7 +128,7 @@ def _verify_backends(self):
for backend_cls in SIMULATORS:
try:
backend_instance = self._get_backend_instance(backend_cls)
backend_name = backend_instance.name()
backend_name = backend_instance.name
ret[backend_name] = backend_instance
except QiskitError as err:
# Ignore backends that could not be initialized.
Expand All @@ -133,7 +149,7 @@ def _get_backend_instance(self, backend_cls):
"""
# Verify that the backend can be instantiated.
try:
backend_instance = backend_cls(provider=self)
backend_instance = backend_cls()
except Exception as err:
raise QiskitError('Backend %s could not be instantiated: %s' %
(backend_cls, err))
Expand Down
Loading

0 comments on commit 0e27947

Please sign in to comment.