Skip to content

Commit

Permalink
Add local channel (#1793)
Browse files Browse the repository at this point in the history
* add local channel type

* return QiskitRuntimeLocalService for local channel

* style fixes

* pylint disable

* pylint disable

* add test case for QiskitRuntimeLocalService

* ignore[no-untyped-def]

* fix type

* update FakeRuntimeService

* updated test

* release note

* Update release-notes/unreleased/1793.feat.rst

Co-authored-by: Kevin Tian <[email protected]>

* back the import

* replace by args and kwargs

* updated docstring

* add check to the refresh method

* Use private _run

* update unit tests

---------

Co-authored-by: Kevin Tian <[email protected]>
Co-authored-by: Kevin Tian <[email protected]>
  • Loading branch information
3 people authored Jul 22, 2024
1 parent 7b81be4 commit b3d0f58
Show file tree
Hide file tree
Showing 11 changed files with 62 additions and 15 deletions.
2 changes: 1 addition & 1 deletion qiskit_ibm_runtime/accounts/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from ..utils import resolve_crn

AccountType = Optional[Literal["cloud", "legacy"]]
ChannelType = Optional[Literal["ibm_cloud", "ibm_quantum"]]
ChannelType = Optional[Literal["ibm_cloud", "ibm_quantum", "local"]]

IBM_QUANTUM_API_URL = "https://auth.quantum-computing.ibm.com/api"
IBM_CLOUD_API_URL = "https://cloud.ibm.com"
Expand Down
4 changes: 2 additions & 2 deletions qiskit_ibm_runtime/base_primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def _run(self, pubs: Union[list[EstimatorPub], list[SamplerPub]]) -> RuntimeJobV
result_decoder=DEFAULT_DECODERS.get(self._program_id()),
)

return self._service.run(
return self._service._run(
program_id=self._program_id(), # type: ignore[arg-type]
options=runtime_options,
inputs=primitive_inputs,
Expand Down Expand Up @@ -400,7 +400,7 @@ def _run_primitive(self, primitive_inputs: Dict, user_kwargs: Dict) -> RuntimeJo
callback=combined.get("environment", {}).get("callback", None),
result_decoder=DEFAULT_DECODERS.get(self._program_id()),
)
return self._service.run( # type: ignore[call-arg]
return self._service._run( # type: ignore[call-arg]
program_id=self._program_id(), # type: ignore[arg-type]
options=runtime_options,
inputs=primitive_inputs,
Expand Down
7 changes: 7 additions & 0 deletions qiskit_ibm_runtime/fake_provider/fake_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,8 +575,15 @@ def refresh(self, service: QiskitRuntimeService) -> None:
service: A :class:`QiskitRuntimeService` instance
Raises:
ValueError: if the provided service is a non-QiskitRuntimeService instance.
Exception: If the real target doesn't exist or can't be accessed
"""
if not isinstance(service, QiskitRuntimeService):
raise ValueError(
"The provided service to update the fake backend is invalid. A QiskitRuntimeService is"
" required to retrieve the real backend's current properties and settings."
)

version = self.backend_version
prod_name = self.backend_name.replace("fake", "ibm")
try:
Expand Down
2 changes: 1 addition & 1 deletion qiskit_ibm_runtime/fake_provider/local_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def least_busy(
"""
return self.backends(min_num_qubits=min_num_qubits, filters=filters)[0]

def run(
def _run(
self,
program_id: Literal["sampler", "estimator"],
inputs: Dict,
Expand Down
17 changes: 15 additions & 2 deletions qiskit_ibm_runtime/qiskit_runtime_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ class QiskitRuntimeService:

global_service = None

def __new__(cls, *args, **kwargs): # type: ignore[no-untyped-def]
channel = kwargs.get("channel", None)
if channel == "local":
# pylint: disable=import-outside-toplevel
from .fake_provider.local_service import QiskitRuntimeLocalService

return super().__new__(QiskitRuntimeLocalService)
else:
return super().__new__(cls)

def __init__(
self,
channel: Optional[ChannelType] = None,
Expand Down Expand Up @@ -85,7 +95,10 @@ def __init__(
values in the loaded account.
Args:
channel: Channel type. ``ibm_cloud`` or ``ibm_quantum``.
channel: Channel type. ``ibm_cloud``, ``ibm_quantum`` or ``local``. If ``local`` is selected,
the local testing mode will be used, and primitive queries will run on a local simulator.
For more details, check the `Qiskit Runtime local testing mode
<https://docs.quantum.ibm.com/guides/local-testing-mode>`_ documentation.
token: IBM Cloud API key or IBM Quantum API token.
url: The API URL.
Defaults to https://cloud.ibm.com (ibm_cloud) or
Expand All @@ -106,7 +119,7 @@ def __init__(
private_endpoint: Connect to private API URL.
Returns:
An instance of QiskitRuntimeService.
An instance of QiskitRuntimeService or QiskitRuntimeLocalService for local channel.
Raises:
IBMInputValueError: If an input is invalid.
Expand Down
2 changes: 1 addition & 1 deletion qiskit_ibm_runtime/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def run(
if self._backend is None:
self._backend = job.backend()
else:
job = self._service.run( # type: ignore[call-arg]
job = self._service._run( # type: ignore[call-arg]
program_id=program_id, # type: ignore[arg-type]
options=options,
inputs=inputs,
Expand Down
8 changes: 8 additions & 0 deletions release-notes/unreleased/1793.feat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
The :class:`QiskitRuntimeLocalService` was created to support a local
testing mode. To avoid having to initialize a separate class, "local"
has been added to the valid :class:`QiskitRuntimeService` channel.

.. code:: python
service=QiskitRuntimeService(channel="local")
a :class:`QiskitRuntimeLocalService` instance will be returned.
16 changes: 16 additions & 0 deletions test/integration/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime.accounts import CloudResourceNameResolutionError
from qiskit_ibm_runtime.fake_provider.local_service import QiskitRuntimeLocalService
from qiskit_ibm_runtime.utils.utils import (
get_resource_controller_api_url,
get_iam_api_url,
Expand Down Expand Up @@ -74,6 +75,21 @@ def test_channel_strategy(self):
)
self.assertTrue(service)

def test_local_channel(self):
"""Test local channel mode"""
local_service = QiskitRuntimeService(
channel="local",
)
local_service1 = QiskitRuntimeService(
channel="local",
url=self.dependencies.url,
token=self.dependencies.token,
instance=self.dependencies.instance,
channel_strategy="default",
)
self.assertIsInstance(local_service, QiskitRuntimeLocalService)
self.assertIsInstance(local_service1, QiskitRuntimeLocalService)

def test_resolve_crn_for_valid_service_instance_name(self):
"""Verify if CRN is transparently resolved based for an existing service instance name."""
self._skip_on_ibm_quantum()
Expand Down
3 changes: 3 additions & 0 deletions test/unit/mock/fake_runtime_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ class FakeRuntimeService(QiskitRuntimeService):
DEFAULT_COMMON_BACKEND = "common_backend"
DEFAULT_UNIQUE_BACKEND_PREFIX = "unique_backend_"

def __new__(cls, *args, num_hgps=2, runtime_client=None, backend_specs=None, **kwargs):
return super().__new__(cls, *args, **kwargs)

def __init__(self, *args, num_hgps=2, runtime_client=None, backend_specs=None, **kwargs):
self._test_num_hgps = num_hgps
self._fake_runtime_client = runtime_client
Expand Down
8 changes: 4 additions & 4 deletions test/unit/test_ibm_primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ def __new__(cls, *args, **kwargs): # pylint: disable=unused-argument
inst = primitive(backend=backend_name)
self.assertIsNone(inst.session)
inst.run(self.qx, observables=self.obs)
mock_service_inst.run.assert_called_once()
runtime_options = mock_service_inst.run.call_args.kwargs["options"]
mock_service_inst._run.assert_called_once()
runtime_options = mock_service_inst._run.call_args.kwargs["options"]
self.assertEqual(runtime_options["backend"], mock_backend)

def test_init_with_session_backend_str(self):
Expand Down Expand Up @@ -240,8 +240,8 @@ def test_no_session(self):
inst = cls(backend)
inst.run(self.qx, observables=self.obs)
self.assertIsNone(inst.session)
service.run.assert_called_once()
kwargs_list = service.run.call_args.kwargs
service._run.assert_called_once()
kwargs_list = service._run.call_args.kwargs
self.assertNotIn("session_id", kwargs_list)
self.assertNotIn("start_session", kwargs_list)

Expand Down
8 changes: 4 additions & 4 deletions test/unit/test_ibm_primitives_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,16 @@ def __new__(cls, *args, **kwargs): # pylint: disable=unused-argument
inst = primitive(backend=backend_name)
self.assertIsNone(inst.mode)
inst.run(**get_primitive_inputs(inst))
mock_service_inst.run.assert_called_once()
runtime_options = mock_service_inst.run.call_args.kwargs["options"]
mock_service_inst._run.assert_called_once()
runtime_options = mock_service_inst._run.call_args.kwargs["options"]
self.assertEqual(runtime_options["backend"], mock_backend)

mock_service_inst.reset_mock()
str_mode_inst = primitive(mode=backend_name)
self.assertIsNone(str_mode_inst.mode)
inst.run(**get_primitive_inputs(str_mode_inst))
mock_service_inst.run.assert_called_once()
runtime_options = mock_service_inst.run.call_args.kwargs["options"]
mock_service_inst._run.assert_called_once()
runtime_options = mock_service_inst._run.call_args.kwargs["options"]
self.assertEqual(runtime_options["backend"], mock_backend)

@data(EstimatorV2, SamplerV2)
Expand Down

0 comments on commit b3d0f58

Please sign in to comment.