iqm-pulla 7.23.0__tar.gz → 8.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.
Files changed (77) hide show
  1. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/CHANGELOG.rst +9 -0
  2. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/PKG-INFO +8 -8
  3. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/Quick Start.ipynb +1 -1
  4. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/requirements/base.in +1 -1
  5. iqm_pulla-8.0.0/requirements/base.txt +6 -0
  6. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/requirements/notebook.txt +1 -1
  7. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/requirements/qir.txt +1 -1
  8. iqm_pulla-8.0.0/requirements/qiskit.txt +5 -0
  9. iqm_pulla-8.0.0/src/iqm/pulla/calibration.py +95 -0
  10. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/pulla/pulla.py +19 -19
  11. iqm_pulla-8.0.0/src/iqm/pulla/quantum_architecture.py +142 -0
  12. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/pulla/utils.py +1 -2
  13. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm_pulla.egg-info/PKG-INFO +8 -8
  14. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm_pulla.egg-info/SOURCES.txt +1 -0
  15. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm_pulla.egg-info/requires.txt +7 -7
  16. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/tests/conftest.py +16 -10
  17. iqm_pulla-8.0.0/version.txt +1 -0
  18. iqm_pulla-7.23.0/requirements/base.txt +0 -6
  19. iqm_pulla-7.23.0/requirements/qiskit.txt +0 -5
  20. iqm_pulla-7.23.0/src/iqm/pulla/calibration.py +0 -56
  21. iqm_pulla-7.23.0/version.txt +0 -1
  22. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/AUTHORS.rst +0 -0
  23. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/LICENSE.txt +0 -0
  24. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/MANIFEST.in +0 -0
  25. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/README.rst +0 -0
  26. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/API.rst +0 -0
  27. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/Compilation Stages.ipynb +0 -0
  28. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/Configuration and Usage.ipynb +0 -0
  29. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/Custom Gates and Implementations.ipynb +0 -0
  30. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/Example - Compilation With Local Calibration Set.ipynb +0 -0
  31. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/Example - Executing QIR programs.ipynb +0 -0
  32. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/Example - Measuring T1.ipynb +0 -0
  33. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/Example - Randomized Benchmarking.ipynb +0 -0
  34. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/Example - Simple Dynamical Decoupling.ipynb +0 -0
  35. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/_static/images/favicon.ico +0 -0
  36. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/_static/images/logo.png +0 -0
  37. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/_templates/autosummary-class-template.rst +0 -0
  38. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/_templates/autosummary-module-template.rst +0 -0
  39. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/authors.rst +0 -0
  40. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/changelog.rst +0 -0
  41. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/common_errors.rst +0 -0
  42. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/conf.py +0 -0
  43. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/examples.rst +0 -0
  44. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/index.rst +0 -0
  45. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/license.rst +0 -0
  46. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/migration_guide.rst +0 -0
  47. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/readme.rst +0 -0
  48. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/references.bib +0 -0
  49. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/references.rst +0 -0
  50. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/docs/user_guides.rst +0 -0
  51. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/pyproject.toml +0 -0
  52. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/requirements/notebook.in +0 -0
  53. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/requirements/qir.in +0 -0
  54. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/requirements/qiskit.in +0 -0
  55. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/setup.cfg +0 -0
  56. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/setup.py +0 -0
  57. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/cpc/__init__.py +0 -0
  58. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/cpc/compiler/__init__.py +0 -0
  59. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/cpc/compiler/circuit_compilation_request_handler.py +0 -0
  60. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/cpc/compiler/compiler.py +0 -0
  61. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/cpc/compiler/dd.py +0 -0
  62. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/cpc/compiler/errors.py +0 -0
  63. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/cpc/compiler/standard_stages.py +0 -0
  64. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/cpc/compiler/station_settings.py +0 -0
  65. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/cpc/interface/__init__.py +0 -0
  66. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/cpc/interface/compiler.py +0 -0
  67. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/cpc/py.typed +0 -0
  68. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/pulla/__init__.py +0 -0
  69. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/pulla/interface.py +0 -0
  70. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/pulla/utils_cirq.py +0 -0
  71. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/pulla/utils_dd.py +0 -0
  72. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/pulla/utils_qir.py +0 -0
  73. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm/pulla/utils_qiskit.py +0 -0
  74. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm_pulla.egg-info/dependency_links.txt +0 -0
  75. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/src/iqm_pulla.egg-info/top_level.txt +0 -0
  76. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/tests/.pylintrc +0 -0
  77. {iqm_pulla-7.23.0 → iqm_pulla-8.0.0}/tests/__init__.py +0 -0
@@ -2,6 +2,15 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ Version 8.0.0 (2025-06-13)
6
+ ==========================
7
+
8
+ Features
9
+ --------
10
+
11
+ - Move :func:`get_calibration_set_values` logic from Station Control to Pulla.
12
+ - Move :func:`create_dynamic_quantum_architecture` logic from Cocos to Pulla.
13
+
5
14
  Version 7.23.0 (2025-06-12)
