iqm-client 29.14.0__tar.gz → 30.1.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.
Files changed (110) hide show
  1. {iqm_client-29.14.0 → iqm_client-30.1.0}/CHANGELOG.rst +32 -4
  2. {iqm_client-29.14.0 → iqm_client-30.1.0}/PKG-INFO +1 -1
  3. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/user_guide_qiskit.rst +21 -4
  4. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/cli/auth.py +1 -0
  5. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/cli/token_manager.py +5 -5
  6. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/iqm_client.py +22 -4
  7. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/util.py +1 -3
  8. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/examples/resonance_example.py +1 -1
  9. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/fake_backends/fake_adonis.py +0 -1
  10. iqm_client-30.1.0/src/iqm/qiskit_iqm/iqm_backend.py +158 -0
  11. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/iqm_move_layout.py +7 -15
  12. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/iqm_naive_move_pass.py +24 -21
  13. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/iqm_provider.py +66 -34
  14. iqm_client-30.1.0/src/iqm/qiskit_iqm/iqm_target.py +372 -0
  15. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm_client.egg-info/PKG-INFO +1 -1
  16. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm_client.egg-info/SOURCES.txt +2 -0
  17. iqm_client-30.1.0/tests/__init__.py +0 -0
  18. {iqm_client-29.14.0 → iqm_client-30.1.0}/tests/conftest.py +57 -0
  19. iqm_client-30.1.0/version.txt +1 -0
  20. iqm_client-29.14.0/src/iqm/qiskit_iqm/iqm_backend.py +0 -345
  21. iqm_client-29.14.0/version.txt +0 -1
  22. {iqm_client-29.14.0 → iqm_client-30.1.0}/AUTHORS.rst +0 -0
  23. {iqm_client-29.14.0 → iqm_client-30.1.0}/CHANGELOG_cirq-iqm.rst +0 -0
  24. {iqm_client-29.14.0 → iqm_client-30.1.0}/CHANGELOG_cortex-cli.rst +0 -0
  25. {iqm_client-29.14.0 → iqm_client-30.1.0}/CHANGELOG_qiskit-iqm.rst +0 -0
  26. {iqm_client-29.14.0 → iqm_client-30.1.0}/INTEGRATION_GUIDE.rst +0 -0
  27. {iqm_client-29.14.0 → iqm_client-30.1.0}/LICENSE.txt +0 -0
  28. {iqm_client-29.14.0 → iqm_client-30.1.0}/MANIFEST.in +0 -0
  29. {iqm_client-29.14.0 → iqm_client-30.1.0}/README.rst +0 -0
  30. {iqm_client-29.14.0 → iqm_client-30.1.0}/docbuild +0 -0
  31. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/API.rst +0 -0
  32. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/_static/images/favicon.ico +0 -0
  33. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/_static/images/logo.png +0 -0
  34. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/_templates/autosummary-class-template.rst +0 -0
  35. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/_templates/autosummary-module-template.rst +0 -0
  36. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/authors.rst +0 -0
  37. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/changelog.rst +0 -0
  38. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/conf.py +0 -0
  39. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/index.rst +0 -0
  40. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/integration_guide.rst +0 -0
  41. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/license.rst +0 -0
  42. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/readme.rst +0 -0
  43. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/user_guide_cirq.rst +0 -0
  44. {iqm_client-29.14.0 → iqm_client-30.1.0}/docs/user_guide_cli.rst +0 -0
  45. {iqm_client-29.14.0 → iqm_client-30.1.0}/pyproject.toml +0 -0
  46. /iqm_client-29.14.0/src/iqm/cirq_iqm/py.typed → /iqm_client-30.1.0/pytest.ini +0 -0
  47. {iqm_client-29.14.0 → iqm_client-30.1.0}/requirements/base.in +0 -0
  48. {iqm_client-29.14.0 → iqm_client-30.1.0}/requirements/base.in.internal +0 -0
  49. {iqm_client-29.14.0 → iqm_client-30.1.0}/requirements/base.txt +0 -0
  50. {iqm_client-29.14.0 → iqm_client-30.1.0}/requirements/cirq.in +0 -0
  51. {iqm_client-29.14.0 → iqm_client-30.1.0}/requirements/cirq.txt +0 -0
  52. {iqm_client-29.14.0 → iqm_client-30.1.0}/requirements/cli.in +0 -0
  53. {iqm_client-29.14.0 → iqm_client-30.1.0}/requirements/cli.txt +0 -0
  54. {iqm_client-29.14.0 → iqm_client-30.1.0}/requirements/qiskit.in +0 -0
  55. {iqm_client-29.14.0 → iqm_client-30.1.0}/requirements/qiskit.txt +0 -0
  56. {iqm_client-29.14.0 → iqm_client-30.1.0}/setup.cfg +0 -0
  57. {iqm_client-29.14.0 → iqm_client-30.1.0}/setup.py +0 -0
  58. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/__init__.py +0 -0
  59. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/devices/__init__.py +0 -0
  60. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/devices/adonis.py +0 -0
  61. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/devices/aphrodite.py +0 -0
  62. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/devices/apollo.py +0 -0
  63. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/devices/iqm_device.py +0 -0
  64. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/devices/iqm_device_metadata.py +0 -0
  65. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/examples/demo_adonis.py +0 -0
  66. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/examples/demo_apollo.py +0 -0
  67. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/examples/demo_common.py +0 -0
  68. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/examples/demo_iqm_execution.py +0 -0
  69. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/examples/usage.ipynb +0 -0
  70. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/extended_qasm_parser.py +0 -0
  71. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/iqm_gates.py +0 -0
  72. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/iqm_sampler.py +0 -0
  73. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/optimizers.py +0 -0
  74. {iqm_client-29.14.0/src/iqm/iqm_client → iqm_client-30.1.0/src/iqm/cirq_iqm}/py.typed +0 -0
  75. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/serialize.py +0 -0
  76. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/cirq_iqm/transpiler.py +0 -0
  77. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/__init__.py +0 -0
  78. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/api.py +0 -0
  79. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/authentication.py +0 -0
  80. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/cli/__init__.py +0 -0
  81. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/cli/cli.py +0 -0
  82. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/cli/models.py +0 -0
  83. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/errors.py +0 -0
  84. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/models.py +0 -0
  85. {iqm_client-29.14.0/src/iqm/qiskit_iqm → iqm_client-30.1.0/src/iqm/iqm_client}/py.typed +0 -0
  86. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/transpile.py +0 -0
  87. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/iqm_client/validation.py +0 -0
  88. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/__init__.py +0 -0
  89. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/examples/__init__.py +0 -0
  90. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/examples/bell_measure.py +0 -0
  91. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/examples/transpile_example.py +0 -0
  92. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/fake_backends/__init__.py +0 -0
  93. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/fake_backends/fake_aphrodite.py +0 -0
  94. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/fake_backends/fake_apollo.py +0 -0
  95. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/fake_backends/fake_deneb.py +0 -0
  96. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/fake_backends/fake_garnet.py +0 -0
  97. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/fake_backends/iqm_fake_backend.py +0 -0
  98. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/iqm_circuit.py +0 -0
  99. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/iqm_circuit_validation.py +0 -0
  100. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/iqm_job.py +0 -0
  101. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/iqm_transpilation.py +0 -0
  102. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/move_gate.py +0 -0
  103. /iqm_client-29.14.0/tests/__init__.py → /iqm_client-30.1.0/src/iqm/qiskit_iqm/py.typed +0 -0
  104. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/qiskit_to_iqm.py +0 -0
  105. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm/qiskit_iqm/transpiler_plugins.py +0 -0
  106. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm_client.egg-info/dependency_links.txt +0 -0
  107. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm_client.egg-info/entry_points.txt +0 -0
  108. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm_client.egg-info/requires.txt +0 -0
  109. {iqm_client-29.14.0 → iqm_client-30.1.0}/src/iqm_client.egg-info/top_level.txt +0 -0
  110. {iqm_client-29.14.0 → iqm_client-30.1.0}/test +0 -0
