iqm-client 29.14.0__tar.gz → 30.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {iqm_client-29.14.0 → iqm_client-30.0.0}/CHANGELOG.rst +23 -3
- {iqm_client-29.14.0 → iqm_client-30.0.0}/PKG-INFO +1 -1
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/user_guide_qiskit.rst +12 -4
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/cli/auth.py +1 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/cli/token_manager.py +5 -5
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/iqm_client.py +22 -4
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/util.py +1 -3
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/examples/resonance_example.py +2 -2
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/fake_backends/fake_adonis.py +0 -1
- iqm_client-30.0.0/src/iqm/qiskit_iqm/iqm_backend.py +158 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/iqm_move_layout.py +7 -15
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/iqm_naive_move_pass.py +24 -21
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/iqm_provider.py +64 -34
- iqm_client-30.0.0/src/iqm/qiskit_iqm/iqm_target.py +372 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm_client.egg-info/PKG-INFO +1 -1
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm_client.egg-info/SOURCES.txt +2 -0
- iqm_client-30.0.0/tests/__init__.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/tests/conftest.py +57 -0
- iqm_client-30.0.0/version.txt +1 -0
- iqm_client-29.14.0/src/iqm/qiskit_iqm/iqm_backend.py +0 -345
- iqm_client-29.14.0/version.txt +0 -1
- {iqm_client-29.14.0 → iqm_client-30.0.0}/AUTHORS.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/CHANGELOG_cirq-iqm.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/CHANGELOG_cortex-cli.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/CHANGELOG_qiskit-iqm.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/INTEGRATION_GUIDE.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/LICENSE.txt +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/MANIFEST.in +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/README.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docbuild +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/API.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/_static/images/favicon.ico +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/_static/images/logo.png +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/_templates/autosummary-class-template.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/_templates/autosummary-module-template.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/authors.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/changelog.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/conf.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/index.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/integration_guide.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/license.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/readme.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/user_guide_cirq.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/docs/user_guide_cli.rst +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/pyproject.toml +0 -0
- /iqm_client-29.14.0/src/iqm/cirq_iqm/py.typed → /iqm_client-30.0.0/pytest.ini +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/requirements/base.in +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/requirements/base.in.internal +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/requirements/base.txt +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/requirements/cirq.in +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/requirements/cirq.txt +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/requirements/cli.in +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/requirements/cli.txt +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/requirements/qiskit.in +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/requirements/qiskit.txt +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/setup.cfg +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/setup.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/__init__.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/devices/__init__.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/devices/adonis.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/devices/aphrodite.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/devices/apollo.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/devices/iqm_device.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/devices/iqm_device_metadata.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/examples/demo_adonis.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/examples/demo_apollo.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/examples/demo_common.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/examples/demo_iqm_execution.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/examples/usage.ipynb +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/extended_qasm_parser.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/iqm_gates.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/iqm_sampler.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/optimizers.py +0 -0
- {iqm_client-29.14.0/src/iqm/iqm_client → iqm_client-30.0.0/src/iqm/cirq_iqm}/py.typed +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/serialize.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/cirq_iqm/transpiler.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/__init__.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/api.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/authentication.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/cli/__init__.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/cli/cli.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/cli/models.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/errors.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/models.py +0 -0
- {iqm_client-29.14.0/src/iqm/qiskit_iqm → iqm_client-30.0.0/src/iqm/iqm_client}/py.typed +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/transpile.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/iqm_client/validation.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/__init__.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/examples/__init__.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/examples/bell_measure.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/examples/transpile_example.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/fake_backends/__init__.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/fake_backends/fake_aphrodite.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/fake_backends/fake_apollo.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/fake_backends/fake_deneb.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/fake_backends/fake_garnet.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/fake_backends/iqm_fake_backend.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/iqm_circuit.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/iqm_circuit_validation.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/iqm_job.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/iqm_transpilation.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/move_gate.py +0 -0
- /iqm_client-29.14.0/tests/__init__.py → /iqm_client-30.0.0/src/iqm/qiskit_iqm/py.typed +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/qiskit_to_iqm.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm/qiskit_iqm/transpiler_plugins.py +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm_client.egg-info/dependency_links.txt +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm_client.egg-info/entry_points.txt +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm_client.egg-info/requires.txt +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/src/iqm_client.egg-info/top_level.txt +0 -0
- {iqm_client-29.14.0 → iqm_client-30.0.0}/test +0 -0
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Version 30.0.0 (2025-08-20)
|
|
6
|
+
===========================
|
|
7
|
+
|
|
8
|
+
Features
|
|
9
|
+
--------
|
|
10
|
+
|
|
11
|
+
- Add the experimental :meth:`IQMClient.get_calibration_quality_metrics` method to retrieve calibration set
|
|
12
|
+
and related quality metrics from the server.
|
|
13
|
+
- Select calibration and quality metric data are now available in :class:`IQMTarget`,
|
|
14
|
+
:class:`IQMBackend`, and :class:`IQMBackendBase` for transpilation and querying. :issue:`SW-769`.
|
|
15
|
+
This data includes the gate durations. :issue:`SW-1321`
|
|
16
|
+
- :func:`transpile_to_IQM` no longer has the ``target`` parameter, the transpilation target is always
|
|
17
|
+
obtained from ``backend``.
|
|
18
|
+
- :class:`IQMTarget` moved into its own module.
|
|
19
|
+
- :class:`IQMFacadeBackend` can be given the name of the :class:`IQMFakeBackend` instance to use.
|
|
20
|
+
|
|
21
|
+
Breaking Changes
|
|
22
|
+
----------------
|
|
23
|
+
- :func:`generate_initial_layout` takes an :class:`IQMTarget` as an input argument instead of :class:`IQMBackend`.
|
|
24
|
+
|
|
5
25
|
Version 29.14.0 (2025-08-20)
|
|
6
26
|
============================
|
|
7
27
|
|
|
@@ -57,7 +77,7 @@ Features
|
|
|
57
77
|
--------
|
|
58
78
|
|
|
59
79
|
- An update to IQMFacadeBackend such that all available IQMFakeBackends are usable for simulation, rather than
|
|
60
|
-
just IQMFakeAdonis
|
|
80
|
+
just IQMFakeAdonis.
|
|
61
81
|
|
|
62
82
|
Version 29.7.0 (2025-07-15)
|
|
63
83
|
===========================
|
|
@@ -352,7 +372,7 @@ Version 22.16.0 (2025-04-03)
|
|
|
352
372
|
============================
|
|
353
373
|
|
|
354
374
|
Feature
|
|
355
|
-
|
|
375
|
+
-------
|
|
356
376
|
|
|
357
377
|
- Enable PEP 604 in linting rules, :issue:`SW-1230`.
|
|
358
378
|
|
|
@@ -368,7 +388,7 @@ Version 22.14.0 (2025-04-02)
|
|
|
368
388
|
============================
|
|
369
389
|
|
|
370
390
|
Features
|
|
371
|
-
|
|
391
|
+
--------
|
|
372
392
|
|
|
373
393
|
- Update the documentation footer to display the package version.
|
|
374
394
|
|
|
@@ -574,6 +574,7 @@ mapping that matches the restriction:
|
|
|
574
574
|
.. code-block:: python
|
|
575
575
|
|
|
576
576
|
qubit_mapping = {i: backend.index_to_qubit_name(q) for i, q in enumerate(qubits)}
|
|
577
|
+
# or qubit_mapping = dict(enumerate(qubits))
|
|
577
578
|
job = backend.run(transpiled_circuit, qubit_mapping=qubit_mapping)
|
|
578
579
|
|
|
579
580
|
|
|
@@ -731,10 +732,17 @@ Results from such executions are random bits. This may be useful when developing
|
|
|
731
732
|
Qiskit on IQM contains :class:`.IQMFacadeBackend`, which allows to combine the mock remote execution with a local
|
|
732
733
|
noisy quantum circuit simulation. This way you can both validate your integration as well as get an idea of the expected circuit execution results.
|
|
733
734
|
|
|
734
|
-
To run a circuit this way, use
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
735
|
+
To run a circuit this way, use :meth:`IQMProvider.get_backend` with one of the backend names
|
|
736
|
+
``facade_adonis, facade_apollo, facade_aphrodite, facade_deneb`` or ``facade_garnet`` to obtain
|
|
737
|
+
a facade backend.
|
|
738
|
+
``facade_adonis, facade_apollo, facade_aphrodite`` represent typical Crystal 5, Crystal 20 and Crystal 54 architecture QPUs,
|
|
739
|
+
whereas ``facade_deneb`` and ``facade_garnet`` represent the corresponding IQM Resonance quantum computers
|
|
740
|
+
more specifically.
|
|
741
|
+
|
|
742
|
+
Note that the provider must be initialized with the URL of a quantum computer with the corresponding
|
|
743
|
+
static quantum architecture (i.e. names of qubits, their connectivity, and the native gateset should match the desired architecture).
|
|
744
|
+
Additionally, the URL should point to a mock server rather than a real quantum computer, as the execution results
|
|
745
|
+
from the server will be discarded and replaced by a simulated result generated by Qiskit Aer.
|
|
738
746
|
|
|
739
747
|
.. code-block:: python
|
|
740
748
|
|
|
@@ -42,6 +42,7 @@ class GrantType(str, Enum):
|
|
|
42
42
|
|
|
43
43
|
class AuthRequest(BaseModel):
|
|
44
44
|
"""Request sent to authentication server for access token and refresh token, or for terminating the session.
|
|
45
|
+
|
|
45
46
|
* Token request with grant type ``'password'`` starts a new session in the authentication server.
|
|
46
47
|
It uses fields ``client_id``, ``grant_type``, ``username`` and ``password``.
|
|
47
48
|
* Token request with grant type ``'refresh_token'`` is used for maintaining an existing session.
|
|
@@ -51,6 +51,7 @@ def start_token_manager(cycle: int, config: ConfigFile, single_run: bool = False
|
|
|
51
51
|
"""Refresh tokens periodically.
|
|
52
52
|
|
|
53
53
|
For each refresh cycle new tokens are requested from auth server.
|
|
54
|
+
|
|
54
55
|
- If refresh is successful next refresh is attempted in the next cycle.
|
|
55
56
|
- If auth server does not respond refresh is attempted repeatedly until it succeeds or
|
|
56
57
|
the existing refresh token expires.
|
|
@@ -104,11 +105,10 @@ def refresh_tokens(config: ConfigFile, current_tokens: dict, cycle: int) -> tupl
|
|
|
104
105
|
cycle: refresh cycle length in seconds
|
|
105
106
|
|
|
106
107
|
Returns:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
sleep_time: time to sleep before next refresh attempt
|
|
108
|
+
* dict containing new tokens or current tokens if auth server could not be connected or
|
|
109
|
+
None if auth server refused to provide new tokens
|
|
110
|
+
* bool, True if tokens were refreshed successfully, False otherwise
|
|
111
|
+
* time to sleep before next refresh attempt
|
|
112
112
|
|
|
113
113
|
"""
|
|
114
114
|
access_token = current_tokens.get("access_token", "")
|
|
@@ -20,6 +20,7 @@ from functools import lru_cache
|
|
|
20
20
|
from http import HTTPStatus
|
|
21
21
|
from importlib.metadata import version
|
|
22
22
|
import json
|
|
23
|
+
import logging
|
|
23
24
|
import os
|
|
24
25
|
import platform
|
|
25
26
|
import time
|
|
@@ -77,6 +78,9 @@ DEFAULT_TIMEOUT_SECONDS = 900
|
|
|
77
78
|
SECONDS_BETWEEN_CALLS = float(os.environ.get("IQM_CLIENT_SECONDS_BETWEEN_CALLS", 1.0))
|
|
78
79
|
|
|
79
80
|
|
|
81
|
+
logger = logging.getLogger(__name__)
|
|
82
|
+
|
|
83
|
+
|
|
80
84
|
class IQMClient:
|
|
81
85
|
"""Provides access to IQM quantum computers.
|
|
82
86
|
|
|
@@ -907,11 +911,18 @@ class IQMClient:
|
|
|
907
911
|
# model = model_class.model_validate_json(response.text)
|
|
908
912
|
except json.decoder.JSONDecodeError as e:
|
|
909
913
|
raise EndpointRequestError(f"Invalid response: {response.text}, {e!r}") from e
|
|
914
|
+
|
|
910
915
|
return model
|
|
911
916
|
|
|
912
|
-
def
|
|
917
|
+
def get_calibration_quality_metrics(self, calibration_set_id: UUID | None = None) -> ObservationFinder:
|
|
913
918
|
"""Retrieve the given calibration set and related quality metrics from the server.
|
|
914
919
|
|
|
920
|
+
.. warning::
|
|
921
|
+
|
|
922
|
+
This method is an experimental interface to the quality metrics and calibration data.
|
|
923
|
+
The API may change considerably in the next versions *with no backwards compatibility*,
|
|
924
|
+
including the API of the ObservationFinder class.
|
|
925
|
+
|
|
915
926
|
Args:
|
|
916
927
|
calibration_set_id: ID of the calibration set to retrieve.
|
|
917
928
|
If ``None``, the current default calibration set is retrieved.
|
|
@@ -925,8 +936,16 @@ class IQMClient:
|
|
|
925
936
|
HTTPException: HTTP exceptions
|
|
926
937
|
|
|
927
938
|
"""
|
|
939
|
+
logger.warning(
|
|
940
|
+
"IQMClient.get_calibration_quality_metrics is an experimental method, and the API will likely change "
|
|
941
|
+
"in the future with no backwards compatibility."
|
|
942
|
+
)
|
|
943
|
+
return self._get_calibration_quality_metrics(calibration_set_id)
|
|
944
|
+
|
|
945
|
+
def _get_calibration_quality_metrics(self, calibration_set_id: UUID | None = None) -> ObservationFinder:
|
|
946
|
+
"""See :meth:`get_calibration_quality_metrics`."""
|
|
928
947
|
if isinstance(self._station_control, IqmServerClient):
|
|
929
|
-
raise ValueError("The
|
|
948
|
+
raise ValueError("The _get_calibration_quality_metrics method is not supported by IqmServerClient.")
|
|
930
949
|
|
|
931
950
|
if not calibration_set_id:
|
|
932
951
|
# find out the default calset id
|
|
@@ -936,5 +955,4 @@ class IQMClient:
|
|
|
936
955
|
calset_obs = self._station_control.get_observation_set_observations(calibration_set_id)
|
|
937
956
|
quality_metrics = self._station_control.get_calibration_set_quality_metrics(calibration_set_id)
|
|
938
957
|
qm_obs = quality_metrics.observations
|
|
939
|
-
|
|
940
|
-
return ObservationFinder(list(calset_obs) + qm_obs)
|
|
958
|
+
return ObservationFinder(calset_obs + qm_obs)
|
|
@@ -14,12 +14,10 @@
|
|
|
14
14
|
"""Helpful utilities that can be used together with IQMClient."""
|
|
15
15
|
|
|
16
16
|
from json import JSONEncoder, dumps, loads
|
|
17
|
-
from typing import Any
|
|
17
|
+
from typing import Any
|
|
18
18
|
|
|
19
19
|
import numpy as np
|
|
20
20
|
|
|
21
|
-
T = TypeVar("T")
|
|
22
|
-
|
|
23
21
|
|
|
24
22
|
class IQMJSONEncoder(JSONEncoder):
|
|
25
23
|
"""JSONEncoder that that adds support for some non-JSON datatypes"""
|
|
@@ -36,8 +36,8 @@ def resonance_example(server_url: str, api_token: str | None) -> dict[str, int]:
|
|
|
36
36
|
"""
|
|
37
37
|
SHOTS = 1000
|
|
38
38
|
|
|
39
|
-
# Initialize a backend
|
|
40
|
-
backend = IQMProvider(server_url, token=api_token).get_backend()
|
|
39
|
+
# Initialize a backend without metrics as IQMClient._get_calibration_quality_metrics is not supported by resonance
|
|
40
|
+
backend = IQMProvider(server_url, token=api_token).get_backend(use_metrics=False)
|
|
41
41
|
|
|
42
42
|
# Just to make sure that "get_static_quantum_architecture" method works
|
|
43
43
|
static_quantum_architecture = backend.client.get_static_quantum_architecture()
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Copyright 2022-2025 Qiskit on IQM developers
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Qiskit backend for IQM quantum computers."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from abc import ABC
|
|
19
|
+
import logging
|
|
20
|
+
from typing import Final
|
|
21
|
+
|
|
22
|
+
from iqm.iqm_client import (
|
|
23
|
+
DynamicQuantumArchitecture,
|
|
24
|
+
ObservationFinder,
|
|
25
|
+
)
|
|
26
|
+
from iqm.qiskit_iqm.iqm_target import IQMTarget
|
|
27
|
+
from qiskit.providers import BackendV2
|
|
28
|
+
from qiskit.transpiler import Target
|
|
29
|
+
|
|
30
|
+
IQM_TO_QISKIT_GATE_NAME: Final[dict[str, str]] = {"prx": "r", "cz": "cz"}
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class IQMBackendBase(BackendV2, ABC):
|
|
35
|
+
"""Abstract base class for various IQM-specific backends.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
architecture: Dynamic quantum architecture associated with the backend instance.
|
|
39
|
+
metrics: Optional calibration data and related quality metrics for the transpilation target.
|
|
40
|
+
name: Optional name for the backend instance.
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
architecture: DynamicQuantumArchitecture,
|
|
47
|
+
*,
|
|
48
|
+
metrics: ObservationFinder | None = None,
|
|
49
|
+
name: str = "IQMBackend",
|
|
50
|
+
**kwargs,
|
|
51
|
+
):
|
|
52
|
+
super().__init__(name=name, **kwargs)
|
|
53
|
+
self.architecture = architecture
|
|
54
|
+
self.metrics = metrics
|
|
55
|
+
# Qiskit uses integer indices to refer to qubits, so we need to map component names to indices.
|
|
56
|
+
# Because of the way the Target and the transpiler interact, the resonators need to have higher indices than
|
|
57
|
+
# qubits, or else transpiling with optimization_level=0 will fail because of lacking resonator indices.
|
|
58
|
+
qb_to_idx = {qb: idx for idx, qb in enumerate(architecture.qubits + architecture.computational_resonators)}
|
|
59
|
+
|
|
60
|
+
# NOTE: both targets include fictional CZs
|
|
61
|
+
self._target = IQMTarget(
|
|
62
|
+
architecture=architecture,
|
|
63
|
+
component_to_idx=qb_to_idx,
|
|
64
|
+
include_resonators=False,
|
|
65
|
+
include_fictional_czs=True,
|
|
66
|
+
metrics=metrics,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
self._target_with_resonators = (
|
|
70
|
+
IQMTarget(
|
|
71
|
+
architecture=architecture,
|
|
72
|
+
component_to_idx=qb_to_idx,
|
|
73
|
+
include_resonators=True,
|
|
74
|
+
include_fictional_czs=True,
|
|
75
|
+
metrics=metrics,
|
|
76
|
+
)
|
|
77
|
+
if "move" in architecture.gates
|
|
78
|
+
else None
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
self._qb_to_idx = qb_to_idx
|
|
82
|
+
self._idx_to_qb = {v: k for k, v in qb_to_idx.items()}
|
|
83
|
+
self._coupling_map = self.target.build_coupling_map()
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def target(self) -> Target:
|
|
87
|
+
"""Return the target without computational resonators."""
|
|
88
|
+
return self._target
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def target_with_resonators(self) -> Target:
|
|
92
|
+
"""Return the target with MOVE gates and resonators included.
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
ValueError: The backend does not have resonators.
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
target = self._target_with_resonators
|
|
99
|
+
if target is None:
|
|
100
|
+
raise ValueError("The backend does not have computational resonators.")
|
|
101
|
+
return target
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def physical_qubits(self) -> list[str]:
|
|
105
|
+
"""Return the list of physical qubits in the backend."""
|
|
106
|
+
return list(self._qb_to_idx)
|
|
107
|
+
|
|
108
|
+
def has_resonators(self) -> bool:
|
|
109
|
+
"""True iff the backend QPU has computational resonators."""
|
|
110
|
+
return bool(self.architecture.computational_resonators)
|
|
111
|
+
|
|
112
|
+
def get_real_target(self) -> Target:
|
|
113
|
+
"""Return the real physical target of the backend without fictional CZ gates."""
|
|
114
|
+
return IQMTarget(
|
|
115
|
+
architecture=self.architecture,
|
|
116
|
+
component_to_idx=self._qb_to_idx,
|
|
117
|
+
include_resonators=True,
|
|
118
|
+
include_fictional_czs=False,
|
|
119
|
+
metrics=self.metrics,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def qubit_name_to_index(self, name: str) -> int:
|
|
123
|
+
"""Given an IQM-style qubit name, return the corresponding index in the register.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
name: IQM-style qubit name ('QB1', 'QB2', etc.)
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Index of the given qubit in the quantum register.
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
ValueError: Qubit name cannot be found on the backend.
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
if name not in self._qb_to_idx:
|
|
136
|
+
raise ValueError(f"Qubit '{name}' is not found on the backend.")
|
|
137
|
+
return self._qb_to_idx[name]
|
|
138
|
+
|
|
139
|
+
def index_to_qubit_name(self, index: int) -> str:
|
|
140
|
+
"""Given a quantum register index, return the corresponding IQM-style qubit name.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
index: Qubit index in the quantum register.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Corresponding IQM-style qubit name ('QB1', 'QB2', etc.).
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
ValueError: Qubit index cannot be found on the backend.
|
|
150
|
+
|
|
151
|
+
"""
|
|
152
|
+
if index not in self._idx_to_qb:
|
|
153
|
+
raise ValueError(f"Qubit index {index} is not found on the backend.")
|
|
154
|
+
return self._idx_to_qb[index]
|
|
155
|
+
|
|
156
|
+
def get_scheduling_stage_plugin(self) -> str:
|
|
157
|
+
"""Return the plugin that should be used for scheduling the circuits on this backend."""
|
|
158
|
+
return "iqm_default_scheduling"
|
|
@@ -11,12 +11,10 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
"""Generate an initial layout for a quantum circuit
|
|
15
|
-
valid on the quantum architecture specification of the given backend.
|
|
16
|
-
"""
|
|
14
|
+
"""Generate an initial layout for a quantum circuit containing MOVE gates."""
|
|
17
15
|
|
|
18
16
|
from iqm.iqm_client import DynamicQuantumArchitecture
|
|
19
|
-
from iqm.qiskit_iqm.iqm_backend import
|
|
17
|
+
from iqm.qiskit_iqm.iqm_backend import IQMTarget
|
|
20
18
|
from qiskit import QuantumCircuit
|
|
21
19
|
from qiskit.circuit import Qubit
|
|
22
20
|
from qiskit.dagcircuit import DAGCircuit
|
|
@@ -228,26 +226,20 @@ class IQMMoveLayout(TrivialLayout):
|
|
|
228
226
|
|
|
229
227
|
|
|
230
228
|
def generate_initial_layout(
|
|
231
|
-
|
|
229
|
+
target: IQMTarget,
|
|
232
230
|
circuit: QuantumCircuit,
|
|
233
|
-
restrict_to_qubits: list[int] | list[str] | None = None,
|
|
234
231
|
) -> Layout:
|
|
235
|
-
"""
|
|
232
|
+
"""Generate an initial layout for the given circuit, for running on the given Star architecture target.
|
|
236
233
|
|
|
237
234
|
Args:
|
|
238
|
-
|
|
239
|
-
circuit:
|
|
240
|
-
restrict_to_qubits: Optional list of qubits to restrict the layout to.
|
|
235
|
+
target: IQM Star architecture target to run the circuit on, without fictional gates.
|
|
236
|
+
circuit: Quantum circuit for which a layout is to be generated.
|
|
241
237
|
|
|
242
238
|
Returns:
|
|
243
|
-
Layout that maps the logical qubits of ``circuit`` to the physical qubits of ``
|
|
239
|
+
Layout that maps the virtual/logical qubits of ``circuit`` to the physical qubits of ``target`` so that
|
|
244
240
|
all the gates in ``circuit`` are available on those loci.
|
|
245
241
|
|
|
246
242
|
"""
|
|
247
|
-
target = backend.get_real_target()
|
|
248
|
-
if restrict_to_qubits is not None:
|
|
249
|
-
target = target.restrict_to_qubits(restrict_to_qubits)
|
|
250
|
-
|
|
251
243
|
layout_gen = IQMMoveLayout(target)
|
|
252
244
|
pm = PassManager(layout_gen)
|
|
253
245
|
pm.run(circuit)
|
|
@@ -200,7 +200,7 @@ def _get_scheduling_method(
|
|
|
200
200
|
def transpile_to_IQM( # noqa: PLR0913
|
|
201
201
|
circuit: QuantumCircuit,
|
|
202
202
|
backend: IQMBackendBase,
|
|
203
|
-
|
|
203
|
+
*,
|
|
204
204
|
initial_layout: Layout | dict | list | None = None,
|
|
205
205
|
perform_move_routing: bool = True,
|
|
206
206
|
optimize_single_qubits: bool = True,
|
|
@@ -214,16 +214,14 @@ def transpile_to_IQM( # noqa: PLR0913
|
|
|
214
214
|
|
|
215
215
|
Works with both the Crystal and Star architectures.
|
|
216
216
|
|
|
217
|
-
Note: When transpiling a circuit with MOVE gates, you might need to set
|
|
218
|
-
If
|
|
217
|
+
Note: When transpiling a circuit with MOVE gates, you might need to set ``optimization_level`` lower.
|
|
218
|
+
If ``optimization_level`` is set too high, the transpiler might add single qubit gates onto the resonator,
|
|
219
219
|
which is not supported by the IQM Star architectures. If this in undesired, it is best to have the transpiler
|
|
220
220
|
add the MOVE gates automatically, rather than manually adding them to the circuit.
|
|
221
221
|
|
|
222
222
|
Args:
|
|
223
|
-
circuit: The circuit to
|
|
224
|
-
backend: The
|
|
225
|
-
target: An alternative target to compile to than the backend, using this option requires intimate knowledge
|
|
226
|
-
of the transpiler and thus it is not recommended to use.
|
|
223
|
+
circuit: The circuit to transpile.
|
|
224
|
+
backend: The backend to transpile to.
|
|
227
225
|
initial_layout: The initial layout to use for the transpilation, same as :func:`~qiskit.compiler.transpile`.
|
|
228
226
|
perform_move_routing: Whether to perform MOVE gate routing.
|
|
229
227
|
optimize_single_qubits: Whether to optimize single qubit gates away.
|
|
@@ -233,30 +231,35 @@ def transpile_to_IQM( # noqa: PLR0913
|
|
|
233
231
|
existing_moves_handling: How to handle existing MOVE gates in the circuit, required if the circuit contains
|
|
234
232
|
MOVE gates.
|
|
235
233
|
restrict_to_qubits: Restrict the transpilation to only use these specific physical qubits. Note that you will
|
|
236
|
-
have to pass this information to
|
|
234
|
+
also have to pass this information to :meth:`.IQMBackend.run` using the ``qubit_mapping`` parameter.
|
|
237
235
|
qiskit_transpiler_kwargs: Arguments to be passed to the Qiskit transpiler.
|
|
238
236
|
|
|
239
237
|
Returns:
|
|
240
238
|
Transpiled circuit ready for running on the backend.
|
|
241
239
|
|
|
242
240
|
"""
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
initial_layout = generate_initial_layout(backend, circuit, restrict_to_qubits)
|
|
252
|
-
if perform_move_routing and existing_moves_handling is None:
|
|
253
|
-
raise ValueError("The circuit contains MOVE gates but existing_moves_handling is not set.")
|
|
254
|
-
else:
|
|
255
|
-
target = backend.target
|
|
241
|
+
circuit_has_moves = circuit.count_ops().get("move", 0) > 0
|
|
242
|
+
# get the target from the backend
|
|
243
|
+
if circuit_has_moves:
|
|
244
|
+
target = backend.target_with_resonators
|
|
245
|
+
if perform_move_routing and existing_moves_handling is None:
|
|
246
|
+
raise ValueError("The circuit contains MOVE gates but existing_moves_handling is not set.")
|
|
247
|
+
else:
|
|
248
|
+
target = backend.target
|
|
256
249
|
|
|
257
250
|
if restrict_to_qubits is not None:
|
|
251
|
+
# qubit names to qiskit indices
|
|
252
|
+
restrict_to_qubits = [backend.qubit_name_to_index(q) if isinstance(q, str) else q for q in restrict_to_qubits]
|
|
258
253
|
target = target.restrict_to_qubits(restrict_to_qubits)
|
|
259
254
|
|
|
255
|
+
if circuit_has_moves and initial_layout is None:
|
|
256
|
+
# Create a sensible initial layout if none is provided, since
|
|
257
|
+
# the standard Qiskit transpile function does not do this well with resonators.
|
|
258
|
+
real_target = backend.get_real_target()
|
|
259
|
+
if restrict_to_qubits is not None:
|
|
260
|
+
real_target = real_target.restrict_to_qubits(restrict_to_qubits)
|
|
261
|
+
initial_layout = generate_initial_layout(real_target, circuit)
|
|
262
|
+
|
|
260
263
|
# Determine which scheduling method to use
|
|
261
264
|
scheduling_method = qiskit_transpiler_kwargs.pop("scheduling_method", None)
|
|
262
265
|
if scheduling_method is None:
|