From b232b28fb213430f355ce889ed29a59ab33cf65b Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Mon, 25 Feb 2019 21:16:42 +0100 Subject: [PATCH 1/6] Specialize IBMQBackend into IBMQSimulator Add a `IBMQSimulator` class, representing a device managed by IBMQ that is a simulator (ie not a device), and preparing for supporting different per-class behaviour. --- qiskit/providers/ibmq/ibmqbackend.py | 18 +++++++++++++----- qiskit/providers/ibmq/ibmqsingleprovider.py | 6 ++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/qiskit/providers/ibmq/ibmqbackend.py b/qiskit/providers/ibmq/ibmqbackend.py index 06ca8c92b..79c7beeb0 100644 --- a/qiskit/providers/ibmq/ibmqbackend.py +++ b/qiskit/providers/ibmq/ibmqbackend.py @@ -63,12 +63,8 @@ def properties(self): The return is via QX API call. Returns: - BackendProperties: The properties of the backend. If the backend - is a simulator, it returns ``None``. + BackendProperties: The properties of the backend. """ - if self.configuration().simulator: - return None - api_properties = self._api.backend_properties(self.name()) return BackendProperties.from_dict(api_properties) @@ -241,3 +237,15 @@ def __repr__(self): self.project) return "<{}('{}') from IBMQ({})>".format( self.__class__.__name__, self.name(), credentials_info) + + +class IBMQSimulator(IBMQBackend): + """Backend class interfacing with an IBMQ simulator.""" + + def properties(self): + """Return the online backend properties. + + Returns: + None + """ + return None diff --git a/qiskit/providers/ibmq/ibmqsingleprovider.py b/qiskit/providers/ibmq/ibmqsingleprovider.py index 69be54043..6013b7110 100644 --- a/qiskit/providers/ibmq/ibmqsingleprovider.py +++ b/qiskit/providers/ibmq/ibmqsingleprovider.py @@ -16,7 +16,7 @@ from qiskit.validation.exceptions import ModelValidationError from .api import IBMQConnector -from .ibmqbackend import IBMQBackend +from .ibmqbackend import IBMQBackend, IBMQSimulator logger = logging.getLogger(__name__) @@ -28,6 +28,7 @@ class IBMQSingleProvider(BaseProvider): Note: this class is not part of the public API and is not guaranteed to be present in future releases. """ + def __init__(self, credentials, ibmq_provider): """Return a new IBMQSingleProvider. @@ -95,7 +96,8 @@ def _discover_remote_backends(self): for raw_config in configs_list: try: config = BackendConfiguration.from_dict(raw_config) - ret[config.backend_name] = IBMQBackend( + backend_cls = IBMQSimulator if config.simulator() else IBMQBackend + ret[config.backend_name] = backend_cls( configuration=config, provider=self._ibm_provider, credentials=self.credentials, From e300873bc7c5c1f320799aee24e45b43a51beaa4 Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Mon, 25 Feb 2019 22:35:14 +0100 Subject: [PATCH 2/6] Add custom run() method for simulator Add a specialized run() method for IBMQSimulator, accepting backend parameters and noise model, which are appended to the Qobj.config. --- qiskit/providers/ibmq/ibmqbackend.py | 15 +++++++ qiskit/providers/ibmq/utils.py | 59 ++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 qiskit/providers/ibmq/utils.py diff --git a/qiskit/providers/ibmq/ibmqbackend.py b/qiskit/providers/ibmq/ibmqbackend.py index 79c7beeb0..b4994532b 100644 --- a/qiskit/providers/ibmq/ibmqbackend.py +++ b/qiskit/providers/ibmq/ibmqbackend.py @@ -13,6 +13,7 @@ from marshmallow import ValidationError from qiskit.providers import BaseBackend, JobStatus +from qiskit.providers.ibmq.utils import update_qobj_config from qiskit.providers.models import (BackendStatus, BackendProperties, PulseDefaults) @@ -249,3 +250,17 @@ def properties(self): None """ return None + + def run(self, qobj, backend_options=None, noise_model=None): + """Run qobj asynchronously. + + Args: + qobj (Qobj): description of job + backend_options (dict): backend options + noise_model (NoiseModel): noise model + + Returns: + IBMQJob: an instance derived from BaseJob + """ + qobj = update_qobj_config(qobj, backend_options, noise_model) + return super(IBMQSimulator, self).run(qobj) diff --git a/qiskit/providers/ibmq/utils.py b/qiskit/providers/ibmq/utils.py new file mode 100644 index 000000000..2624cb131 --- /dev/null +++ b/qiskit/providers/ibmq/utils.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +"""Utilities related to the IBMQ Provider.""" + +import json + +from numpy import ndarray + +from qiskit.qobj import QobjHeader + + +class AerJSONEncoder(json.JSONEncoder): + """ + JSON encoder for NumPy arrays and complex numbers. + This functions as the standard JSON Encoder but adds support + for encoding: + complex numbers z as lists [z.real, z.imag] + ndarrays as nested lists. + """ + + # pylint: disable=method-hidden,arguments-differ + def default(self, obj): + if isinstance(obj, ndarray): + return obj.tolist() + if isinstance(obj, complex): + return [obj.real, obj.imag] + if hasattr(obj, "as_dict"): + return obj.as_dict() + return super().default(obj) + + +def update_qobj_config(qobj, backend_options=None, noise_model=None): + """Update a Qobj configuration from options and noise model. + + Args: + qobj (Qobj): description of job + backend_options (dict): backend options + noise_model (NoiseModel): noise model + """ + config = qobj.config.as_dict() + + # Append backend options to configuration. + if backend_options: + for key, val in backend_options.items(): + config[key] = val + + # Append noise model to configuration. + if noise_model: + config['noise_model'] = noise_model + + # Update the Qobj configuration. + qobj.config = QobjHeader.from_dict(json.dumps(config, cls=AerJSONEncoder)) + + return qobj From bb42f8c758c03665b9d9a9350340b3c3dfffa3be Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Mon, 25 Feb 2019 22:40:56 +0100 Subject: [PATCH 3/6] Lint and style fixes --- qiskit/providers/ibmq/ibmqbackend.py | 1 + qiskit/providers/ibmq/utils.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/qiskit/providers/ibmq/ibmqbackend.py b/qiskit/providers/ibmq/ibmqbackend.py index b4994532b..655b4b901 100644 --- a/qiskit/providers/ibmq/ibmqbackend.py +++ b/qiskit/providers/ibmq/ibmqbackend.py @@ -262,5 +262,6 @@ def run(self, qobj, backend_options=None, noise_model=None): Returns: IBMQJob: an instance derived from BaseJob """ + # pylint: disable=arguments-differ qobj = update_qobj_config(qobj, backend_options, noise_model) return super(IBMQSimulator, self).run(qobj) diff --git a/qiskit/providers/ibmq/utils.py b/qiskit/providers/ibmq/utils.py index 2624cb131..3bdd8852b 100644 --- a/qiskit/providers/ibmq/utils.py +++ b/qiskit/providers/ibmq/utils.py @@ -15,8 +15,8 @@ class AerJSONEncoder(json.JSONEncoder): - """ - JSON encoder for NumPy arrays and complex numbers. + """JSON encoder for NumPy arrays and complex numbers. + This functions as the standard JSON Encoder but adds support for encoding: complex numbers z as lists [z.real, z.imag] @@ -41,6 +41,9 @@ def update_qobj_config(qobj, backend_options=None, noise_model=None): qobj (Qobj): description of job backend_options (dict): backend options noise_model (NoiseModel): noise model + + Returns: + Qobj: qobj. """ config = qobj.config.as_dict() From 339dff9f412aa072c64894446f36cd88b30e2287 Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Tue, 26 Feb 2019 16:46:53 +0100 Subject: [PATCH 4/6] Add cython build dependency --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 60400235e..1a985bac3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,7 @@ stage_generic: &stage_generic # TODO: until the split is complete, install a terra branch. # - pip install -U -r requirements.txt - git clone https://github.com/Qiskit/qiskit-terra.git + - pip install cython - pip install -e qiskit-terra - pip install "requests>=2.19" - pip install "requests-ntlm>=1.1.0" From 2a8056f79ce25a6610727111e46c38518dbb853b Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Tue, 26 Feb 2019 21:59:39 +0100 Subject: [PATCH 5/6] Fix accessing Qobj configuration --- qiskit/providers/ibmq/ibmqsingleprovider.py | 2 +- qiskit/providers/ibmq/utils.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/qiskit/providers/ibmq/ibmqsingleprovider.py b/qiskit/providers/ibmq/ibmqsingleprovider.py index 6013b7110..ed5c9ad27 100644 --- a/qiskit/providers/ibmq/ibmqsingleprovider.py +++ b/qiskit/providers/ibmq/ibmqsingleprovider.py @@ -96,7 +96,7 @@ def _discover_remote_backends(self): for raw_config in configs_list: try: config = BackendConfiguration.from_dict(raw_config) - backend_cls = IBMQSimulator if config.simulator() else IBMQBackend + backend_cls = IBMQSimulator if config.simulator else IBMQBackend ret[config.backend_name] = backend_cls( configuration=config, provider=self._ibm_provider, diff --git a/qiskit/providers/ibmq/utils.py b/qiskit/providers/ibmq/utils.py index 3bdd8852b..75e5f446f 100644 --- a/qiskit/providers/ibmq/utils.py +++ b/qiskit/providers/ibmq/utils.py @@ -54,9 +54,10 @@ def update_qobj_config(qobj, backend_options=None, noise_model=None): # Append noise model to configuration. if noise_model: - config['noise_model'] = noise_model + config['noise_model'] = json.loads(json.dumps(noise_model, + cls=AerJSONEncoder)) # Update the Qobj configuration. - qobj.config = QobjHeader.from_dict(json.dumps(config, cls=AerJSONEncoder)) + qobj.config = QobjHeader.from_dict(config) return qobj From 7a2a9fddb5ba404504015c1d48945e192590c00c Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Thu, 28 Feb 2019 00:00:07 +0100 Subject: [PATCH 6/6] Update to latest pylint version --- qiskit/providers/ibmq/api/utils.py | 6 ++---- requirements-dev.txt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/qiskit/providers/ibmq/api/utils.py b/qiskit/providers/ibmq/api/utils.py index e914b966f..e8f8b9d32 100644 --- a/qiskit/providers/ibmq/api/utils.py +++ b/qiskit/providers/ibmq/api/utils.py @@ -123,8 +123,7 @@ def obtain_token(self, config=None): if error_message: raise CredentialsError('error during login: %s' % error_message) - else: - raise CredentialsError('invalid token') + raise CredentialsError('invalid token') try: response.raise_for_status() self.data_credentials = response.json() @@ -355,8 +354,7 @@ def _response_good(self, response): response.status_code, url, response.text)) - else: - return self._parse_response(response) + return self._parse_response(response) try: if str(response.headers['content-type']).startswith("text/html;"): self.result = response.text diff --git a/requirements-dev.txt b/requirements-dev.txt index 28bab150e..9f2d301cd 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,3 @@ pycodestyle -pylint>=2.2,<2.3 +pylint>=2.3,<2.4 vcrpy