@@ -2,6 +2,34 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ Version 30.1.0 (2025-08-28)
6
+ ===========================
7
+
8
+ Bug fixes
9
+ ---------
10
+
11
+ - fix initialization of backends on resonance
12
+
13
+ Version 30.0.0 (2025-08-20)
14
+ ===========================
15
+
16
+ Features
17
+ --------
18
+
19
+ - Add the experimental :meth:`IQMClient.get_calibration_quality_metrics` method to retrieve calibration set
20
+ and related quality metrics from the server.
21
+ - Select calibration and quality metric data are now available in :class:`IQMTarget`,
22
+ :class:`IQMBackend`, and :class:`IQMBackendBase` for transpilation and querying. :issue:`SW-769`.
23
+ This data includes the gate durations. :issue:`SW-1321`
24
+ - :func:`transpile_to_IQM` no longer has the ``target`` parameter, the transpilation target is always
25
+ obtained from ``backend``.
26
+ - :class:`IQMTarget` moved into its own module.
27
+ - :class:`IQMFacadeBackend` can be given the name of the :class:`IQMFakeBackend` instance to use.
28
+
29
+ Breaking Changes
30
+ ----------------
31
+ - :func:`generate_initial_layout` takes an :class:`IQMTarget` as an input argument instead of :class:`IQMBackend`.
32
+
5
33
  Version 29.14.0 (2025-08-20)
