Skip to content

Commit

Permalink
Resolves #262: add qiskit.backend methods... (#363)
Browse files Browse the repository at this point in the history
* Resolves #262: add qiskit.backend methods...

for getting parameter, calibration, and status.

* linting quantumprogram

* Update _backendutils.py

* Update _backendutils.py

* Update _basebackend.py

* Update _qeremote.py

* Update _basebackend.py

* Update _quantumprogram.py

* Update _qeremote.py

* Warning, updates to test, and backencs

1. Added to the quantum program warnings — may of done this incorrect
2. Removed old versions
3. In the configuration we lost the rewrite of the mapping. I agree we
want to update the API but first we need the backend configuration to
work
4. Made the test use quantum program

Still to be done — check why failing some test
— add some test to backend that test the test_basebackends  that test
new methods

* Fixing some spelling and linting

* Spelling fixes

* Lint and spelling

* Linter fixes and some spelling in backendutils

* Cleaning up the coupling_map

@ewinston we decided that all new code should use the new map and all
old code the older one. I have remove the complicated identity map we
inserted :-)

* Renaming configuration as config to fix scope error

* Linting in test

* Linting more

* Fixing the vqe

* More linting

* Update test_quantumprogram.py

* lint errors I found trying to debug the error

* Removing backend from quantum_program test

* Making the backend object based

* Adding tests for the backend object

* Linting tests

* Spelling and linting

* Removing some set_api that are not needed

* set log level to debug

* Removing the api from the tests and needed by the program

* Cleaning up and adding a discover

* correct travis.yml env variable specification

* Removing more old code

* Updated lengths with rename of q_name and c_name

* Lint errors found from travis fixed

* Moving import order to help travis linter

* Revise DeprecationWarnings, add note to docstrings

Add a context manager for ensuring the DeprecationWarnings are shown
regardless of the user configuration, as by default they are hidden for
end users (ie. when not on an interpreter).
Revise the strings for the warnings and add entries to the docstrings
for visually showing them on the online docs.

* Enable deprecation warnings during __init__

Enable the display of deprecation warnings during qiskit.__init__
instead of via a context manager, for simplifying the deprecated calls
and help providing visual aids for the editors.

* fix cyclic import and scope of configuration

These were pylint warnings. The configuration one was due to having a function of
of the same name as a parameter in _backendutils.py.

* fix linter

* remove converting coupling_map to dict

* minor lint fix

* Small fix to the backends.

* Fix LookupErrors, style, comments

Fix several backendutils methods raising ConnectionError instead of
LookupError when the entities were not found.
Update the structure of those methods for simplicity and consistency.
Fix commented out code on tests.
  • Loading branch information
ewinston authored and diego-plan9 committed Mar 27, 2018
1 parent 27fd0b3 commit cc2546b
Show file tree
Hide file tree
Showing 12 changed files with 752 additions and 344 deletions.
2 changes: 2 additions & 0 deletions qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,6 @@
from ._quantumprogram import QuantumProgram
from ._result import Result

from . import backends

__version__ = '0.5.0'
426 changes: 233 additions & 193 deletions qiskit/_quantumprogram.py

Large diffs are not rendered by default.

26 changes: 25 additions & 1 deletion qiskit/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
# =============================================================================
"""Common utilities for QISKit."""

import sys
import logging
import re
import sys
import warnings

API_NAME = 'IBMQuantumExperience'
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -85,5 +87,27 @@ def _check_ibmqx_version():
str(ibmqx_require))


def _enable_deprecation_warnings():
"""
Force the `DeprecationWarning` warnings to be displayed for the qiskit
module, overriding the system configuration as they are ignored by default
[1] for end-users.
TODO: on Python 3.7, this might not be needed due to PEP-0565 [2].
[1] https://docs.python.org/3/library/warnings.html#default-warning-filters
[2] https://www.python.org/dev/peps/pep-0565/
"""
# pylint: disable=invalid-name
deprecation_filter = ('always', None, DeprecationWarning,
re.compile(r'^qiskit\.*', re.UNICODE), 0)

# Instead of using warnings.simple_filter() directly, the internal
# _add_filter() function is used for being able to match against the
# module.
warnings._add_filter(*deprecation_filter, append=False)


