Skip to content

Commit

Permalink
Merge branch 'main' into isa
Browse files Browse the repository at this point in the history
  • Loading branch information
yaelbh authored Jul 2, 2024
2 parents ccd0cf8 + c607150 commit 7c9eed9
Show file tree
Hide file tree
Showing 14 changed files with 240 additions and 27 deletions.
4 changes: 3 additions & 1 deletion qiskit_ibm_runtime/accounts/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,5 +332,7 @@ def _assert_valid_instance(instance: str) -> None:
"""Assert that the instance name is valid for the given account type."""
if not (isinstance(instance, str) and len(instance) > 0):
raise InvalidAccountError(
f"Invalid `instance` value. Expected a non-empty string, got '{instance}'."
f"Invalid `instance` value. Expected a non-empty string, got '{instance}'. "
"If using the ibm_quantum channel,",
"please specify the channel when saving your account with `channel = 'ibm_quantum'`.",
)
7 changes: 7 additions & 0 deletions qiskit_ibm_runtime/base_runtime_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,13 @@ def usage_estimation(self) -> Dict[str, Any]:

return self._usage_estimation

@property
def instance(self) -> Optional[str]:
"""For ibm_quantum channel jobs, return the instance where the job was run.
For ibm_cloud, `None` is returned.
"""
return self._backend._instance

@abstractmethod
def in_final_state(self) -> bool:
"""Return whether the job is in a final job state such as ``DONE`` or ``ERROR``."""
Expand Down
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
98 changes: 94 additions & 4 deletions qiskit_ibm_runtime/fake_provider/local_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@

from __future__ import annotations

import math
import copy
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 +29,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 +44,7 @@
class QiskitRuntimeLocalService:
"""Class for local testing mode."""

def __init__(
self,
) -> None:
def __init__(self) -> None:
"""QiskitRuntimeLocalService constructor.
Expand All @@ -51,6 +54,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 Expand Up @@ -239,6 +327,8 @@ def _run_backend_primitive_v2(
prim_options["default_shots"] = default_shots
primitive_inst = BackendSamplerV2(backend=backend, options=prim_options)
else:
if default_shots := options_copy.pop("default_shots", None):
inputs["precision"] = 1 / math.sqrt(default_shots)
if default_precision := options_copy.pop("default_precision", None):
prim_options["default_precision"] = default_precision
primitive_inst = BackendEstimatorV2(backend=backend, options=prim_options)
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
14 changes: 4 additions & 10 deletions qiskit_ibm_runtime/runtime_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
)
from .utils.result_decoder import ResultDecoder
from .utils.queueinfo import QueueInfo
from .utils.deprecation import deprecate_function
from .api.clients import RuntimeClient
from .api.exceptions import RequestsApiError
from .api.client_parameters import ClientParameters
Expand Down Expand Up @@ -319,10 +320,7 @@ def wait_for_final_state( # pylint: disable=arguments-differ
self,
timeout: Optional[float] = None,
) -> None:
"""Use the websocket server to wait for the final the state of a job.
The server will remain open if the job is still running and the connection will
be terminated once the job completes. Then update and return the status of the job.
"""Poll for the job status from the API until the status is in a final state.
Args:
timeout: Seconds to wait for the job. If ``None``, wait indefinitely.
Expand All @@ -332,12 +330,6 @@ def wait_for_final_state( # pylint: disable=arguments-differ
"""
try:
start_time = time.time()
if self._status not in self.JOB_FINAL_STATES and not self._is_streaming():
self._ws_client_future = self._executor.submit(self._start_websocket_client)
if self._is_streaming():
self._ws_client_future.result(timeout)
# poll for status after stream has closed until status is final
# because status doesn't become final as soon as stream closes
status = self.status()
while status not in self.JOB_FINAL_STATES:
elapsed_time = time.time() - start_time
Expand Down Expand Up @@ -368,6 +360,7 @@ def backend(self, timeout: Optional[float] = None) -> Optional[Backend]:
raise IBMRuntimeError(f"Failed to get job backend: {err}") from None
return self._backend

@deprecate_function("stream_results()", "0.25", "", stacklevel=1)
def stream_results(
self, callback: Callable, decoder: Optional[Type[ResultDecoder]] = None
) -> None:
Expand Down Expand Up @@ -398,6 +391,7 @@ def stream_results(
decoder=decoder,
)

@deprecate_function("interim_results()", "0.25", "", stacklevel=1)
def interim_results(self, decoder: Optional[Type[ResultDecoder]] = None) -> Any:
"""Return the interim results of the job.
Expand Down
14 changes: 4 additions & 10 deletions qiskit_ibm_runtime/runtime_job_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
RuntimeJobTimeoutError,
)
from .utils.result_decoder import ResultDecoder
from .utils.deprecation import deprecate_function
from .api.clients import RuntimeClient
from .api.exceptions import RequestsApiError
from .api.client_parameters import ClientParameters
Expand Down Expand Up @@ -236,10 +237,7 @@ def wait_for_final_state( # pylint: disable=arguments-differ
self,
timeout: Optional[float] = None,
) -> None:
"""Use the websocket server to wait for the final the state of a job.
The server will remain open if the job is still running and the connection will
be terminated once the job completes. Then update and return the status of the job.
"""Poll for the job status from the API until the status is in a final state.
Args:
timeout: Seconds to wait for the job. If ``None``, wait indefinitely.
Expand All @@ -249,12 +247,6 @@ def wait_for_final_state( # pylint: disable=arguments-differ
"""
try:
start_time = time.time()
if self._status not in self.JOB_FINAL_STATES and not self._is_streaming():
self._ws_client_future = self._executor.submit(self._start_websocket_client)
if self._is_streaming():
self._ws_client_future.result(timeout)
# poll for status after stream has closed until status is final
# because status doesn't become final as soon as stream closes
status = self.status()
while status not in self.JOB_FINAL_STATES:
elapsed_time = time.time() - start_time
Expand Down Expand Up @@ -285,6 +277,7 @@ def backend(self, timeout: Optional[float] = None) -> Optional[Backend]:
raise IBMRuntimeError(f"Failed to get job backend: {err}") from None
return self._backend

@deprecate_function("stream_results()", "0.25", "", stacklevel=1)
def stream_results(
self, callback: Callable, decoder: Optional[Type[ResultDecoder]] = None
) -> None:
Expand Down Expand Up @@ -315,6 +308,7 @@ def stream_results(
decoder=decoder,
)

@deprecate_function("interim_results()", "0.25", "", stacklevel=1)
def interim_results(self, decoder: Optional[Type[ResultDecoder]] = None) -> Any:
"""Return the interim results of the job.
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``.
2 changes: 2 additions & 0 deletions release-notes/unreleased/1771.feat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added an ``instance`` property to :class:`BaseRuntimeJob` which returns the instance
where the job was run.
2 changes: 2 additions & 0 deletions release-notes/unreleased/1773.feat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
``default_shots`` are now a supported option when using ``EstimatorV2`` in
local testing mode.
2 changes: 2 additions & 0 deletions release-notes/unreleased/1776.deprecation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:meth:`qiskit_ibm_runtime.RuntimeJobV2.interim_results` and :meth:`qiskit_ibm_runtime.RuntimeJobV2.stream_results`
are now both deprecated.
7 changes: 7 additions & 0 deletions test/integration/test_ibm_job_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import uuid
import time
from datetime import datetime, timedelta
from unittest import SkipTest
from pydantic import ValidationError

from dateutil import tz
Expand Down Expand Up @@ -63,6 +64,12 @@ def test_job_id(self):
"""Test getting a job ID."""
self.assertTrue(self.sim_job.job_id() is not None)

def test_job_instance(self):
"""Test getting job instance."""
if self.dependencies.channel == "ibm_cloud":
raise SkipTest("Cloud channel instance is not returned.")
self.assertEqual(self.dependencies.instance, self.sim_job.instance)

def test_get_backend_name(self):
"""Test getting a backend name."""
self.assertTrue(self.sim_job.backend().name == self.sim_backend.name)
Expand Down
Loading

0 comments on commit 7c9eed9

Please sign in to comment.