6
34
  ============================
7
35
 
@@ -32,7 +60,7 @@ Version 29.11.0 (2025-08-07)
32
60
  Features
33
61
  --------
34
62
 
35
- - Added :meth:`IQMClient.get_structured_metrics` for obtaining quality metric information.
63
+ - Added :meth:`IQMClient._get_calibration_quality_metrics` for obtaining quality metric and calibration information.
36
64
 
37
65
  Version 29.10.0 (2025-07-31)
38
66
  ============================
@@ -57,7 +85,7 @@ Features
57
85
  --------
58
86
 
59
87
  - An update to IQMFacadeBackend such that all available IQMFakeBackends are usable for simulation, rather than
60
- just IQMFakeAdonis
88
+ just IQMFakeAdonis.
61
89
 
62
90
  Version 29.7.0 (2025-07-15)
63
91
  ===========================
@@ -352,7 +380,7 @@ Version 22.16.0 (2025-04-03)
352
380
  ============================
353
381
 
354
382
  Feature
355
- *******
383
+ -------
356
384
 
357
385
  - Enable PEP 604 in linting rules, :issue:`SW-1230`.
358
386
 
@@ -368,7 +396,7 @@ Version 22.14.0 (2025-04-02)
368
396
  ============================
369
397
 
370
398
  Features
371
- ********
399
+ --------
372
400
 
373
401
  - Update the documentation footer to display the package version.
374
402
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-client
3
- Version: 29.14.0
3
+ Version: 30.1.0
4
4
  Summary: Client library for accessing an IQM quantum computer
5
5
  Author-email: IQM Finland Oy <developers@meetiqm.com>
6
6
  License: Apache License
@@ -174,6 +174,15 @@ circuit(s) are sampled:
174
174
  `Inspecting circuits before submitting them for execution`_ for inspecting the actual run request sent for
175
175
  execution.
176
176
 
177
+ .. note::
178
+
179
+ As of ``iqm-client >= 30.1.0``, structured quality metrics and calibration data are available to
180
+ ``IQMTarget`` for improved transpilation. To import the latest valid quality metric data corresponding
181
+ to the default calibration set into ``IQMTarget``, set ``use_metrics`` to ``True`` when initializing the
182
+ class. For Resonance users, this data is not yet available via the Resonance API, so use the default setting
183
+ of ``use_metrics`` of ``False``.
184
+
185
+
177
186
  You can optionally provide IQMBackend specific options as additional keyword arguments to
178
187
  :meth:`.IQMBackend.run`, documented at :meth:`.IQMBackend.create_run_request`.
179
188
  For example, you can enable heralding measurements using ``circuit_compilation_options`` as follows:
@@ -574,6 +583,7 @@ mapping that matches the restriction:
574
583
  .. code-block:: python
575
584
 
576
585
  qubit_mapping = {i: backend.index_to_qubit_name(q) for i, q in enumerate(qubits)}
586
+ # or qubit_mapping = dict(enumerate(qubits))
577
587
  job = backend.run(transpiled_circuit, qubit_mapping=qubit_mapping)
