Skip to content

Commit

Permalink
Migrate basic aer provider to new versioned interface (#5128)
Browse files Browse the repository at this point in the history
* Add lightweight v2 provider interface starter

This commit is a lighterweight v2 provider interface. This is an
alternative to what is built in #4885. While the model in #4885 is a
desireable end state it requires a lot of changes all at once for
providers and potentially users. Instead this commit brings the core
concept from #4885 of a cleaner explicitly versioned abstract interface
but minimizes the changes to the data model used in v1. Only some small
changes are made, mainly that jobs can be sync or async, Backend.run()
takes a circuit or schedule, options are set via an Options object at
__init__, with set_option(), or as kwargs on run(). In all other places
the object models from the v1 provider interface are used. This makes
the migration to a versioned interface simpler to start. From there we
can continue to evolve the interface as was done in #4485, like moving
to a target object, reimplementing properties and defaults as versioned
objects, etc.

Since the differences here are so small this commit brings the basicaer
provider over to the v2 provider interface. This can be done with
minimal effort to showcase how similar the 2 interfaces are.

* Fix basicaer simulator init

* Fix lint

* Add provider property to basicaer for Aqua backwards compat

* Add provider method back to backend class for backwards compat

* Fix lint

* Add release notes

* Add v2 provider to docs

* Fix lint

* Revert basicaer v2 provider migration

* Apply suggestions from code review

Co-authored-by: Ali Javadi-Abhari <ajavadia@users.noreply.github.com>
Co-authored-by: Jessie Yu <jessieyu@us.ibm.com>

* Add missing version attributes

* Make Options a simplenamespace subclass

* Update Backend docstrings

* Add v2 Backend support to the rest of terra

* Fix lint

* Fix lint

* Flatten providers subpackage

* Apply suggestions from code review

Co-authored-by: Ali Javadi-Abhari <ajavadia@users.noreply.github.com>

* Update release notes

* Migrate basic aer provider to v2 interface

This commit migrates the basic aer provider to the v2 interface. This
was originally included in #5086 but had to be removed because of a
potential backwards compatibility issue with aqua when using basic aer
as the provider (aqua 0.7.x explicity checks for v1 interface backends).

* DNM install aqua from source to test tutorials

* Remove deprecated schema validation

* Test failures

* Fix tests and lint

* Install aqua from source until release

* Add release notes

* Add ignis from source too as a dependency of aqua

* Apply suggestions from code review

Co-authored-by: Ali Javadi-Abhari <ajavadia@users.noreply.github.com>

* Finish upgrade release note

Co-authored-by: Ali Javadi-Abhari <ajavadia@users.noreply.github.com>
Co-authored-by: Jessie Yu <jessieyu@us.ibm.com>
  • Loading branch information
3 people authored Mar 30, 2021
1 parent 346ffa8 commit f34c0da
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 293 deletions.
5 changes: 3 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -705,8 +705,9 @@ stages:
git clone https://github.com/Qiskit/qiskit-tutorials --depth=1
python -m pip install --upgrade pip
pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt
pip install -U -c constraints.txt -e .
pip install -U "qiskit-ibmq-provider" "qiskit-aer" "z3-solver" "git+https://github.com/Qiskit/qiskit-ignis" "qiskit-aqua" "pyscf<1.7.4" "matplotlib<3.3.0" sphinx nbsphinx sphinx_rtd_theme cvxpy -c constraints.txt
pip install -c constraints.txt -e .
# TODO: Remove aqua and ignis from source after next aqua release
pip install "qiskit-ibmq-provider" "qiskit-aer" "z3-solver" "git+https://github.com/Qiskit/qiskit-ignis" "git+https://github.com/Qiskit/qiskit-aqua" "pyscf<1.7.4" "matplotlib<3.3.0" sphinx nbsphinx sphinx_rtd_theme cvxpy -c constraints.txt
python setup.py build_ext --inplace
sudo apt install -y graphviz pandoc
pip check
Expand Down
103 changes: 19 additions & 84 deletions qiskit/providers/basicaer/basicaerjob.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,122 +10,57 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""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

# pylint: disable=abstract-method

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

Args:
func (callable): test function to be decorated.
"""This module implements the job class used by Basic Aer Provider."""

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
import warnings

from qiskit.providers import JobStatus
from qiskit.providers.job import JobV1

class BasicAerJob(BaseJob):
"""BasicAerJob class.

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

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

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

def submit(self):
"""Submit the job to the backend for execution.
Raises:
JobError: if trying to re-submit the job.
"""
if self._future is not None:
raise JobError("We have already submitted the job!")

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

@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.
"""Get job result .
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)
if timeout is not None:
warnings.warn("The timeout kwarg doesn't have any meaning with "
"BasicAer because execution is synchronous and the "
"result already exists when run() returns.",
UserWarning)

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

@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
return JobStatus.DONE

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

def qobj(self):
"""Return the Qobj submitted for this job.
Returns:
Qobj: the Qobj submitted for this job.
"""
return self._qobj
19 changes: 7 additions & 12 deletions qiskit/providers/basicaer/basicaerprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import logging

from qiskit.exceptions import QiskitError
from qiskit.providers import BaseProvider
from qiskit.providers.provider import ProviderV1
from qiskit.providers.exceptions import QiskitBackendNotFoundError
from qiskit.providers.providerutils import resolve_backend_name, filter_backends

Expand All @@ -35,11 +35,11 @@
]


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

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

# Populate the list of Basic Aer backends.
self._backends = self._verify_backends()
Expand Down Expand Up @@ -109,14 +109,9 @@ def _verify_backends(self):
"""
ret = OrderedDict()
for backend_cls in SIMULATORS:
try:
backend_instance = self._get_backend_instance(backend_cls)
backend_name = backend_instance.name()
ret[backend_name] = backend_instance
except QiskitError as err:
# Ignore backends that could not be initialized.
logger.info('Basic Aer backend %s is not available: %s',
backend_cls, str(err))
backend_instance = self._get_backend_instance(backend_cls)
backend_name = backend_instance.name()
ret[backend_name] = backend_instance
return ret

def _get_backend_instance(self, backend_cls):
Expand Down
62 changes: 45 additions & 17 deletions qiskit/providers/basicaer/qasm_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,18 @@
import uuid
import time
import logging
import warnings

from math import log2
from collections import Counter
import numpy as np

from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.utils.multiprocessing import local_hardware_info
from qiskit.providers.models import QasmBackendConfiguration
from qiskit.result import Result
from qiskit.providers import BaseBackend
from qiskit.providers.backend import BackendV1
from qiskit.providers.options import Options
from qiskit.providers.basicaer.basicaerjob import BasicAerJob
from .exceptions import BasicAerError
from .basicaertools import single_gate_matrix
Expand All @@ -50,7 +53,7 @@
logger = logging.getLogger(__name__)


class QasmSimulatorPy(BaseBackend):
class QasmSimulatorPy(BackendV1):
"""Python implementation of a qasm simulator."""

MAX_QUBITS_MEMORY = int(log2(local_hardware_info()['memory'] * (1024 ** 3) / 16))
Expand Down Expand Up @@ -127,11 +130,12 @@ class QasmSimulatorPy(BaseBackend):
# This should be set to True for the statevector simulator
SHOW_FINAL_STATE = False

def __init__(self, configuration=None, provider=None):
super().__init__(configuration=(
configuration or QasmBackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)),
provider=provider)

def __init__(self, configuration=None, provider=None, **fields):
super().__init__(
configuration=(configuration or QasmBackendConfiguration.from_dict(
self.DEFAULT_CONFIGURATION)),
provider=provider,
**fields)
# Define attributes in __init__.
self._local_random = np.random.RandomState()
self._classical_memory = 0
Expand All @@ -141,12 +145,19 @@ def __init__(self, configuration=None, provider=None):
self._number_of_qubits = 0
self._shots = 0
self._memory = False
self._initial_statevector = self.DEFAULT_OPTIONS["initial_statevector"]
self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"]
self._initial_statevector = self.options.get('initial_statevector')
self._chop_threshold = self.options.get("chop_threashold")
self._qobj_config = None
# TEMP
self._sample_measure = False

@classmethod
def _default_options(cls):
return Options(shots=1024, memory=False,
initial_statevector=None, chop_threshold=1e-15,
allow_sample_measuring=True, seed_simulator=None,
parameter_binds=None)

def _add_unitary(self, gate, qubits):
"""Apply an N-qubit unitary matrix.
Expand Down Expand Up @@ -292,10 +303,10 @@ def _validate_initial_statevector(self):
def _set_options(self, qobj_config=None, backend_options=None):
"""Set the backend options for all experiments in a qobj"""
# Reset default options
self._initial_statevector = self.DEFAULT_OPTIONS["initial_statevector"]
self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"]
if backend_options is None:
backend_options = {}
self._initial_statevector = self.options.get("initial_statevector")
self._chop_threshold = self.options.get("chop_threshold")
if 'backend_options' in backend_options and backend_options['backend_options']:
backend_options = backend_options['backend_options']

# Check for custom initial statevector in backend_options first,
# then config second
Expand Down Expand Up @@ -377,7 +388,7 @@ def _validate_measure_sampling(self, experiment):
# measure sampling is allowed
self._sample_measure = True

def run(self, qobj, backend_options=None):
def run(self, qobj, **backend_options):
"""Run qobj asynchronously.
Args:
Expand All @@ -402,11 +413,28 @@ def run(self, qobj, backend_options=None):
"initial_statevector": np.array([1, 0, 0, 1j]) / np.sqrt(2),
}
"""
self._set_options(qobj_config=qobj.config,
if isinstance(qobj, (QuantumCircuit, list)):
from qiskit.compiler import assemble
out_options = {}
for key in backend_options:
if not hasattr(self.options, key):
warnings.warn(
"Option %s is not used by this backend" % key,
UserWarning, stacklevel=2)
else:
out_options[key] = backend_options[key]
qobj = assemble(qobj, self, **out_options)
qobj_options = qobj.config
else:
warnings.warn('Using a qobj for run() is deprecated and will be '
'removed in a future release.',
PendingDeprecationWarning,
stacklevel=2)
qobj_options = qobj.config
self._set_options(qobj_config=qobj_options,
backend_options=backend_options)
job_id = str(uuid.uuid4())
job = BasicAerJob(self, job_id, self._run_job, qobj)
job.submit()
job = BasicAerJob(self, job_id, self._run_job(job_id, qobj))
return job

def _run_job(self, job_id, qobj):
Expand Down
6 changes: 3 additions & 3 deletions qiskit/providers/basicaer/statevector_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@ class StatevectorSimulatorPy(QasmSimulatorPy):
# Override base class value to return the final state vector
SHOW_FINAL_STATE = True

def __init__(self, configuration=None, provider=None):
def __init__(self, configuration=None, provider=None, **fields):
super().__init__(configuration=(
configuration or QasmBackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)),
provider=provider)
provider=provider, **fields)

def run(self, qobj, backend_options=None):
def run(self, qobj, **backend_options):
"""Run qobj asynchronously.
Args:
Expand Down
Loading

0 comments on commit f34c0da

Please sign in to comment.