6
15
  ===========================
7
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulla
3
- Version: 7.23.0
3
+ Version: 8.0.0
4
4
  Summary: Client library for pulse-level access to an IQM quantum computer
5
5
  Author-email: IQM Finland Oy <developers@meetiqm.com>
6
6
  License: Apache License
@@ -217,30 +217,30 @@ Description-Content-Type: text/x-rst
217
217
  License-File: LICENSE.txt
218
218
  License-File: AUTHORS.rst
219
219
  Requires-Dist: iqm-exa-common<27,>=26
220
- Requires-Dist: iqm-station-control-client<9,>=8
220
+ Requires-Dist: iqm-station-control-client<10,>=9
221
221
  Requires-Dist: iqm-pulse<10,>=9
222
- Requires-Dist: iqm-data-definitions<3.0,>=2.6
222
+ Requires-Dist: iqm-data-definitions<3.0,>=2.13
223
223
  Requires-Dist: pylatexenc==2.10
224
224
  Requires-Dist: pydantic<3.0,>=2.10.4
225
225
  Provides-Extra: notebook
226
226
  Requires-Dist: iqm-exa-common<27,>=26; extra == "notebook"
227
- Requires-Dist: iqm-station-control-client<9,>=8; extra == "notebook"
227
+ Requires-Dist: iqm-station-control-client<10,>=9; extra == "notebook"
228
228
  Requires-Dist: iqm-pulse<10,>=9; extra == "notebook"
229
229
  Requires-Dist: notebook<7,>=6.4.11; extra == "notebook"
230
230
  Requires-Dist: matplotlib<4,>=3.6.3; extra == "notebook"
231
231
  Requires-Dist: nbclient~=0.5.10; extra == "notebook"
232
232
  Provides-Extra: qir
233
233
  Requires-Dist: iqm-exa-common<27,>=26; extra == "qir"
234
- Requires-Dist: iqm-station-control-client<9,>=8; extra == "qir"
234
+ Requires-Dist: iqm-station-control-client<10,>=9; extra == "qir"
235
235
  Requires-Dist: iqm-pulse<10,>=9; extra == "qir"
236
236
  Requires-Dist: iqm-pyqir==0.12.0; extra == "qir"
237
237
  Requires-Dist: iqm-qiskit-qir==0.8.0; extra == "qir"
238
238
  Provides-Extra: qiskit
239
239
  Requires-Dist: iqm-exa-common<27,>=26; extra == "qiskit"
240
- Requires-Dist: iqm-station-control-client<9,>=8; extra == "qiskit"
240
+ Requires-Dist: iqm-station-control-client<10,>=9; extra == "qiskit"
241
241
  Requires-Dist: iqm-pulse<10,>=9; extra == "qiskit"
242
- Requires-Dist: iqm-client<29,>=28; extra == "qiskit"
243
- Requires-Dist: iqm-client[qiskit]<29,>=28; extra == "qiskit"
242
+ Requires-Dist: iqm-client[qiskit]<30,>=29; extra == "qiskit"
243
+ Requires-Dist: iqm-client<30,>=29; extra == "qiskit"
244
244
 
245
245
  IQM Pulla
246
246
  #########
@@ -367,7 +367,7 @@
367
367
  "quantum_computer_url = os.environ.get(\"RESONANCE_QUANTUM_COMPUTER_URL\") \n",
368
368
  "api_token = os.environ.get(\"RESONANCE_API_TOKEN\")\n",
369
369
  "\n",
370
- "p = Pulla(quantum_computer_url, get_token_callback=lambda: api_token)\n",
370
+ "p = Pulla(quantum_computer_url, get_token_callback=lambda: f\"Bearer {api_token}\")\n",
371
371
  "provider = IQMProvider(quantum_computer_url, token=api_token)\n",
372
372
  "backend = provider.get_backend()\n",
373
373
  "\n",
@@ -1,3 +1,3 @@
1
- iqm-data-definitions >= 2.6, < 3.0
1
+ iqm-data-definitions >= 2.13, < 3.0
2
2
  pylatexenc == 2.10
3
3
  pydantic >= 2.10.4, < 3.0
@@ -0,0 +1,6 @@
1
+ iqm-exa-common>=26,<27
2
+ iqm-station-control-client>=9,<10
3
+ iqm-pulse>=9,<10
4
+ iqm-data-definitions >= 2.13, < 3.0
5
+ pylatexenc == 2.10
6
+ pydantic >= 2.10.4, < 3.0
@@ -1,5 +1,5 @@
1
1
  iqm-exa-common>=26,<27
2
- iqm-station-control-client>=8,<9
2
+ iqm-station-control-client>=9,<10
3
3
  iqm-pulse>=9,<10
4
4
  notebook >= 6.4.11, < 7
5
5
  matplotlib >= 3.6.3, < 4
@@ -1,5 +1,5 @@
1
1
  iqm-exa-common>=26,<27
2
- iqm-station-control-client>=8,<9
2
+ iqm-station-control-client>=9,<10
3
3
  iqm-pulse>=9,<10
4
4
  iqm-pyqir==0.12.0
5
5
  iqm-qiskit-qir==0.8.0
@@ -0,0 +1,5 @@
1
+ iqm-exa-common>=26,<27
2
+ iqm-station-control-client>=9,<10
3
+ iqm-pulse>=9,<10
4
+ iqm-client[qiskit]>=29,<30
5
+ iqm-client>=29,<30
@@ -0,0 +1,95 @@
1
+ # Copyright 2024-2025 IQM
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
+
15
+ """Provider of calibration sets and quality metrics from remote IQM servers."""
16
+
17
+ from copy import deepcopy
18
+ import logging
19
+ import uuid
20
+
21
+ from exa.common.data.value import ObservationValue
22
+ from iqm.pulla.interface import CalibrationSet, CalibrationSetId
23
+ from iqm.pulla.utils import calset_from_observations
24
+ from iqm.station_control.client.iqm_server.iqm_server_client import IqmServerClient
25
+ from iqm.station_control.interface.models import ObservationSetData
26
+ from iqm.station_control.interface.station_control import StationControlInterface
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ CalibrationDataFetchException = RuntimeError
31
+
32
+
33
+ class CalibrationDataProvider:
34
+ """Access calibration info via station control client and cache data in memory."""
35
+
36
+ def __init__(self, station_control: StationControlInterface):
37
+ self._station_control = station_control
38
+ self._calibration_sets: dict[CalibrationSetId, CalibrationSet] = {}
39
+
40
+ def get_calibration_set(self, cal_set_id: CalibrationSetId) -> CalibrationSet:
41
+ """Get the calibration set from the database and cache it."""
42
+ logger.debug("Get the calibration set from the database: cal_set_id=%s", cal_set_id)
43
+ try:
44
+ if cal_set_id not in self._calibration_sets:
45
+ cal_set_values = self.get_calibration_set_values(cal_set_id)
46
+ self._calibration_sets[cal_set_id] = cal_set_values
47
+ return deepcopy(self._calibration_sets[cal_set_id])
48
+ except Exception as e:
49
+ raise CalibrationDataFetchException("Could not fetch calibration set from the database.") from e
50
+
51
+ def get_latest_calibration_set(self, chip_label) -> tuple[CalibrationSet, CalibrationSetId]:
52
+ """Get the latest calibration set id for chip label from the database."""
53
+ logger.debug("Get the latest calibration set for chip label: chip_label=%s", chip_label)
54
+ try:
55
+ if isinstance(self._station_control, IqmServerClient):
56
+ latest_cal_set_id = self._station_control.get_latest_calibration_set_id(chip_label)
57
+ latest_calibration_set = self.get_calibration_set(latest_cal_set_id), latest_cal_set_id
58
+ else:
59
+ latest_calibration_set = self._get_latest_calibration_set(chip_label)
60
+ except Exception as e:
61
+ raise CalibrationDataFetchException(
62
+ f"Could not fetch latest calibration set id from the database: {e}"
63
+ ) from e
64
+ return latest_calibration_set, latest_calibration_set.observation_set_id
65
+
66
+ def _get_latest_calibration_set(self, dut_label: str) -> ObservationSetData:
67
+ observation_sets = self._station_control.query_observation_sets(
68
+ observation_set_type="calibration-set",
69
+ dut_label=dut_label,
70
+ invalid=False,
71
+ end_timestamp__isnull=False, # Finalized
72
+ order_by="-end_timestamp",
73
+ limit=1,
74
+ )
75
+ return observation_sets[0]
76
+
77
+ def get_calibration_set_values(self, calibration_set_id: uuid.UUID) -> dict[str, ObservationValue]:
78
+ """Get saved calibration set observations by UUID
79
+
80
+ Args:
81
+ calibration_set_id: UUID of the calibration set to retrieve.
82
+
83
+ Returns:
84
+ Dictionary of observations belonging to the given calibration set.
85
+
86
+ """
87
+ if isinstance(self._station_control, IqmServerClient):
88
+ calibration_set_values = self._station_control.get_calibration_set_values(calibration_set_id)
89
+ else:
90
+ observation_set = self._station_control.get_observation_set(calibration_set_id)
91
+ if observation_set.observation_set_type != "calibration-set":
92
+ raise ValueError("Observation set type is not 'calibration-set'")
93
+ observations = self._station_control.get_observation_set_observations(calibration_set_id)
94
+ calibration_set_values = calset_from_observations(observations)
95
+ return calibration_set_values
@@ -47,10 +47,8 @@ from iqm.pulla.interface import (
47
47
  from iqm.pulla.utils import extract_readout_controller_result_names, map_sweep_results_to_logical_qubits
48
48
  from iqm.pulse.playlist.channel import ChannelProperties, get_channel_properties_from_station_settings
49
49
  from iqm.pulse.playlist.playlist import Playlist
50
- from iqm.station_control.client.station_control import StationControlClient
51
- from iqm.station_control.client.utils import get_progress_bar_callback
52
- from iqm.station_control.interface.models import JobExecutorStatus
53
- from iqm.station_control.interface.models.sweep import SweepDefinition
50
+ from iqm.station_control.client.utils import get_progress_bar_callback, init_station_control
51
+ from iqm.station_control.interface.models import JobExecutorStatus, SweepDefinition
54
52
 
55
53
  # ██████ ██ ██ ██ ██ █████
56
54
  # ██ ██ ██ ██ ██ ██ ██ ██
@@ -88,14 +86,15 @@ class Pulla:
88
86
 
89
87
  # SC Client to be used for fetching calibration data, submitting sweeps, and retrieving results.
90
88
  try:
91
- self._station_control_client = StationControlClient.init(
89
+ self._station_control = init_station_control(
92
90
  station_control_url, get_token_callback=self.get_token_callback, **kwargs
93
91
  )
92
+
94
93
  except Exception as e:
95
94
  logger.error("Failed to initialize Station Control Client: %s", e)
96
95
  raise ValueError("Failed to initialize Station Control Client") from e
97
96
  # Separate wrapper on top of SC Client to simplify calibration data fetching.
98
- self._calibration_data_provider = CalibrationDataProvider(self._station_control_client)
97
+ self._calibration_data_provider = CalibrationDataProvider(self._station_control)
99
98
 
100
99
  # Data needed for the compiler.
101
100
  self._station_control_settings: SettingNode | None = None
@@ -159,9 +158,12 @@ class Pulla:
159
158
  return calibration_set
160
159
 
161
160
  def get_chip_label(self) -> str:
162
- """Returns the chip label of the current quantum computer. The chip label is fetched from the Station Control API.""" # noqa: E501
161
+ """Returns the chip label of the current quantum computer.
162
+
163
+ The chip label is fetched from the Station Control API.
164
+ """
163
165
  try:
164
- duts = self._station_control_client.get_duts()
166
+ duts = self._station_control.get_duts()
165
167
  except requests.RequestException as e:
166
168
  raise ChipLabelRetrievalException(f"Failed to retrieve the chip label: {e}") from e
167
169
 
@@ -172,7 +174,7 @@ class Pulla:
172
174
  def get_chip_topology(self) -> ChipTopology:
173
175
  """Returns chip topology that was fetched from the IQM server during Pulla initialization."""
174
176
  try:
175
- record = self._station_control_client.get_chip_design_record(self.get_chip_label())
177
+ record = self._station_control.get_chip_design_record(self.get_chip_label())
176
178
  except Exception as e:
177
179
  raise CHADRetrievalException("Could not fetch chip design record") from e
178
180
  return ChipTopology.from_chip_design_record(record)
@@ -182,7 +184,7 @@ class Pulla:
182
184
  if self._station_control_settings is None:
183
185
  # request the station settings, cache the results
184
186
  try:
185
- self._station_control_settings = self._station_control_client.get_settings()
187
+ self._station_control_settings = self._station_control.get_settings()
186
188
  except Exception as e:
187
189
  raise SettingsRetrievalException("Could not fetch station settings") from e
188
190
  return self._station_control_settings
@@ -227,7 +229,7 @@ class Pulla:
227
229
  if k == "readout":
228
230
  readout_components.append(v)
229
231
 
230
- sweep_response = self._station_control_client.sweep(
232
+ sweep_response = self._station_control.sweep(
231
233
  SweepDefinition(
232
234
  sweep_id=uuid.uuid4(),
233
235
  playlist=playlist,
@@ -246,15 +248,13 @@ class Pulla:
246
248
  logger.info("Waiting for the job to finish...")
247
249
 
248
250
  while True:
249
- sweep_data = self._station_control_client.get_sweep(job_id)
251
+ sweep_data = self._station_control.get_sweep(job_id)
250
252
  sc_result = StationControlResult(sweep_id=job_id, task_id=job_id, status=TaskStatus.PENDING)
251
253
 
252
254
  if sweep_data.job_status <= JobExecutorStatus.EXECUTION_STARTED:
253
255
  # Wait in the task queue while showing a progress bar
254
256
 
255
- interrupted = self._station_control_client._wait_job_completion(
256
- str(job_id), get_progress_bar_callback()
257
- )
257
+ interrupted = self._station_control._wait_job_completion(str(job_id), get_progress_bar_callback())
258
258
  if interrupted:
259
259
  raise KeyboardInterrupt
260
260
 
@@ -263,7 +263,7 @@ class Pulla:
263
263
 
264
264
  sc_result.status = TaskStatus.READY
265
265
  sc_result.result = map_sweep_results_to_logical_qubits(
266
- self._station_control_client.get_sweep_results(job_id),
266
+ self._station_control.get_sweep_results(job_id),
267
267
  context["readout_mappings"],
268
268
  context["options"].heralding_mode,
269
269
  )
@@ -284,7 +284,7 @@ class Pulla:
284
284
  sweep_data.begin_timestamp.isoformat() if sweep_data.begin_timestamp else None
285
285
  )
286
286
  sc_result.end_time = sweep_data.end_timestamp.isoformat() if sweep_data.end_timestamp else None
287
- job = self._station_control_client.get_job(job_id)
287
+ job = self._station_control.get_job(job_id)
288
288
  sc_result.message = job["job_error"]
289
289
  logger.error("Submission failed! Error: %s", sc_result.message)
290
290
  return sc_result
@@ -295,7 +295,7 @@ class Pulla:
295
295
  sweep_data.begin_timestamp.isoformat() if sweep_data.begin_timestamp else None
296
296
  )
297
297
  sc_result.end_time = sweep_data.end_timestamp.isoformat() if sweep_data.end_timestamp else None
298
- job = self._station_control_client.get_job(job_id)
298
+ job = self._station_control.get_job(job_id)
299
299
  sc_result.message = job["job_error"]
300
300
  logger.error("Submission was revoked!")
301
301
  return sc_result
@@ -304,7 +304,7 @@ class Pulla:
304
304
 
305
305
  except KeyboardInterrupt as exc:
306
306
  logger.info("Caught KeyboardInterrupt, revoking job %s", job_id)
307
- self._station_control_client.abort_job(job_id)
307
+ self._station_control.abort_job(job_id)
308
308
  raise KeyboardInterrupt from exc
309
309
 
310
310
 
@@ -0,0 +1,142 @@
1
+ """Methods for creating static and dynamic quantum architectures."""
2
+
3
+ from collections import Counter
4
+ import logging
5
+ from uuid import UUID
6
+
7
+ from exa.common.qcm_data.chip_topology import ChipTopology, sort_components
8
+ from iqm.pulse.builder import build_quantum_ops
9
+ from iqm.station_control.interface.models import (
10
+ DynamicQuantumArchitecture,
11
+ GateImplementationInfo,
12
+ GateInfo,
13
+ Locus,
14
+ ObservationLite,
15
+ StaticQuantumArchitecture,
16
+ )
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def create_static_quantum_architecture(chip_topology: ChipTopology) -> StaticQuantumArchitecture:
22
+ """Creates a static quantum architecture (SQA) for the given chip topology.
23
+
24
+ Args:
25
+ chip_topology: The chip topology.
26
+
27
+ Returns:
28
+ Static quantum architecture containing information about qubits, computational resonators, and connectivity.
29
+
30
+ """
31
+ # Components within each connection are sorted by coupler_to_components
32
+ unsorted_connections = list(chip_topology.coupler_to_components.values())
33
+ # Sort connections first based on the first component, then the second component etc. of each connection
34
+ sorted_components = sort_components({component for connection in unsorted_connections for component in connection})
35
+ component_to_index = {component: index for index, component in enumerate(sorted_components)}
36
+
37
+ def sort_key(connection: tuple[str, ...]) -> tuple[int, ...]:
38
+ return tuple(component_to_index[component] for component in connection)
39
+
40
+ # The components in each connection are already sorted, now we sort the connections
41
+ connectivity = sorted(unsorted_connections, key=sort_key)
42
+ return StaticQuantumArchitecture(
43
+ qubits=chip_topology.qubits_sorted,
44
+ computational_resonators=chip_topology.computational_resonators_sorted,
45
+ connectivity=connectivity,
46
+ )
47
+
48
+
49
+ def create_dynamic_quantum_architecture(
50
+ calibration_set_id: UUID,
51
+ observations: list[ObservationLite],
52
+ chip_topology: ChipTopology,
53
+ ) -> DynamicQuantumArchitecture:
54
+ """Creates a dynamic quantum architecture (DQA) for the given calibration set.
55
+
56
+ Args:
57
+ calibration_set_id: ID of the calibration set used to create the DQA.
58
+ observations: Calibration set observations used to create the DQA.
59
+ chip_topology: The chip topology.
60
+
61
+ Returns:
62
+ Dynamic quantum architecture containing information about calibrated gates/operations.
63
+
64
+ """
65
+ qubits: set[str] = set()
66
+ computational_resonators: set[str] = set()
67
+ gates: dict[str, GateInfo] = {}
68
+
69
+ # known gates and implementations
70
+ quantum_op_table = build_quantum_ops({})
71
+
72
+ # error reporting
73
+ unknown_ops: set[str] = set()
74
+ unknown_implementations: set[str] = set()
75
+
76
+ def analyze_observation(obs_name: str) -> None:
77
+ """Deduce ops/implementations/loci and used QPU components from the gate cal data."""
78
+ parts = obs_name.split(".")
79
+ if parts[0] == "gates":
80
+ gate_name, gate_implementation, gate_locus = parts[1:4]
81
+ # ignore unknown gates and implementations (for backwards and forwards compatibility)
82
+ if (quantum_op := quantum_op_table.get(gate_name)) is None:
83
+ unknown_ops.add(gate_name)
84
+ return
85
+ if gate_implementation not in quantum_op.implementations:
86
+ unknown_implementations.add(f"{gate_name}.{gate_implementation}")
87
+ return
88
+
89
+ gate_info = gates.setdefault(
90
+ gate_name,
91
+ GateInfo(
92
+ implementations={},
93
+ default_implementation="",
94
+ override_default_implementation={},
95
+ ),
96
+ )
97
+ gate_implementation_info = gate_info.implementations.setdefault(
98
+ gate_implementation, GateImplementationInfo(loci=tuple())
99
+ )
100
+ gate_locus_components = tuple(gate_locus.split("__"))
101
+ if gate_locus_components not in gate_implementation_info.loci:
102
+ gate_implementation_info.loci += (gate_locus_components,)
103
+ for locus_component in gate_locus_components:
104
+ if chip_topology.is_qubit(locus_component):
105
+ qubits.add(locus_component)
106
+ if chip_topology.is_computational_resonator(locus_component):
107
+ computational_resonators.add(locus_component)
108
+
109
+ for observation in observations:
110
+ analyze_observation(observation.dut_field)
111
+
112
+ for name in unknown_ops:
113
+ logger.info("Unknown operation '%s' found in calibration set %s", name, str(calibration_set_id))
114
+ for name in unknown_implementations:
115
+ logger.info("Unknown implementation '%s' found in calibration set %s", name, str(calibration_set_id))
116
+
117
+ # Now ``gates`` only contains known gates and implementations, and each implementation has
118
+ # at least one locus. Pick a default implementations for each available (gate, locus).
119
+ for gate_name, gate_info in gates.items():
120
+ locus_default_implementations: dict[Locus, str] = {}
121
+
122
+ # pick the default implementation for each locus using the hardcoded priority order
123
+ for implementation in quantum_op_table[gate_name].implementations:
124
+ if implementation in gate_info.implementations:
125
+ for locus in gate_info.implementations[implementation].loci:
126
+ locus_default_implementations.setdefault(locus, implementation)
127
+
128
+ # choose default implementation to be the most common locus-specific default,
129
+ # and add the other locus-specific defaults to override_default_implementation
130
+ gate_info.default_implementation = Counter(locus_default_implementations.values()).most_common(1)[0][0]
131
+ gate_info.override_default_implementation = {
132
+ locus: implementation
133
+ for locus, implementation in locus_default_implementations.items()
134
+ if implementation != gate_info.default_implementation
135
+ }
136
+
137
+ return DynamicQuantumArchitecture(
138
+ calibration_set_id=calibration_set_id,
139
+ qubits=sort_components(qubits),
140
+ computational_resonators=sort_components(computational_resonators),
141
+ gates=gates,
142
+ )
@@ -42,7 +42,6 @@ from iqm.pulse.playlist.channel import ChannelProperties
42
42
  from iqm.pulse.playlist.instructions import Instruction
43
43
  from iqm.pulse.playlist.schedule import Schedule, Segment
44
44
  from iqm.pulse.timebox import TimeBox
45
- from iqm.station_control.client import utils as station_control_client_utils
46
45
  from iqm.station_control.interface.models.observation import ObservationBase
47
46
 
48
47
  LOCUS_SEPARATOR = "__" # EXA uses this, currently
@@ -602,4 +601,4 @@ def calset_from_observations(calset_observations: Iterable[ObservationBase]) ->
602
601
  calibration set
603
602
 
604
603
  """
605
- return station_control_client_utils.calset_from_observations(calset_observations)
604
+ return {obs.dut_field: obs.value for obs in calset_observations}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulla
3
- Version: 7.23.0
3
+ Version: 8.0.0
4
4
  Summary: Client library for pulse-level access to an IQM quantum computer
5
5
  Author-email: IQM Finland Oy <developers@meetiqm.com>
6
6
  License: Apache License
@@ -217,30 +217,30 @@ Description-Content-Type: text/x-rst
217
217
  License-File: LICENSE.txt
218
218
  License-File: AUTHORS.rst
219
219
  Requires-Dist: iqm-exa-common<27,>=26
220
- Requires-Dist: iqm-station-control-client<9,>=8
220
+ Requires-Dist: iqm-station-control-client<10,>=9
221
221
  Requires-Dist: iqm-pulse<10,>=9
222
- Requires-Dist: iqm-data-definitions<3.0,>=2.6
222
+ Requires-Dist: iqm-data-definitions<3.0,>=2.13
223
223
  Requires-Dist: pylatexenc==2.10
224
224
  Requires-Dist: pydantic<3.0,>=2.10.4
225
225
  Provides-Extra: notebook
226
226
  Requires-Dist: iqm-exa-common<27,>=26; extra == "notebook"
227
- Requires-Dist: iqm-station-control-client<9,>=8; extra == "notebook"
227
+ Requires-Dist: iqm-station-control-client<10,>=9; extra == "notebook"
228
228
  Requires-Dist: iqm-pulse<10,>=9; extra == "notebook"
229
229
  Requires-Dist: notebook<7,>=6.4.11; extra == "notebook"
230
230
  Requires-Dist: matplotlib<4,>=3.6.3; extra == "notebook"
231
231
  Requires-Dist: nbclient~=0.5.10; extra == "notebook"
232
232
  Provides-Extra: qir
233
233
  Requires-Dist: iqm-exa-common<27,>=26; extra == "qir"
234
- Requires-Dist: iqm-station-control-client<9,>=8; extra == "qir"
234
+ Requires-Dist: iqm-station-control-client<10,>=9; extra == "qir"
235
235
  Requires-Dist: iqm-pulse<10,>=9; extra == "qir"
236
236
  Requires-Dist: iqm-pyqir==0.12.0; extra == "qir"
237
237
  Requires-Dist: iqm-qiskit-qir==0.8.0; extra == "qir"
238
238
  Provides-Extra: qiskit
239
239
  Requires-Dist: iqm-exa-common<27,>=26; extra == "qiskit"
240
- Requires-Dist: iqm-station-control-client<9,>=8; extra == "qiskit"
240
+ Requires-Dist: iqm-station-control-client<10,>=9; extra == "qiskit"
241
241
  Requires-Dist: iqm-pulse<10,>=9; extra == "qiskit"
242
- Requires-Dist: iqm-client<29,>=28; extra == "qiskit"
243
- Requires-Dist: iqm-client[qiskit]<29,>=28; extra == "qiskit"
242
+ Requires-Dist: iqm-client[qiskit]<30,>=29; extra == "qiskit"
243
+ Requires-Dist: iqm-client<30,>=29; extra == "qiskit"
244
244
 
245
245
  IQM Pulla
246
246
  #########
@@ -55,6 +55,7 @@ src/iqm/pulla/__init__.py
55
55
  src/iqm/pulla/calibration.py
56
56
  src/iqm/pulla/interface.py
57
57
  src/iqm/pulla/pulla.py
58
+ src/iqm/pulla/quantum_architecture.py
58
59
  src/iqm/pulla/utils.py
59
60
  src/iqm/pulla/utils_cirq.py
60
61
  src/iqm/pulla/utils_dd.py
@@ -1,13 +1,13 @@
1
1
  iqm-exa-common<27,>=26
2
- iqm-station-control-client<9,>=8
2
+ iqm-station-control-client<10,>=9
3
3
  iqm-pulse<10,>=9
4
- iqm-data-definitions<3.0,>=2.6
4
+ iqm-data-definitions<3.0,>=2.13
5
5
  pylatexenc==2.10
6
6
  pydantic<3.0,>=2.10.4
7
7
 
8
8
  [notebook]
9
9
  iqm-exa-common<27,>=26
10
- iqm-station-control-client<9,>=8
10
+ iqm-station-control-client<10,>=9
11
11
  iqm-pulse<10,>=9
12
12
  notebook<7,>=6.4.11
13
13
  matplotlib<4,>=3.6.3
@@ -15,14 +15,14 @@ nbclient~=0.5.10
15
15
 
16
16
  [qir]
17
17
  iqm-exa-common<27,>=26
18
- iqm-station-control-client<9,>=8
18
+ iqm-station-control-client<10,>=9
19
19
  iqm-pulse<10,>=9
20
20
  iqm-pyqir==0.12.0
21
21
  iqm-qiskit-qir==0.8.0
22
22
 
23
23
  [qiskit]
24
24
  iqm-exa-common<27,>=26
25
- iqm-station-control-client<9,>=8
25
+ iqm-station-control-client<10,>=9
26
26
  iqm-pulse<10,>=9
27
- iqm-client<29,>=28
28
- iqm-client[qiskit]<29,>=28
27
+ iqm-client[qiskit]<30,>=29
28
+ iqm-client<30,>=29
@@ -18,6 +18,7 @@ from importlib.metadata import version
18
18
  import json
19
19
  import os
20
20
  from pathlib import Path
21
+ from unittest.mock import Mock
21
22
  from uuid import UUID
22
23
 
23
24
  from httpx import Response as HTTPResponse
@@ -27,6 +28,7 @@ from iqm.iqm_client.models import (
27
28
  GateInfo,
28
29
  )
29
30
  from iqm.qiskit_iqm.iqm_provider import IQMBackend, IQMClient
31
+ from mockito import ANY, mock, when
30
32
  import pytest
31
33
  import requests
32
34
  from requests import Response
@@ -80,18 +82,19 @@ def pulla_on_spark(request, monkeypatch):
80
82
  # TODO SW-1387: Use v1 API
81
83
  # if args[0] == f"{root_url}/station/v1/about":
82
84
  if args[0] == f"{root_url}/station/about":
83
- response = Response()
85
+ response = Mock(spec=Response)
84
86
  response.status_code = HTTPStatus.OK
85
- response.json = lambda: {
86
- "software_versions": {"iqm-station-control-client": version("iqm-station-control-client")}
87
- }
87
+ data = {"software_versions": {"iqm-station-control-client": version("iqm-station-control-client")}}
88
+ response.text = json.dumps(data)
89
+ response.json = lambda: data
90
+ response.ok = True
88
91
  return response
89
92
  # TODO SW-1387: Use v1 API
90
93
  # if args[0] == f"{root_url}/station/v1/duts":
91
94
  if args[0] == f"{root_url}/station/duts":
92
- response = Response()
95
+ response = Mock(spec=Response)
93
96
  response.status_code = HTTPStatus.OK
94
- response.json = lambda: [{"label": "M000_fake_0_0", "dut_type": "chip"}]
97
+ response.text = json.dumps([{"label": "M000_fake_0_0", "dut_type": "chip"}])
95
98
  return response
96
99
  # TODO SW-1387: Use v1 API
97
100
  # if args[0].startswith(f"{root_url}/station/v1/sweeps/"):
@@ -232,10 +235,13 @@ def qiskit_backend_spark(monkeypatch) -> IQMBackend:
232
235
  )
233
236
 
234
237
  monkeypatch.setattr(IQMClient, "get_dynamic_quantum_architecture", lambda self, calset_id: dqa)
235
- if hasattr(IQMClient, "_check_versions"): # _check_versions was introduced in a minor version of IQM Client
236
- monkeypatch.setattr(IQMClient, "_check_versions", lambda self: None)
237
- client = IQMClient(f"{root_url}/cocos", client_signature="test fixture")
238
- return IQMBackend(client, calibration_set_id=calset_id)
238
+ monkeypatch.setattr(StationControlClient, "_check_api_versions", lambda self: None)
239
+ mock_about_response = mock(spec=Response)
240
+ when(mock_about_response).raise_for_status().thenReturn(None)
241
+ when(mock_about_response).json().thenReturn({})
242
+ when(requests).get(f"{root_url}/station/about", headers=ANY).thenReturn(mock_about_response)
243
+ iqm_client = IQMClient(f"{root_url}/station", client_signature="test fixture")
244
+ return IQMBackend(iqm_client, calibration_set_id=calset_id)
239
245
 
240
246
 
241
247
  @pytest.fixture
@@ -0,0 +1 @@
1
+ 8.0.0
@@ -1,6 +0,0 @@
1
- iqm-exa-common>=26,<27
2
- iqm-station-control-client>=8,<9
3
- iqm-pulse>=9,<10
4
- iqm-data-definitions >= 2.6, < 3.0
5
- pylatexenc == 2.10
6
- pydantic >= 2.10.4, < 3.0
@@ -1,5 +0,0 @@
1
- iqm-exa-common>=26,<27
2
- iqm-station-control-client>=8,<9
3
- iqm-pulse>=9,<10
4
- iqm-client>=28,<29
5
- iqm-client[qiskit]>=28,<29
@@ -1,56 +0,0 @@
1
- # Copyright 2024-2025 IQM
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
-
15
- """Provider of calibration sets and quality metrics from remote IQM servers."""
16
-
17
- from copy import deepcopy
18
- import logging
19
-
20
- from iqm.pulla.interface import CalibrationSet, CalibrationSetId
21
- from iqm.station_control.client.station_control import StationControlClient
22
-
23
- logger = logging.getLogger(__name__)
24
-
25
- CalibrationDataFetchException = RuntimeError
26
-
27
-
28
- class CalibrationDataProvider:
29
- """Access calibration info via station control client and cache data in memory."""
30
-
31
- def __init__(self, station_control_client: StationControlClient):
32
- self._station_control_client = station_control_client
33
- self._calibration_sets: dict[CalibrationSetId, CalibrationSet] = {}
34
-
35
- def get_calibration_set(self, cal_set_id: CalibrationSetId) -> CalibrationSet:
36
- """Get the calibration set from the database and cache it."""
37
- logger.debug("Get the calibration set from the database: cal_set_id=%s", cal_set_id)
38
- try:
39
- if cal_set_id not in self._calibration_sets:
40
- cal_set_values = self._station_control_client.get_calibration_set_values(cal_set_id)
41
- self._calibration_sets[cal_set_id] = cal_set_values
42
- return deepcopy(self._calibration_sets[cal_set_id])
43
- except Exception as e:
44
- raise CalibrationDataFetchException("Could not fetch calibration set from the database.") from e
45
-
46
- def get_latest_calibration_set(self, chip_label) -> tuple[CalibrationSet, CalibrationSetId]:
47
- """Get the latest calibration set id for chip label from the database."""
48
- logger.debug("Get the latest calibration set for chip label: chip_label=%s", chip_label)
49
- try:
50
- latest_cal_set_id = self._station_control_client.get_latest_calibration_set_id(chip_label)
51
- except Exception as e:
52
- raise CalibrationDataFetchException(
53
- f"Could not fetch latest calibration set id from the database: {e}"
54
- ) from e
55
-
56
- return self.get_calibration_set(latest_cal_set_id), latest_cal_set_id
@@ -1 +0,0 @@
1
- 7.23.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes