Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Populate backend.configuration_dict from target_info endpoint #666

Merged
merged 26 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions qiskit-superstaq/qiskit_superstaq/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from typing import Dict, List, Optional

import general_superstaq as gss
import pytest
import qiskit

import qiskit_superstaq as qss


class MockSuperstaqBackend(qss.SuperstaqBackend):
"""Stand-in for `SuperstaqBackend` that the tests can call."""

def __init__(self, provider: qss.SuperstaqProvider, target: str) -> None:
"""Initializes a `SuperstaqBackend`.

Args:
provider: Provider for a Superstaq backend.
target: A string containing the name of a target backend.
"""
self._provider = provider
self.configuration_dict = {
"backend_name": target,
"backend_version": "n/a",
"n_qubits": -1,
"basis_gates": None,
"gates": [],
"local": False,
"simulator": False,
"conditional": False,
"open_pulse": False,
"memory": False,
"max_shots": -1,
"coupling_map": None,
}

gss.validation.validate_target(target)

qiskit.providers.BackendV1.__init__(
self,
configuration=qiskit.providers.models.BackendConfiguration.from_dict(
self.configuration_dict
),
provider=provider,
)


class MockSuperstaqClient(gss._SuperstaqClient):
"""Stand-in for `_SuperstaqClient` that the tests can call."""

def get_targets(self) -> Dict[str, Dict[str, List[str]]]:
"""Makes a GET request to retrieve targets from the Superstaq API.

Gets a list of available, unavailable, and retired targets.

Returns:
A dictionary listing the targets.
"""
return {
"superstaq_targets": {
"compile-and-run": [
"ibmq_qasm_simulator",
"ibmq_armonk_qpu",
"ibmq_santiago_qpu",
"ibmq_bogota_qpu",
"ibmq_lima_qpu",
"ibmq_belem_qpu",
"ibmq_quito_qpu",
"ibmq_statevector_simulator",
"ibmq_mps_simulator",
"ibmq_extended-stabilizer_simulator",
"ibmq_stabilizer_simulator",
"ibmq_manila_qpu",
"aws_dm1_simulator",
"aws_tn1_simulator",
"ionq_ion_qpu",
"aws_sv1_simulator",
"rigetti_aspen-9_qpu",
],
"compile-only": ["aqt_keysight_qpu", "sandia_qscout_qpu"],
}
}


class MockSuperstaqProvider(qss.SuperstaqProvider):
"""Stand-in for `SuperstaqProvider` that the tests can call."""

def __init__(
self,
api_key: Optional[str] = None,
remote_host: Optional[str] = None,
api_version: str = gss.API_VERSION,
max_retry_seconds: int = 3600,
verbose: bool = False,
) -> None:
"""Initializes a `SuperstaqProvider`.

Args:
api_key: A string that allows access to the Superstaq API. If no key is provided, then
this instance tries to use the environment variable `SUPERSTAQ_API_KEY`. If
`SUPERSTAQ_API_KEY` is not set, then this instance checks for the
following files:
- `$XDG_DATA_HOME/super.tech/superstaq_api_key`
- `$XDG_DATA_HOME/coldquanta/superstaq_api_key`
- `~/.super.tech/superstaq_api_key`
- `~/.coldquanta/superstaq_api_key`
If one of those files exists, then it is treated as a plain text file, and the first
line of this file is interpreted as an API key. Failure to find an API key raises
an `EnvironmentError`.
remote_host: The location of the API in the form of a URL. If this is None,
then this instance will use the environment variable `SUPERSTAQ_REMOTE_HOST`.
If that variable is not set, then this uses
`https://superstaq.super.tech/{api_version}`,
where `{api_version}` is the `api_version` specified below.
api_version: The version of the API.
max_retry_seconds: The number of seconds to retry calls for. Defaults to one hour.
verbose: Whether to print to stdio and stderr on retriable errors.

Raises:
EnvironmentError: If an API key was not provided and could not be found.
"""
self._name = "mock_superstaq_provider"

self._client = MockSuperstaqClient(
client_name="qiskit-superstaq",
remote_host=remote_host,
api_key=api_key,
api_version=api_version,
max_retry_seconds=max_retry_seconds,
verbose=verbose,
)

def get_backend(self, name: str) -> MockSuperstaqBackend:
return MockSuperstaqBackend(self, name)


@pytest.fixture()
def fake_superstaq_provider() -> MockSuperstaqProvider:
"""Fixture that retrieves the `SuperstaqProvider`.

Returns:
The Mock Superstaq provider.
"""
return MockSuperstaqProvider(api_key="MY_TOKEN")
15 changes: 11 additions & 4 deletions qiskit-superstaq/qiskit_superstaq/superstaq_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,27 @@ def __init__(self, provider: qss.SuperstaqProvider, target: str) -> None:
target: A string containing the name of a target backend.
"""
self._provider = provider

target_info = self._provider._client.target_info(target)["target_info"]
self.configuration_dict = {
"backend_name": target,
"backend_version": "n/a",
"n_qubits": -1,
"basis_gates": None,
"n_qubits": target_info.get("num_qubits"),
"basis_gates": target_info.get("native_gate_set"),
"gates": [],
"local": False,
"simulator": False,
"conditional": False,
"open_pulse": False,
bharat-thotakura marked this conversation as resolved.
Show resolved Hide resolved
"memory": False,
"max_shots": -1,
"max_shots": None,
"coupling_map": None,
}
target_info.pop("target", None)
target_info.pop("num_qubits", None)
target_info.pop("native_gate_set", None)
bharat-thotakura marked this conversation as resolved.
Show resolved Hide resolved

self.configuration_dict.update(target_info)
gss.validation.validate_target(target)

super().__init__(
Expand All @@ -64,6 +70,7 @@ def _default_options(cls) -> qiskit.providers.Options:
return qiskit.providers.Options(shots=1000)

def __eq__(self, other: Any) -> bool:

if not isinstance(other, qss.SuperstaqBackend):
return False

Expand Down Expand Up @@ -391,7 +398,7 @@ def target_info(self) -> Dict[str, Any]:
Returns:
A dictionary of target information.
"""
return self._provider._client.target_info(self.name())["target_info"]
return self.configuration_dict

def resource_estimate(
self, circuits: Union[qiskit.QuantumCircuit, Sequence[qiskit.QuantumCircuit]]
Expand Down
59 changes: 25 additions & 34 deletions qiskit-superstaq/qiskit_superstaq/superstaq_backend_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# pylint: disable=missing-function-docstring,missing-class-docstring
from __future__ import annotations

import json
import textwrap
from typing import TYPE_CHECKING
from unittest.mock import DEFAULT, MagicMock, patch

import general_superstaq as gss
Expand All @@ -9,21 +12,17 @@

import qiskit_superstaq as qss

if TYPE_CHECKING:
from qiskit_superstaq.conftest import MockSuperstaqProvider

def test_default_options() -> None:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary test: Don't affect coverage with it's removal and doesn't look like a regression test.

provider = qss.SuperstaqProvider(api_key="MY_TOKEN")
backend = qss.SuperstaqBackend(provider=provider, target="ibmq_qasm_simulator")

assert qiskit.providers.Options(shots=1000) == backend._default_options()


def test_run() -> None:
def test_run(fake_superstaq_provider: MockSuperstaqProvider) -> None:
qc = qiskit.QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 0], [1, 1])

backend = qss.SuperstaqProvider(api_key="123").get_backend("ss_example_qpu")
backend = fake_superstaq_provider.get_backend("ss_example_qpu")

with patch(
"general_superstaq.superstaq_client._SuperstaqClient.create_job",
Expand All @@ -38,7 +37,7 @@ def test_run() -> None:
backend.run(qc, shots=1000)


def test_multi_circuit_run() -> None:
def test_multi_circuit_run(fake_superstaq_provider: MockSuperstaqProvider) -> None:
qc1 = qiskit.QuantumCircuit(1, 1)
qc1.h(0)
qc1.measure(0, 0)
Expand All @@ -48,7 +47,7 @@ def test_multi_circuit_run() -> None:
qc2.cx(0, 1)
qc2.measure([0, 1], [0, 1])

backend = qss.SuperstaqProvider(api_key="123").get_backend("ss_example_qpu")
backend = fake_superstaq_provider.get_backend("ss_example_qpu")

with patch(
"general_superstaq.superstaq_client._SuperstaqClient.create_job",
Expand All @@ -59,13 +58,13 @@ def test_multi_circuit_run() -> None:
assert answer == expected


def test_multi_arg_run() -> None:
def test_multi_arg_run(fake_superstaq_provider: MockSuperstaqProvider) -> None:
qc = qiskit.QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 0], [1, 1])

backend = qss.SuperstaqProvider(api_key="123").get_backend("ss_example_qpu")
backend = fake_superstaq_provider.get_backend("ss_example_qpu")

with patch(
"general_superstaq.superstaq_client._SuperstaqClient.create_job",
Expand All @@ -76,14 +75,12 @@ def test_multi_arg_run() -> None:
assert answer == expected


def test_retrieve_job() -> None:
def test_retrieve_job(fake_superstaq_provider: MockSuperstaqProvider) -> None:
qc = qiskit.QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 0], [1, 1])

provider = qss.SuperstaqProvider(api_key="MY_TOKEN")
backend = provider.get_backend("ibmq_qasm_simulator")
backend = fake_superstaq_provider.get_backend("ibmq_qasm_simulator")
with patch(
"general_superstaq.superstaq_client._SuperstaqClient.create_job",
return_value={"job_ids": ["job_id"], "status": "ready"},
Expand All @@ -92,16 +89,14 @@ def test_retrieve_job() -> None:
assert job == backend.retrieve_job("job_id")


def test_eq() -> None:
provider = qss.SuperstaqProvider(api_key="123")

backend1 = provider.get_backend("ibmq_qasm_simulator")
def test_eq(fake_superstaq_provider: MockSuperstaqProvider) -> None:
backend1 = fake_superstaq_provider.get_backend("ibmq_qasm_simulator")
assert backend1 != 3

backend2 = provider.get_backend("ibmq_athens_qpu")
backend2 = fake_superstaq_provider.get_backend("ibmq_athens_qpu")
assert backend1 != backend2

backend3 = provider.get_backend("ibmq_qasm_simulator")
backend3 = fake_superstaq_provider.get_backend("ibmq_qasm_simulator")
assert backend1 == backend3


Expand Down Expand Up @@ -233,9 +228,10 @@ def test_ibmq_compile(mock_post: MagicMock) -> None:


@patch("requests.post")
def test_qscout_compile(mock_post: MagicMock) -> None:
provider = qss.SuperstaqProvider(api_key="MY_TOKEN")
backend = provider.get_backend("sandia_qscout_qpu")
def test_qscout_compile(
mock_post: MagicMock, fake_superstaq_provider: MockSuperstaqProvider
) -> None:
backend = fake_superstaq_provider.get_backend("sandia_qscout_qpu")

qc = qiskit.QuantumCircuit(1)
qc.h(0)
Expand Down Expand Up @@ -268,7 +264,7 @@ def test_qscout_compile(mock_post: MagicMock) -> None:
"final_logical_to_physicals": json.dumps([[(0, 13)], [(0, 13)]]),
"jaqal_programs": [jaqal_program, jaqal_program],
}
out = provider.qscout_compile([qc, qc])
out = fake_superstaq_provider.qscout_compile([qc, qc])
assert out.circuits == [qc, qc]
assert out.final_logical_to_physicals == [{0: 13}, {0: 13}]

Expand All @@ -290,12 +286,7 @@ def test_compile(mock_post: MagicMock) -> None:
assert out.final_logical_to_physicals == [{0: 0}]


def test_target_info() -> None:
def test_target_info(fake_superstaq_provider: MockSuperstaqProvider) -> None:
target = "ibmq_qasm_simulator"
backend = qss.SuperstaqProvider(api_key="123").get_backend(target)
fake_data = {"target_info": {"backend_name": target}}
with patch(
"general_superstaq.superstaq_client._SuperstaqClient.target_info",
return_value=fake_data,
):
assert backend.target_info() == fake_data["target_info"]
backend = fake_superstaq_provider.get_backend(target)
assert backend.target_info()["backend_name"] == target
12 changes: 8 additions & 4 deletions qiskit-superstaq/qiskit_superstaq/superstaq_job_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# pylint: disable=missing-function-docstring,missing-class-docstring
from typing import Dict, Union
from __future__ import annotations

from typing import TYPE_CHECKING, Dict, Union
from unittest import mock

import general_superstaq as gss
Expand All @@ -8,15 +10,17 @@

import qiskit_superstaq as qss

if TYPE_CHECKING:
from qisskit_superstaq.conftest import MockSuperstaqProvider


def mock_response(status_str: str) -> Dict[str, Union[str, int, Dict[str, int]]]:
return {"status": status_str, "samples": {"10": 100}, "shots": 100}


@pytest.fixture
def backend() -> qss.SuperstaqBackend:
provider = qss.SuperstaqProvider(api_key="token")
return provider.get_backend("ss_example_qpu")
def backend(fake_superstaq_provider: MockSuperstaqProvider) -> qss.SuperstaqBackend:
return fake_superstaq_provider.get_backend("ss_example_qpu")


def test_wait_for_results(backend: qss.SuperstaqBackend) -> None:
Expand Down
11 changes: 0 additions & 11 deletions qiskit-superstaq/qiskit_superstaq/superstaq_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,17 +469,6 @@ def process_dfe(self, ids: List[str]) -> float:
"""
return self._client.process_dfe(ids)

def target_info(self, target: str) -> Dict[str, Any]:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is deleted cause it's not how qiskit users are supposed to be getting target info. They should be using backend.configuration

"""Returns information about the device specified by `target`.

Args:
target: A string containing the name of a target backend.

Returns:
Information about a target backend.
"""
return self._client.target_info(target)["target_info"]

def get_targets(self) -> Dict[str, Any]:
"""Gets list of targets.

Expand Down
Loading