Skip to content

Commit

Permalink
Adding backend related methods to QiskitRuntimeLocalService (#1764)
Browse files Browse the repository at this point in the history
* basic functionality

* added tests

* for else

* instance

* style and mypy

* pylint

* pylint

* release note

* release notes

* remove random choice
  • Loading branch information
SamFerracin committed Jun 26, 2024
1 parent eea7df4 commit 42bcec4
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 5 deletions.
4 changes: 4 additions & 0 deletions qiskit_ibm_runtime/fake_provider/fake_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ def _set_defs_dict_from_json(self) -> None:
decode_pulse_defaults(defs_dict)
self._defs_dict = defs_dict

def _supports_dynamic_circuits(self) -> bool:
supported_features = self._conf_dict.get("supported_features") or []
return "qasm3" in supported_features

def _load_json(self, filename: str) -> dict:
with open( # pylint: disable=unspecified-encoding
os.path.join(self.dirname, filename)
Expand Down
95 changes: 91 additions & 4 deletions qiskit_ibm_runtime/fake_provider/local_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import logging
import warnings
from dataclasses import asdict
from typing import Dict, Literal, Union
from typing import Callable, Dict, List, Literal, Optional, Union

from qiskit.primitives import (
BackendEstimator,
Expand All @@ -28,8 +28,12 @@
)
from qiskit.primitives.primitive_job import PrimitiveJob
from qiskit.providers.backend import BackendV1, BackendV2
from qiskit.providers.exceptions import QiskitBackendNotFoundError
from qiskit.providers.providerutils import filter_backends
from qiskit.utils import optionals

from .fake_backend import FakeBackendV2 # pylint: disable=cyclic-import
from .fake_provider import FakeProviderForBackendV2 # pylint: disable=unused-import, cyclic-import
from ..ibm_backend import IBMBackend
from ..runtime_options import RuntimeOptions

Expand All @@ -39,9 +43,7 @@
class QiskitRuntimeLocalService:
"""Class for local testing mode."""

def __init__(
self,
) -> None:
def __init__(self) -> None:
"""QiskitRuntimeLocalService constructor.
Expand All @@ -51,6 +53,91 @@ def __init__(
"""
self._channel_strategy = None

def backend(self, name: str = None) -> FakeBackendV2:
"""Return a single fake backend matching the specified filters.
Args:
name: The name of the backend.
Returns:
Backend: A backend matching the filtering.
"""
return self.backends(name=name)[0]

def backends(
self,
name: Optional[str] = None,
min_num_qubits: Optional[int] = None,
dynamic_circuits: Optional[bool] = None,
filters: Optional[Callable[[FakeBackendV2], bool]] = None,
) -> List[FakeBackendV2]:
"""Return all the available fake backends, subject to optional filtering.
Args:
name: Backend name to filter by.
min_num_qubits: Minimum number of qubits the fake backend has to have.
dynamic_circuits: Filter by whether the fake backend supports dynamic circuits.
filters: More complex filters, such as lambda functions.
For example::
from qiskit_ibm_runtime.fake_provider.local_service import QiskitRuntimeLocalService
QiskitRuntimeService.backends(
filters=lambda backend: (backend.online_date.year == 2021)
)
QiskitRuntimeLocalService.backends(
filters=lambda backend: (backend.num_qubits > 30 and backend.num_qubits < 100)
)
Returns:
The list of available fake backends that match the filters.
Raises:
QiskitBackendNotFoundError: If none of the available fake backends matches the given
filters.
"""
backends = FakeProviderForBackendV2().backends(name)
err = QiskitBackendNotFoundError("No backend matches the criteria.")

if name:
for b in backends:
if b.name == name:
backends = [b]
break
else:
raise err

if min_num_qubits:
backends = [b for b in backends if b.num_qubits >= min_num_qubits]

if dynamic_circuits is not None:
backends = [b for b in backends if b._supports_dynamic_circuits() == dynamic_circuits]

backends = filter_backends(backends, filters=filters)

if not backends:
raise err

return backends

def least_busy(
self,
min_num_qubits: Optional[int] = None,
filters: Optional[Callable[[FakeBackendV2], bool]] = None,
) -> FakeBackendV2:
"""Mimics the :meth:`QiskitRuntimeService.least_busy` method by returning a randomly-chosen
fake backend.
Args:
min_num_qubits: Minimum number of qubits the fake backend has to have.
filters: More complex filters, such as lambda functions, that can be defined as for the
:meth:`backends` method.
Returns:
A fake backend.
"""
return self.backends(min_num_qubits=min_num_qubits, filters=filters)[0]

def run(
self,
program_id: Literal["sampler", "estimator"],
Expand Down
4 changes: 3 additions & 1 deletion qiskit_ibm_runtime/qiskit_runtime_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,9 +471,11 @@ def backends(
For example::
QiskitRuntimeService.backends(
filters=lambda b: b.max_shots > 50000)
filters=lambda b: b.max_shots > 50000
)
QiskitRuntimeService.backends(
filters=lambda x: ("rz" in x.basis_gates )
)
use_fractional_gates: Set True to allow for the backends to include
fractional gates in target. Currently this feature cannot be used
simulataneously with the dynamic circuits, PEC, or PEA.
Expand Down
1 change: 1 addition & 0 deletions release-notes/unreleased/1764.feat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added ``backend``, ``backends``, and ``least_busy`` methods to ``QiskitRuntimeLocalService``.
97 changes: 97 additions & 0 deletions test/unit/fake_provider/test_local_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Test of generated fake backends."""
from ddt import data, ddt

from qiskit.providers.exceptions import QiskitBackendNotFoundError

from qiskit_ibm_runtime.fake_provider.fake_backend import FakeBackendV2
from qiskit_ibm_runtime.fake_provider import FakeAlgiers, FakeTorino, FakeProviderForBackendV2
from qiskit_ibm_runtime.fake_provider.local_service import QiskitRuntimeLocalService
from ...ibm_test_case import IBMTestCase


@ddt
class QiskitRuntimeLocalServiceTest(IBMTestCase):
"""Qiskit runtime local service test."""

def test_backend(self):
"""Tests the ``backend`` method."""
service = QiskitRuntimeLocalService()
assert isinstance(service.backend(), FakeBackendV2)
assert isinstance(service.backend("fake_algiers"), FakeAlgiers)
assert isinstance(service.backend("fake_torino"), FakeTorino)

def test_backends(self):
"""Tests the ``backends`` method."""
all_backends = QiskitRuntimeLocalService().backends()
expected = FakeProviderForBackendV2().backends()
assert len(all_backends) == len(expected)

for b1, b2 in zip(all_backends, expected):
assert isinstance(b1, b2.__class__)

def test_backends_name_filter(self):
"""Tests the ``name`` filter of the ``backends`` method."""
backends = QiskitRuntimeLocalService().backends("fake_torino")
assert len(backends) == 1
assert isinstance(backends[0], FakeTorino)

def test_backends_min_num_qubits_filter(self):
"""Tests the ``min_num_qubits`` filter of the ``backends`` method."""
for b in QiskitRuntimeLocalService().backends(min_num_qubits=27):
assert b.num_qubits >= 27

@data(False, True)
def test_backends_dynamic_circuits_filter(self, supports):
"""Tests the ``dynamic_circuits`` filter of the ``backends`` method."""
for b in QiskitRuntimeLocalService().backends(dynamic_circuits=supports):
assert b._supports_dynamic_circuits() == supports

def test_backends_filters(self):
"""Tests the ``filters`` argument of the ``backends`` method."""
for b in QiskitRuntimeLocalService().backends(
filters=lambda b: (b.online_date.year == 2021)
):
assert b.online_date.year == 2021

for b in QiskitRuntimeLocalService().backends(
filters=lambda b: (b.num_qubits > 30 and b.num_qubits < 100)
):
assert b.num_qubits > 30 and b.num_qubits < 100

def test_backends_filters_combined(self):
"""Tests the ``backends`` method with more than one filter."""
service = QiskitRuntimeLocalService()

backends1 = service.backends(name="fake_torino", min_num_qubits=27)
assert len(backends1) == 1
assert isinstance(backends1[0], FakeTorino)

backends2 = service.backends(
min_num_qubits=27, filters=lambda b: (b.online_date.year == 2021)
)
assert len(backends2) == 7

def test_backends_errors(self):
"""Tests the errors raised by the ``backends`` method."""
service = QiskitRuntimeLocalService()

with self.assertRaises(QiskitBackendNotFoundError):
service.backends("torino")
with self.assertRaises(QiskitBackendNotFoundError):
service.backends("fake_torino", filters=lambda b: (b.online_date.year == 1992))

def test_least_busy(self):
"""Tests the ``least_busy`` method."""
assert isinstance(QiskitRuntimeLocalService().least_busy(), FakeBackendV2)

0 comments on commit 42bcec4

Please sign in to comment.