578
588
 
579
589
 
@@ -731,10 +741,17 @@ Results from such executions are random bits. This may be useful when developing
731
741
  Qiskit on IQM contains :class:`.IQMFacadeBackend`, which allows to combine the mock remote execution with a local
732
742
  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
743
 
734
- To run a circuit this way, use the ``"facade_x"``, (where `x` is one of the adonis, aprhodite, apollo, deneb or garnet architectures) backend retrieved from the provider.
735
- Note that the provider must be initialized with the URL of a quantum computer with the equivalent architecture (i.e. names of qubits, their
736
- connectivity, and the native gateset should match the desired architecture).
737
- Additionally, the URL should point to a mock environment rather than a real device as the execution results from the remote will be discarded and replaced by a simulated result generated by Qiskit Aer.
744
+ To run a circuit this way, use :meth:`IQMProvider.get_backend` with one of the backend names
745
+ ``facade_adonis, facade_apollo, facade_aphrodite, facade_deneb`` or ``facade_garnet`` to obtain
746
+ a facade backend.
747
+ ``facade_adonis, facade_apollo, facade_aphrodite`` represent typical Crystal 5, Crystal 20 and Crystal 54 architecture QPUs,
748
+ whereas ``facade_deneb`` and ``facade_garnet`` represent the corresponding IQM Resonance quantum computers
749
+ more specifically.
750
+
751
+ Note that the provider must be initialized with the URL of a quantum computer with the corresponding
752
+ static quantum architecture (i.e. names of qubits, their connectivity, and the native gateset should match the desired architecture).
753
+ Additionally, the URL should point to a mock server rather than a real quantum computer, as the execution results
754
+ from the server will be discarded and replaced by a simulated result generated by Qiskit Aer.
738
755
 
739
756
  .. code-block:: python
740
757
 
@@ -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
- Tuple[Optional[dict], bool, int] = (tokens, status, sleep_time)
108
- tokens: 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
- status: bool, True if tokens were refreshed successfully, False otherwise
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 get_structured_metrics(self, calibration_set_id: UUID | None = None) -> ObservationFinder:
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 get_structured_metrics method is not supported by IqmServerClient.")
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, TypeVar
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,7 +36,7 @@ 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
39
+ # Initialize a backend without metrics as IQMClient._get_calibration_quality_metrics is not supported by resonance
40
40
  backend = IQMProvider(server_url, token=api_token).get_backend()
41
41
 
42
42
  # Just to make sure that "get_static_quantum_architecture" method works
@@ -44,5 +44,4 @@ def IQMFakeAdonis() -> IQMFakeBackend:
44
44
  },
45
45
  name="sample-chip",
46
46
  )
47
-
48
47
  return IQMFakeBackend(architecture, error_profile, name="IQMFakeAdonisBackend")
@@ -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 that is
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 IQMBackendBase, IQMTarget
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
- backend: IQMBackendBase,
229
+ target: IQMTarget,
232
230
  circuit: QuantumCircuit,
233
- restrict_to_qubits: list[int] | list[str] | None = None,
234
231
  ) -> Layout:
235
- """Generates an initial layout for the given circuit, when run against the given backend.
232
+ """Generate an initial layout for the given circuit, for running on the given Star architecture target.
236
233
 
237
234
  Args:
238
- backend: IQM backend to run against.
239
- circuit: Star architecture circuit for which a layout is to be generated.
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 ``backend`` so that
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
- target: IQMTarget | None = None,
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 the `optimization_level` lower.
218
- If the `optimization_level` is set too high, the transpiler might add single qubit gates onto the resonator,
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 be transpiled without MOVE gates.
224
- backend: The target backend to compile to. Does not require a resonator.
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 the ``backend.run`` method as well as a dictionary.
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
- if restrict_to_qubits is not None:
244
- restrict_to_qubits = [backend.qubit_name_to_index(q) if isinstance(q, str) else q for q in restrict_to_qubits]
245
-
246
- if target is None:
247
- if circuit.count_ops().get("move", 0) > 0 or circuit.num_qubits > backend.num_qubits:
248
- target = backend.target_with_resonators
249
- # Create a sensible initial layout if none is provided
250
- if initial_layout is None:
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: