Skip to content

Commit

Permalink
[QI2-1194] Added status to QIBackend
Browse files Browse the repository at this point in the history
  • Loading branch information
NischalQuTech committed Dec 24, 2024
1 parent e64fff7 commit 2819726
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 56 deletions.
21 changes: 19 additions & 2 deletions qiskit_quantuminspire/qi_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pprint import PrettyPrinter
from typing import Any, List, Union

from compute_api_client import BackendType
from compute_api_client import ApiClient, BackendStatus, BackendType, BackendTypesApi
from qiskit.circuit import Instruction, Measure, QuantumCircuit
from qiskit.circuit.library import (
CCXGate,
Expand All @@ -21,8 +21,9 @@
from qiskit.providers.options import Options
from qiskit.transpiler import CouplingMap, Target

from qiskit_quantuminspire.api.client import config
from qiskit_quantuminspire.qi_jobs import QIJob
from qiskit_quantuminspire.utils import is_coupling_map_complete
from qiskit_quantuminspire.utils import is_coupling_map_complete, run_async

# Used for parameterizing Qiskit gates in the gate mapping
_THETA = Parameter("ϴ")
Expand Down Expand Up @@ -145,6 +146,20 @@ def max_circuits(self) -> Union[int, None]:
def id(self) -> int:
return self._id

@property
def status(self) -> BackendStatus:
backend_type: BackendType = run_async(self._get_backend_type())
return backend_type.status

async def _get_backend_type(self) -> BackendType:
async with ApiClient(config()) as client:
backend_types_api = BackendTypesApi(client)
return await backend_types_api.read_backend_type_backend_types_id_get(self._id)

@property
def available(self) -> bool:
return bool(self.status != BackendStatus.OFFLINE)

def run(self, run_input: Union[QuantumCircuit, List[QuantumCircuit]], **options: Any) -> QIJob:
"""Create and run a (batch)job on an QuantumInspire Backend.
Expand All @@ -154,6 +169,8 @@ def run(self, run_input: Union[QuantumCircuit, List[QuantumCircuit]], **options:
Returns:
QIJob: A reference to the batch job that was submitted.
"""
if not self.available:
raise RuntimeError(f"{self.name} is {self.status}, jobs can't be submitted")
self.set_options(**options)
job = QIJob(run_input=run_input, backend=self)
job.submit()
Expand Down
120 changes: 66 additions & 54 deletions tests/test_qi_backend.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from unittest.mock import MagicMock
from typing import Any, Callable, Dict, Generator, Optional, Type
from unittest.mock import MagicMock, PropertyMock

import pytest
from pytest_mock import MockerFixture
Expand All @@ -9,6 +10,33 @@
from tests.helpers import create_backend_type


@pytest.fixture()
def qi_job_mock(mocker: MockerFixture) -> Generator[MagicMock, None, None]:
job = MagicMock()
job.submit = MagicMock()
mocker.patch("qiskit_quantuminspire.qi_backend.QIJob", return_value=job)
yield job
job.submit.reset_mock()


@pytest.fixture
def qi_backend_factory(mocker: MockerFixture) -> Callable[..., QIBackend]:

def _generate_qi_backend(params: Optional[Dict[Any, Any]] = None) -> QIBackend:
params = params or {}
backend_type = params.pop("backend_type", create_backend_type(max_number_of_shots=4096))
is_online = params.pop("backend_online", True)

qi_backend = QIBackend(backend_type=backend_type)
mocker.patch.object(
type(qi_backend), "available", PropertyMock(return_value=is_online) # The class of the instance
)

return qi_backend

return _generate_qi_backend


@pytest.mark.parametrize(
"gateset, topology, nqubits, expected_instructions",
[
Expand Down Expand Up @@ -102,27 +130,26 @@ def test_qi_backend_repr() -> None:
assert qi_backend.name in repr(qi_backend)


def test_qi_backend_run(mocker: MockerFixture) -> None:
@pytest.mark.parametrize("backend_online", [True, False]) # Test cases for backend being available and offline
def test_qi_backend_run_backend_status(
qi_job_mock: MagicMock, qi_backend_factory: Callable[..., QIBackend], backend_online: bool
) -> None:
# Arrange
job = MagicMock()
mocker.patch("qiskit_quantuminspire.qi_backend.QIJob", return_value=job)
backend_type = create_backend_type(max_number_of_shots=4096)
qi_backend = QIBackend(backend_type=backend_type)

# Act
qi_backend = qi_backend_factory(params={"backend_online": backend_online})
qc = QuantumCircuit(2, 2)
qi_backend.run(qc)

# Assert
job.submit.assert_called_once()
# Act & Assert
if backend_online:
qi_backend.run(qc)
qi_job_mock.submit.assert_called_once()
else:
with pytest.raises(RuntimeError):
qi_backend.run(qc)


def test_qi_backend_run_updates_shots(mocker: MockerFixture) -> None:
def test_qi_backend_run_updates_shots(qi_job_mock: MagicMock, qi_backend_factory: Callable[..., QIBackend]) -> None:
# Arrange
job = MagicMock()
mocker.patch("qiskit_quantuminspire.qi_backend.QIJob", return_value=job)
backend_type = create_backend_type(max_number_of_shots=4096)
qi_backend = QIBackend(backend_type=backend_type)
qi_backend = qi_backend_factory()

# Act
qc = QuantumCircuit(2, 2)
Expand All @@ -132,59 +159,44 @@ def test_qi_backend_run_updates_shots(mocker: MockerFixture) -> None:
assert qi_backend.options.get("shots") == 1500


def test_qi_backend_run_unsupported_options(mocker: MockerFixture) -> None:
# Arrange
job = MagicMock()
mocker.patch("qiskit_quantuminspire.qi_backend.QIJob", return_value=job)
backend_type = create_backend_type(max_number_of_shots=4096)
qi_backend = QIBackend(backend_type=backend_type)

# Act & Assert
qc = QuantumCircuit(2, 2)
with pytest.raises(AttributeError):
qi_backend.run(qc, unsupported_option=True)


def test_qi_backend_run_no_shot_memory_support(mocker: MockerFixture) -> None:
@pytest.mark.parametrize(
"option, value, expected_exception",
[
("unsupported_option", True, AttributeError),
("memory", True, ValueError),
("seed_simulator", 1, ValueError),
],
)
def test_qi_backend_run_with_unsupported_options(
qi_job_mock: MagicMock,
qi_backend_factory: Callable[..., QIBackend],
option: str,
value: Any,
expected_exception: Type[Exception],
) -> None:
# Arrange
job = MagicMock()
mocker.patch("qiskit_quantuminspire.qi_backend.QIJob", return_value=job)
backend_type = create_backend_type(max_number_of_shots=4096)
qi_backend = QIBackend(backend_type=backend_type)
qi_backend = qi_backend_factory()

# Act & Assert
qc = QuantumCircuit(2, 2)
with pytest.raises(ValueError):
qi_backend.run(qc, memory=True)
with pytest.raises(expected_exception):
qi_backend.run(qc, **{option: value})


def test_qi_backend_run_supports_shot_memory(mocker: MockerFixture) -> None:
def test_qi_backend_run_supports_shot_memory(
qi_job_mock: MagicMock, qi_backend_factory: Callable[..., QIBackend]
) -> None:
# Arrange
job = MagicMock()
mocker.patch("qiskit_quantuminspire.qi_backend.QIJob", return_value=job)
backend_type = create_backend_type(max_number_of_shots=4096)
backend_type.supports_raw_data = True
qi_backend = QIBackend(backend_type=backend_type)
qi_backend = qi_backend_factory(params={"backend_type": backend_type})

# Act
qc = QuantumCircuit(2, 2)
qi_backend.run(qc, memory=True)

# Assert
job.submit.assert_called_once()


def test_qi_backend_run_option_bad_value(mocker: MockerFixture) -> None:
# Arrange
job = MagicMock()
mocker.patch("qiskit_quantuminspire.qi_backend.QIJob", return_value=job)
backend_type = create_backend_type(max_number_of_shots=4096)
qi_backend = QIBackend(backend_type=backend_type)

# Act & Assert
qc = QuantumCircuit(2, 2)
with pytest.raises(ValueError):
qi_backend.run(qc, seed_simulator=1)
qi_job_mock.submit.assert_called_once()


def test_qi_backend_construction_toffoli_gate_unsupported(
Expand Down

0 comments on commit 2819726

Please sign in to comment.