Skip to content

Commit

Permalink
Add support for dynamic backends for IonQ (#428)
Browse files Browse the repository at this point in the history
* Add support for dynamic backends for IonQ

Using the backends endpoint, add available backends to the list.

* Review comments

* Formatting fixes

* changelog

* Remove unnecessary element in docstring

Co-authored-by: Nguyen Damien <ngn.damien@gmail.com>
  • Loading branch information
Cynocracy and Takishima committed Apr 11, 2022
1 parent 6f2c2be commit b22a278
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 20 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- Added IonQ dynamic backends fetch.

### Repository

- Fix issues with building on CentOS 7 & 8
Expand Down
27 changes: 14 additions & 13 deletions projectq/backends/_ionq/_ionq_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ def __init__(self, verbose=False):

def update_devices_list(self):
"""Update the list of devices this backend can support."""
self.authenticate(self.token)
req = super().get(urljoin(_API_URL, 'backends'))
req.raise_for_status()
r_json = req.json()
# Legacy backends, kept for backward compatibility.
self.backends = {
'ionq_simulator': {
'nq': 29,
Expand All @@ -57,6 +62,8 @@ def update_devices_list(self):
'target': 'qpu',
},
}
for backend in r_json:
self.backends[backend["backend"]] = {"nq": backend["qubits"], "target": backend["backend"]}
if self._verbose: # pragma: no cover
print('- List of IonQ devices available:')
print(self.backends)
Expand Down Expand Up @@ -240,19 +247,14 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover

raise RequestTimeoutError("Timeout. The ID of your submitted job is {}.".format(execution_id))

def show_devices(self):
"""Show the currently available device list for the IonQ provider.
def show_devices(verbose=False):
"""Show the currently available device list for the IonQ provider.
Args:
verbose (bool): If True, additional information is printed
Returns:
list: list of available devices and their properties.
"""
ionq_session = IonQ(verbose=verbose)
ionq_session.update_devices_list()
return ionq_session.backends
Returns:
list: list of available devices and their properties.
"""
self.update_devices_list()
return self.backends


def retrieve(
Expand Down Expand Up @@ -398,6 +400,5 @@ def send(
__all__ = [
'send',
'retrieve',
'show_devices',
'IonQ',
]
62 changes: 59 additions & 3 deletions projectq/backends/_ionq/_ionq_http_client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,74 @@ def user_password_input(prompt):
assert str(excinfo.value) == 'An authentication token is required!'


def test_is_online():
def test_is_online(monkeypatch):
def mock_get(_self, path, *args, **kwargs):
assert urljoin(_api_url, 'backends') == path
mock_response = mock.MagicMock()
mock_response.json = mock.MagicMock(
return_value=[
{
"backend": "qpu.s11",
"status": "available",
"qubits": 11,
"average_queue_time": 3253287,
"last_updated": 1647863473555,
"characterization_url": "/characterizations/48ccd423-2913-45e0-a669-e0f676abeb82",
},
{
"backend": "simulator",
"status": "available",
"qubits": 19,
"average_queue_time": 1499,
"last_updated": 1627065490042,
},
],
)
return mock_response

monkeypatch.setattr('requests.sessions.Session.get', mock_get)

ionq_session = _ionq_http_client.IonQ()
ionq_session.authenticate('not none')
ionq_session.update_devices_list()
assert ionq_session.is_online('ionq_simulator')
assert ionq_session.is_online('ionq_qpu')
assert ionq_session.is_online('qpu.s11')
assert not ionq_session.is_online('ionq_unknown')


def test_show_devices():
device_list = _ionq_http_client.show_devices()
def test_show_devices(monkeypatch):
def mock_get(_self, path, *args, **kwargs):
assert urljoin(_api_url, 'backends') == path
mock_response = mock.MagicMock()
mock_response.json = mock.MagicMock(
return_value=[
{
"backend": "qpu.s11",
"status": "available",
"qubits": 11,
"average_queue_time": 3253287,
"last_updated": 1647863473555,
"characterization_url": "/characterizations/48ccd423-2913-45e0-a669-e0f676abeb82",
},
{
"backend": "simulator",
"status": "available",
"qubits": 19,
"average_queue_time": 1499,
"last_updated": 1627065490042,
},
],
)
return mock_response

monkeypatch.setattr('requests.sessions.Session.get', mock_get)

ionq_session = _ionq_http_client.IonQ()
ionq_session.authenticate('not none')
device_list = ionq_session.show_devices()
assert isinstance(device_list, dict)
assert len(device_list) == 4
for info in device_list.values():
assert 'nq' in info
assert 'target' in info
Expand Down
7 changes: 5 additions & 2 deletions projectq/setups/ionq.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
->The 29 qubits simulator
"""
from projectq.backends._exceptions import DeviceOfflineError
from projectq.backends._ionq._ionq_http_client import show_devices
from projectq.backends._ionq._ionq_http_client import IonQ
from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper
from projectq.ops import (
Barrier,
Expand All @@ -47,7 +47,10 @@

def get_engine_list(token=None, device=None):
"""Return the default list of compiler engine for the IonQ platform."""
devices = show_devices(token)
service = IonQ()
if token is not None:
service.authenticate(token=token)
devices = service.show_devices()
if not device or device not in devices:
raise DeviceOfflineError("Error checking engine list: no '{}' devices available".format(device))

Expand Down
13 changes: 11 additions & 2 deletions projectq/setups/ionq_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import pytest

from projectq.backends._exceptions import DeviceOfflineError
from projectq.backends._ionq._ionq_http_client import IonQ
from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper


Expand All @@ -26,7 +27,11 @@ def test_basic_ionq_mapper(monkeypatch):
def mock_show_devices(*args, **kwargs):
return {'dummy': {'nq': 3, 'target': 'dummy'}}

monkeypatch.setattr(projectq.setups.ionq, 'show_devices', mock_show_devices)
monkeypatch.setattr(
IonQ,
'show_devices',
mock_show_devices,
)
engine_list = projectq.setups.ionq.get_engine_list(device='dummy')
assert len(engine_list) > 1
mapper = engine_list[-1]
Expand All @@ -41,7 +46,11 @@ def test_ionq_errors(monkeypatch):
def mock_show_devices(*args, **kwargs):
return {'dummy': {'nq': 3, 'target': 'dummy'}}

monkeypatch.setattr(projectq.setups.ionq, 'show_devices', mock_show_devices)
monkeypatch.setattr(
IonQ,
'show_devices',
mock_show_devices,
)

with pytest.raises(DeviceOfflineError):
projectq.setups.ionq.get_engine_list(device='simulator')

0 comments on commit b22a278

Please sign in to comment.