_check_python_version()
_check_ibmqx_version()
_enable_deprecation_warnings()
5 changes: 4 additions & 1 deletion qiskit/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
from ._basebackend import BaseBackend
from ._backendutils import (get_backend_class,
get_backend_instance,
get_backend_configuration,
configuration,
calibration,
parameters,
status,
local_backends,
remote_backends,
register_backend,
Expand Down
110 changes: 82 additions & 28 deletions qiskit/backends/_backendutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import re
from collections import namedtuple

from qiskit import mapper
import qiskit
from ._basebackend import BaseBackend
from .. import QISKitError

Expand Down Expand Up @@ -89,41 +89,36 @@ def discover_local_backends(directory=os.path.dirname(__file__)):


def discover_remote_backends(api):
"""Discover backends available on the Quantum Experience
"""Discover backends available from IBM Q
Args:
api (IBMQuantumExperience): Quantum Experience API
api (IBMQuantumExperience): IBM Q API
Returns:
list: list of discovered backend names
"""
from ._qeremote import QeRemote
QeRemote.set_api(api)
configuration_list = api.available_backends()
config_list = api.available_backends()
backend_name_list = []
for configuration in configuration_list:
configuration_edit = {}
backend_name = configuration['name']
for config in config_list:
config_edit = {}
backend_name = config['name']
backend_name_list.append(backend_name)
configuration_edit['local'] = False
for key in configuration.keys():
config_edit['local'] = False
for key in config.keys():
new_key = _snake_case_to_camel_case(key)
if new_key not in ['id', 'serial_number', 'topology_id', 'status']:
configuration_edit[new_key] = configuration[key]
if new_key == 'coupling_map':
if isinstance(configuration[key], list):
cmap = mapper.coupling_list2dict(configuration[key])
configuration_edit[new_key] = cmap
config_edit[new_key] = config[key]
# online_qasm_simulator uses different name for basis_gates
if 'gateSet' in configuration:
configuration_edit['basis_gates'] = configuration['gateSet']
del configuration_edit['gate_set']
if 'gateSet' in config:
config_edit['basis_gates'] = config['gateSet']
del config_edit['gate_set']
# ibmqx_qasm_simulator doesn't report coupling_map
if ('coupling_map' not in configuration_edit.keys() and
configuration['simulator']):
configuration_edit['coupling_map'] = 'all-to-all'
if 'coupling_map' not in config_edit.keys() and config['simulator']:
config_edit['coupling_map'] = 'all-to-all'
registered_backend = RegisteredBackend(backend_name,
QeRemote,
configuration_edit)
config_edit)
_REGISTERED_BACKENDS[backend_name] = registered_backend
return backend_name_list

Expand Down Expand Up @@ -153,7 +148,7 @@ def update_backends(api=None):
return backend_name_list


def register_backend(cls, configuration=None):
def register_backend(cls, configuration_=None):
"""Register a backend in the list of available backends.
Register a `cls` backend in the `_REGISTERED_BACKENDS` dict, validating
Expand All @@ -164,7 +159,7 @@ def register_backend(cls, configuration=None):
Args:
cls (class): a subclass of BaseBackend that contains a backend
configuration (dict): backend configuration to use instead of class'
configuration_ (dict): backend configuration to use instead of class'
default.
Returns:
Expand All @@ -184,7 +179,7 @@ def register_backend(cls, configuration=None):
raise QISKitError('Could not register backend: %s is not a subclass '
'of BaseBackend' % cls)
try:
backend_instance = cls(configuration=configuration)
backend_instance = cls(configuration=configuration_)
except Exception as err:
raise QISKitError('Could not register backend: %s could not be '
'instantiated: %s' % (cls, err))
Expand All @@ -207,7 +202,7 @@ def deregister_backend(backend_name):
"""Remove backend from list of available backens
Args:
backend_name (str): name of backend to unregister
backend_name (str): name of backend to deregister
Raises:
KeyError if backend_name is not registered.
Expand Down Expand Up @@ -247,13 +242,13 @@ def get_backend_instance(backend_name):
"""
try:
registered_backend = _REGISTERED_BACKENDS[backend_name]
return registered_backend.cls(
configuration=registered_backend.configuration)
except KeyError:
raise LookupError('backend "{}" is not available'.format(backend_name))
return registered_backend.cls(
configuration=registered_backend.configuration)


def get_backend_configuration(backend_name):
def configuration(backend_name):
"""Return the configuration for the named backend.
Args:
Expand All @@ -271,6 +266,65 @@ def get_backend_configuration(backend_name):
raise LookupError('backend "{}" is not available'.format(backend_name))


def calibration(backend_name):
"""Return the calibration for the named backend.
Args:
backend_name (str): the backend name
Returns:
dict: calibration dict
Raises:
LookupError: if backend is unavailable
"""
try:
backend = qiskit.backends.get_backend_instance(backend_name)
return backend.calibration
except KeyError:
raise LookupError('backend "{}" is not available'.format(backend_name))


def parameters(backend_name):
"""Return the online backend parameters.
Args:
backend_name (str): Name of the backend.
Returns:
dict: The parameters of the named backend.
Raises:
ConnectionError: if the API call failed.
LookupError: If parameters for the named backend can't be
found.
"""
try:
backend = qiskit.backends.get_backend_instance(backend_name)
return backend.parameters
except KeyError:
raise LookupError('backend "{}" is not available'.format(backend_name))


def status(backend_name):
"""Return the status for the named backend.
Args:
backend_name (str): the backend name
Returns:
dict: status dict
Raises:
LookupError: if backend is unavailable
"""
try:
backend = qiskit.backends.get_backend_instance(backend_name)
return backend.status
except KeyError:
raise LookupError('backend "{}" is not available'.format(backend_name))


def local_backends():
"""Get the local backends."""
return [backend.name for backend in _REGISTERED_BACKENDS.values()
Expand Down
17 changes: 17 additions & 0 deletions qiskit/backends/_basebackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,20 @@ def run(self, q_job):
def configuration(self):
"""Return backend configuration"""
return self._configuration

@property
def calibration(self):
"""Return backend calibration"""
backend_name = self.configuration['name']
return {'backend': backend_name, 'calibrations': None}

@property
def parameters(self):
"""Return backend parameters"""
backend_name = self.configuration['name']
return {'backend': backend_name, 'parameters': None}

@property
def status(self):
"""Return backend status"""
return {'available': True}
92 changes: 92 additions & 0 deletions qiskit/backends/_qeremote.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import time
import logging
import pprint
import re
from qiskit.backends._basebackend import BaseBackend
from qiskit import _openquantumcompiler as openquantumcompiler
from qiskit import QISKitError
Expand All @@ -29,6 +30,9 @@

logger = logging.getLogger(__name__)

FIRST_CAP_RE = re.compile('(.)([A-Z][a-z]+)')
ALL_CAP_RE = re.compile('([a-z0-9])([A-Z])')


class QeRemote(BaseBackend):
"""Backend class interfacing with the Quantum Experience remotely.
Expand Down Expand Up @@ -115,6 +119,87 @@ def set_api(cls, api):
"""Associate API with class"""
cls._api = api

@property
def calibration(self):
"""Return the online backend calibrations.
The return is via QX API call.
Returns:
dict: The calibration of the backend.
Raises:
ConnectionError: if the API call failed.
LookupError: If a configuration for the backend can't be found.
"""
if not self._api:
raise ConnectionError('API not set')

try:
backend_name = self.configuration['name']
calibrations = self._api.backend_calibration(backend_name)
except Exception as ex:
raise LookupError(
"Couldn't get backend calibration: {0}".format(ex))

calibrations_edit = {}
for key, vals in calibrations.items():
new_key = _snake_case_to_camel_case(key)
calibrations_edit[new_key] = vals

return calibrations_edit

@property
def parameters(self):
"""Return the online backend parameters.
Returns:
dict: The parameters of the backend.
Raises:
ConnectionError: if the API call faled.
LookupError: If parameters for the backend can't be found.
"""
if not self._api:
raise ConnectionError('API not set')

try:
backend_name = self.configuration['name']
parameters = self._api.backend_parameters(backend_name)
except Exception as ex:
raise LookupError(
"Couldn't get backend parameters: {0}".format(ex))

parameters_edit = {}
for key, vals in parameters.items():
new_key = _snake_case_to_camel_case(key)
parameters_edit[new_key] = vals

return parameters_edit

@property
def status(self):
"""Return the online backend status.
Returns:
dict: The status of the backend.
Raises:
ConnectionError: if the API call failed.
LookupError: If status for the backend can't be found.
"""
if not self._api:
raise ConnectionError('API not set')

try:
backend_name = self.configuration['name']
status = self._api.backend_status(backend_name)
except Exception as ex:
raise LookupError(
"Couldn't get backend status: {0}".format(ex))

return status


def _wait_for_job(jobid, api, wait=5, timeout=60):
"""Wait until all online ran circuits of a qobj are 'COMPLETED'.
Expand Down Expand Up @@ -161,3 +246,10 @@ def _wait_for_job(jobid, api, wait=5, timeout=60):
'status': job_result['qasms'][index]['status']})
return {'job_id': jobid, 'status': job_result['status'],
'result': job_result_return}


# this is also in _backendutils but using that was creating cyclic import.
def _snake_case_to_camel_case(name):
"""Return a snake case string from a camelcase string."""
string_1 = FIRST_CAP_RE.sub(r'\1_\2', name)
return ALL_CAP_RE.sub(r'\1_\2', string_1).lower()
Loading

0 comments on commit cc2546b

Please sign in to comment.