-
Notifications
You must be signed in to change notification settings - Fork 162
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
Add refresh method to fake backends #1740
Changes from all commits
30335d0
55a12fd
3143210
be56edd
ca82d05
371d85c
50dc96f
1992ec6
af4e30c
41e6293
b4ace3c
01dcf0e
0d21fc3
be2c56f
c1f1395
ae8773b
0f3f9c2
334a237
42665b1
3009b24
957f716
40391b6
58c3802
685dcd7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,7 +14,7 @@ | |
""" | ||
Base class for dummy backends. | ||
""" | ||
|
||
import logging | ||
import warnings | ||
import collections | ||
import json | ||
|
@@ -40,9 +40,13 @@ | |
from qiskit.providers.basic_provider import BasicSimulator | ||
|
||
from qiskit_ibm_runtime.utils.backend_converter import convert_to_target | ||
from .. import QiskitRuntimeService | ||
from ..utils.backend_encoder import BackendEncoder | ||
|
||
from ..utils.deprecation import issue_deprecation_msg | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class _Credentials: | ||
def __init__(self, token: str = "123456", url: str = "https://") -> None: | ||
|
@@ -458,6 +462,65 @@ def _get_noise_model_from_backend_v2( # type: ignore | |
|
||
return noise_model | ||
|
||
def refresh(self, service: QiskitRuntimeService) -> None: | ||
"""Update the data files from its real counterpart | ||
|
||
This method pulls the latest backend data files from their real counterpart and | ||
overwrites the corresponding files in the local installation: | ||
* ../fake_provider/backends/{backend_name}/conf_{backend_name}.json | ||
* ../fake_provider/backends/{backend_name}/defs_{backend_name}.json | ||
* ../fake_provider/backends/{backend_name}/props_{backend_name}.json | ||
|
||
The new data files will persist through sessions so the files will stay updated unless they | ||
are manually reverted locally or when qiskit-ibm-runtime is upgraded/reinstalled. | ||
|
||
Args: | ||
service: A :class:`QiskitRuntimeService` instance | ||
|
||
Raises: | ||
Exception: If the real target doesn't exist or can't be accessed | ||
""" | ||
|
||
version = self.backend_version | ||
prod_name = self.backend_name.replace("fake", "ibm") | ||
try: | ||
backends = service.backends(prod_name) | ||
real_backend = backends[0] | ||
|
||
real_props = real_backend.properties() | ||
real_config = real_backend.configuration() | ||
real_defs = real_backend.defaults() | ||
|
||
if real_props: | ||
new_version = real_props.backend_version | ||
|
||
if new_version > version: | ||
props_path = os.path.join(self.dirname, self.props_filename) | ||
with open(props_path, "w", encoding="utf-8") as fd: | ||
fd.write(json.dumps(real_props.to_dict(), cls=BackendEncoder)) | ||
kt474 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if real_config: | ||
config_path = os.path.join(self.dirname, self.conf_filename) | ||
config_dict = real_config.to_dict() | ||
with open(config_path, "w", encoding="utf-8") as fd: | ||
fd.write(json.dumps(config_dict, cls=BackendEncoder)) | ||
|
||
if real_defs: | ||
defs_path = os.path.join(self.dirname, self.defs_filename) | ||
with open(defs_path, "w", encoding="utf-8") as fd: | ||
fd.write(json.dumps(real_defs.to_dict(), cls=BackendEncoder)) | ||
|
||
logger.info( | ||
"The backend %s has been updated from {version} to %s version.", | ||
self.backend_name, | ||
real_props.backend_version, | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This just updates the files but doesn't actually refresh the |
||
else: | ||
logger.info("There are no available new updates for %s.", self.backend_name) | ||
|
||
except Exception as ex: | ||
logger.info("The refreshing of %s has failed: %s", self.backend_name, str(ex)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should raise an exception or at least a warning. Info level log doesn't get surfaced by default. |
||
|
||
|
||
class FakeBackend(BackendV1): | ||
"""This is a dummy backend just for testing purposes.""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2024. | ||
# | ||
# 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. | ||
|
||
"""Utilities for working with IBM Quantum backends.""" | ||
import json | ||
from datetime import datetime | ||
from typing import Any | ||
|
||
from qiskit.circuit import ParameterExpression | ||
|
||
|
||
class BackendEncoder(json.JSONEncoder): | ||
"""A json encoder for qobj""" | ||
|
||
def default(self, obj: Any) -> Any: # pylint: disable=arguments-differ | ||
"""Default encoding""" | ||
# Convert numpy arrays: | ||
if hasattr(obj, "tolist"): | ||
return obj.tolist() | ||
# Use Qobj complex json format: | ||
if isinstance(obj, complex): | ||
return [obj.real, obj.imag] | ||
if isinstance(obj, ParameterExpression): | ||
return float(obj) | ||
if isinstance(obj, datetime): | ||
return obj.isoformat() | ||
return json.JSONEncoder.default(self, obj) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
The instances of :class:`FakeBackend` can now be updated from | ||
their real counterpart using the :meth:`FakeBackendV2.refresh`. | ||
To pull the latest real backend data files, access permission is | ||
required. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,7 +36,7 @@ | |
FakeSherbrooke, | ||
FakePrague, | ||
) | ||
from ..ibm_test_case import IBMTestCase | ||
from ..ibm_test_case import IBMTestCase, IBMIntegrationTestCase | ||
|
||
FAKE_PROVIDER_FOR_BACKEND_V2 = FakeProviderForBackendV2() | ||
FAKE_PROVIDER = FakeProvider() | ||
|
@@ -171,3 +171,20 @@ def test_non_cx_tests(self): | |
self.assertIsInstance(backend.target.operation_from_name("cz"), CZGate) | ||
backend = FakeSherbrooke() | ||
self.assertIsInstance(backend.target.operation_from_name("ecr"), ECRGate) | ||
|
||
|
||
class TestRefreshFakeBackends(IBMIntegrationTestCase): | ||
|
||
@classmethod | ||
def setUpClass(cls): | ||
# pylint: disable=arguments-differ | ||
# pylint: disable=no-value-for-parameter | ||
super().setUpClass() | ||
|
||
def test_refresh_method(self): | ||
"""Test refresh method""" | ||
old_backend = FakeSherbrooke() | ||
with self.assertLogs("qiskit_ibm_runtime", level="INFO"): | ||
old_backend.refresh(self.service) | ||
new_backend = FakeSherbrooke() | ||
self.assertGreaterEqual(old_backend.backend_version, new_backend.backend_version) | ||
Comment on lines
+186
to
+190
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't quite understand this test. If FakeSherbrooke currently has version 1. The server data has version 2. If the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we really rely on the
version
? I don't think it gets updated every time new calibration is pushed. And why even check the version if the server side data is already the freshest.