iqm-pulla 11.9.0__py3-none-any.whl → 11.10.0__py3-none-any.whl

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/pulla/interface.py CHANGED
@@ -58,15 +58,15 @@ class TaskStatus(StrEnum):
58
58
  """Task is waiting to be executed"""
59
59
 
60
60
 
61
- CalibrationSet = dict[str, ObservationValue]
62
- CalibrationSetId = UUID
61
+ CalibrationSet: TypeAlias = dict[str, ObservationValue]
62
+ CalibrationSetId: TypeAlias = UUID
63
63
 
64
- CircuitMeasurementResults = dict[str, list[list[int]]]
64
+ CircuitMeasurementResults: TypeAlias = dict[str, list[list[int]]]
65
65
  """Measurement results from a single circuit/schedule. For each measurement operation in the circuit,
66
66
  maps the measurement key to the corresponding results. The outer list elements correspond to shots,
67
67
  and the inner list elements to the qubits measured in the measurement operation."""
68
68
 
69
- CircuitMeasurementResultsBatch = list[CircuitMeasurementResults]
69
+ CircuitMeasurementResultsBatch: TypeAlias = list[CircuitMeasurementResults]
70
70
  """Type that represents measurement results for a batch of circuits."""
71
71
 
72
72
 
iqm/pulla/utils.py CHANGED
@@ -15,15 +15,17 @@
15
15
  """Utility functions for IQM Pulla."""
16
16
 
17
17
  from collections import namedtuple
18
- from collections.abc import Hashable, Iterable, Sequence, Set
18
+ from collections.abc import Hashable, Iterable, Iterator, Sequence, Set
19
19
  from dataclasses import replace
20
20
  from itertools import chain
21
- from typing import Any
21
+ from typing import Any, TypeAlias
22
22
 
23
23
  import numpy as np
24
+ from typing_extensions import deprecated
24
25
 
25
26
  from exa.common.data.setting_node import SettingNode
26
27
  from exa.common.data.value import ObservationValue
28
+ from exa.common.helpers.deprecation import format_deprecated
27
29
  from exa.common.qcm_data.chip_topology import ChipTopology
28
30
  from iqm.cpc.compiler.errors import CalibrationError, InsufficientContextError, UnknownCircuitExecutionOptionError
29
31
  from iqm.cpc.compiler.station_settings import build_station_settings
@@ -42,9 +44,14 @@ from iqm.pulse.playlist.channel import ChannelProperties
42
44
  from iqm.pulse.playlist.instructions import Instruction
43
45
  from iqm.pulse.playlist.schedule import Schedule, Segment
44
46
  from iqm.pulse.timebox import TimeBox
47
+ from iqm.station_control.client.qon import locus_str_to_locus
45
48
  from iqm.station_control.interface.models.observation import ObservationBase
46
49
 
47
- LOCUS_SEPARATOR = "__" # EXA uses this, currently
50
+ _CircuitMeasurementResultsNew: TypeAlias = dict[str, np.ndarray]
51
+ """Measurement results from a single circuit/schedule. For each measurement operation in the circuit,
52
+ maps the measurement key to an array of results, where the first dimension correspond to shots,
53
+ and the second dimension to the qubits measured in the measurement operation."""
54
+ # TODO should replace CircuitMeasurementResults in the API
48
55
 
49
56
 
50
57
  def circuit_operations_to_cpc(circ_ops: tuple[CircuitOperation], name: str | None = None) -> CPC_Circuit:
@@ -132,10 +139,11 @@ def _result_idx(label: str, circuit_idx: int, labels_for_circuit: list[list[str]
132
139
  return len([circuit_labels for circuit_labels in labels_for_circuit[:circuit_idx] if label in circuit_labels])
133
140
 
134
141
 
142
+ @deprecated(format_deprecated("convert_sweep_spot", "convert_sweep_spot_to_arrays", "2025-09-25"))
135
143
  def convert_sweep_spot(
136
144
  results: dict[str, np.ndarray], readout_mappings: ReadoutMappingBatch
137
145
  ) -> CircuitMeasurementResultsBatch:
138
- """Convert the sweep measurement results from Station Control into circuit measurement results.
146
+ """Deprecated. Same as :func:`.convert_sweep_spot_to_arrays` but returns a less efficient format.
139
147
 
140
148
  Args:
141
149
  results: mapping of acquisition labels to 1D arrays of readout results with the length
@@ -146,31 +154,74 @@ def convert_sweep_spot(
146
154
  Returns:
147
155
  converted measurement results
148
156
 
157
+ """
158
+ circuit_results_iter = convert_sweep_spot_to_arrays(results, readout_mappings)
159
+ return [{mk: array.tolist() for mk, array in circuit_res.items()} for circuit_res in circuit_results_iter]
160
+
161
+
162
+ def convert_sweep_spot_to_arrays(
163
+ results: dict[str, np.ndarray], readout_mappings: ReadoutMappingBatch
164
+ ) -> Iterator[_CircuitMeasurementResultsNew]:
165
+ """Convert the sweep measurement results from Station Control into circuit batch measurement results.
166
+
167
+ Args:
168
+ results: mapping of acquisition labels to 1D arrays of readout results with the length
169
+ ``num_shots * num_triggers_for_label_in_batch``
170
+ readout_mappings: for each circuit in the batch, a mapping of measurement keys to corresponding
171
+ tuples of acquisition labels
172
+
173
+ Yields:
174
+ Converted measurement results for each circuit in the batch.
175
+
149
176
  """
150
177
  num_triggers_for_label, labels_for_circuit = _get_trigger_indexing_for(readout_mappings)
151
178
  first_key = next(iter(results))
152
179
  num_shots = len(results[first_key]) // num_triggers_for_label[first_key] # num shots equal for all labels
153
-
154
180
  results = {
155
- label: measurements.reshape((num_shots, num_triggers_for_label[label]))
181
+ label: measurements.reshape((num_shots, num_triggers_for_label[label])).astype(np.uint8)
156
182
  for label, measurements in results.items()
157
183
  }
158
- return [
159
- {
184
+ for circuit_idx, readout_mapping in enumerate(readout_mappings):
185
+ yield {
160
186
  mk: np.stack(
161
187
  [results[label][:, _result_idx(label, circuit_idx, labels_for_circuit)] for label in result_labels],
162
188
  axis=1,
163
- ).tolist()
189
+ )
164
190
  for mk, result_labels in readout_mapping.items()
165
191
  }
166
- for circuit_idx, readout_mapping in enumerate(readout_mappings)
167
- ]
168
192
 
169
193
 
194
+ @deprecated(
195
+ format_deprecated(
196
+ "convert_sweep_spot_with_heralding_mode_zero",
197
+ "convert_sweep_spot_to_arrays_with_heralding_mode_zero",
198
+ "2025-09-25",
199
+ )
200
+ )
170
201
  def convert_sweep_spot_with_heralding_mode_zero(
171
202
  results: dict[str, np.ndarray], readout_mappings: ReadoutMappingBatch
172
203
  ) -> CircuitMeasurementResultsBatch:
173
- """Like :func:`convert_sweep_spot`, but for results that contain heralding measurements.
204
+ """Deprecated. Same as :func:`.convert_sweep_spot_to_arrays` but returns a less efficient format.
205
+
206
+ Args:
207
+ results: Mapping of acquisition labels to 1D arrays of readout results with the length
208
+ ``num_shots * num_triggers_for_label_in_batch``. The herald
209
+ results are found under ``HERALDING_KEY``.
210
+ readout_mappings: For each circuit in the batch, a mapping of measurement keys to corresponding
211
+ tuples of acquisition labels.
212
+
213
+ Returns:
214
+ converted, filtered measurement results, with the heralding measurement data removed
215
+
216
+ """
217
+ circuit_results_iter = convert_sweep_spot_to_arrays_with_heralding_mode_zero(results, readout_mappings)
218
+ return [{mk: array.tolist() for mk, array in circuit_res.items()} for circuit_res in circuit_results_iter]
219
+
220
+
221
+ def convert_sweep_spot_to_arrays_with_heralding_mode_zero(
222
+ results: dict[str, np.ndarray], readout_mappings: ReadoutMappingBatch
223
+ ) -> Iterator[_CircuitMeasurementResultsNew]:
224
+ """Like :func:`convert_sweep_spot_to_arrays`, but for results that contain heralding measurements.
174
225
 
175
226
  * For each circuit we only keep the shots for which the heralding result is zero for all the
176
227
  qubits used in the circuit.
@@ -182,16 +233,16 @@ def convert_sweep_spot_with_heralding_mode_zero(
182
233
  readout_mappings: For each circuit in the batch, a mapping of measurement keys to corresponding
183
234
  tuples of acquisition labels.
184
235
 
185
- Returns:
186
- converted, filtered measurement results, with the heralding measurement data removed
236
+ Yields:
237
+ Converted measurement results for each circuit in the batch, filtered based on the result of
238
+ the heralding measurement, , with the heralding measurement data removed.
187
239
 
188
240
  """
189
241
  num_triggers_for_label, labels_for_circuit = _get_trigger_indexing_for(readout_mappings)
190
242
  first_key = next(iter(results.keys()))
191
243
  num_shots = len(results[first_key]) // num_triggers_for_label[first_key] # num shots equal for all labels
192
- transformed_results: CircuitMeasurementResultsBatch = []
193
244
  results = {
194
- label: measurements.reshape((num_shots, num_triggers_for_label[label]))
245
+ label: measurements.reshape((num_shots, num_triggers_for_label[label])).astype(np.uint8)
195
246
  for label, measurements in results.items()
196
247
  }
197
248
  for circuit_idx, readout_mapping in enumerate(readout_mappings):
@@ -210,17 +261,14 @@ def convert_sweep_spot_with_heralding_mode_zero(
210
261
  raise RuntimeError(
211
262
  f'Execution of circuit {circuit_idx} in heralding mode "{HeraldingMode.ZEROS}" discarded all the shots.'
212
263
  )
213
- transformed_results.append(
214
- {
215
- mk: np.stack(
216
- [results[label][mask, _result_idx(label, circuit_idx, labels_for_circuit)] for label in labels],
217
- axis=1,
218
- ).tolist()
219
- for mk, labels in readout_mapping.items()
220
- if mk != HERALDING_KEY
221
- }
222
- )
223
- return transformed_results
264
+ yield {
265
+ mk: np.stack(
266
+ [results[label][mask, _result_idx(label, circuit_idx, labels_for_circuit)] for label in labels],
267
+ axis=1,
268
+ )
269
+ for mk, labels in readout_mapping.items()
270
+ if mk != HERALDING_KEY
271
+ }
224
272
 
225
273
 
226
274
  def extract_readout_controller_result_names(readout_mappings: ReadoutMappingBatch) -> set[str]:
@@ -238,10 +286,10 @@ def map_sweep_results_to_logical_qubits(
238
286
  array of readout results, with ``shots * num_triggers_for_label`` elements.
239
287
  readout_mappings: for each circuit in the batch, a mapping of measurement keys to corresponding
240
288
  tuples of result parameter names.
241
- heralding_mode: Heralding mode, either ``ZEROS`` (when doing heralded readout) or ``NONE``.
289
+ heralding_mode: Whether we use heralded readout or not.
242
290
 
243
291
  Returns:
244
- converted, filtered measurement results, with the heralding measurement data removed
292
+ Converted, filtered measurement results, with the heralding measurement data removed.
245
293
 
246
294
  """
247
295
  # TODO the SC return data format should be rationalized, for example list[dict[str, np.ndarray]]
@@ -251,8 +299,14 @@ def map_sweep_results_to_logical_qubits(
251
299
  # circuit execution uses just one soft sweep spot
252
300
  results = {k: v[0] for k, v in sweep_results.items()}
253
301
  if heralding_mode == HeraldingMode.NONE:
254
- return convert_sweep_spot(results, readout_mappings)
255
- return convert_sweep_spot_with_heralding_mode_zero(results, readout_mappings)
302
+ circuit_results_iter = convert_sweep_spot_to_arrays(results, readout_mappings)
303
+ else:
304
+ circuit_results_iter = convert_sweep_spot_to_arrays_with_heralding_mode_zero(results, readout_mappings)
305
+
306
+ # TODO 99% of the time in this function is spent in the tolist() converting the arrays to ints.
307
+ # For large datasets, it is several seconds.
308
+ # To rectify this, we should change CircuitMeasurementResultsBatch of the public API.
309
+ return [{mk: array.tolist() for mk, array in circuit_res.items()} for circuit_res in circuit_results_iter]
256
310
 
257
311
 
258
312
  InstructionLocation = namedtuple("InstructionLocation", ["channel_name", "index", "duration"])
@@ -418,7 +472,7 @@ def calset_to_cal_data_tree(calibration_set: CalibrationSet) -> OpCalibrationDat
418
472
  if len(path) < 5:
419
473
  raise CalibrationError(f"Calibration observation name '{key}' is malformed.")
420
474
  # treat the locus specially
421
- locus = tuple(path[3].split(LOCUS_SEPARATOR))
475
+ locus = locus_str_to_locus(path[3])
422
476
  locus = () if locus == ("",) else locus
423
477
  # mypy likes this
424
478
  set_path(tree.setdefault(path[1], {}).setdefault(path[2], {}).setdefault(locus, {}), path[4:], value) # type: ignore[arg-type]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulla
3
- Version: 11.9.0
3
+ Version: 11.10.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
@@ -239,8 +239,8 @@ Provides-Extra: qiskit
239
239
  Requires-Dist: iqm-exa-common <28,>=27 ; extra == 'qiskit'
240
240
  Requires-Dist: iqm-station-control-client <12,>=11 ; extra == 'qiskit'
241
241
  Requires-Dist: iqm-pulse <13,>=12 ; extra == 'qiskit'
242
- Requires-Dist: iqm-client <32,>=31 ; extra == 'qiskit'
243
242
  Requires-Dist: iqm-client[qiskit] <32,>=31 ; extra == 'qiskit'
243
+ Requires-Dist: iqm-client <32,>=31 ; extra == 'qiskit'
244
244
 
245
245
  IQM Pulla
246
246
  #########
@@ -10,18 +10,18 @@ iqm/cpc/interface/__init__.py,sha256=mvhNx1I9K5Sg40CwPntWj35M3Bni__23PWEw_qYaXtQ
10
10
  iqm/cpc/interface/compiler.py,sha256=zxUyalyiBIQA9EUK_fDGNq0SZ5OwfxQdB6Xk5BDDMVk,9797
11
11
  iqm/pulla/__init__.py,sha256=fj5Qh8R9K-z6q5g9-CySBZsG8d33nU7hCHrqIIB8_-0,901
12
12
  iqm/pulla/calibration.py,sha256=c_SNlTxXC0y-ahDY7JY53N7SofU5WUWMrNBogtdfHl4,4523
13
- iqm/pulla/interface.py,sha256=QquVKhOl2fs2V03fqQJq5sIEaIGiYvetPKKNz_fWH54,3570
13
+ iqm/pulla/interface.py,sha256=XZ_uOWyxlZBmil5-Z02R6rZ1dsR5t43xr6oL01DAows,3614
14
14
  iqm/pulla/pulla.py,sha256=kgcyoMe9a9VvD9Y7HLvFdaQaax8vJkP_OVRd7sftvjo,16166
15
15
  iqm/pulla/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  iqm/pulla/quantum_architecture.py,sha256=jj9zqK9CcODedR69p8zLSj3qDfzIxI_f9rb8s07GKaM,6377
17
- iqm/pulla/utils.py,sha256=xVu4jLlSHX-_YQT1kB-zBiGy4wNy0YyOKZg4AGMB5DE,24748
17
+ iqm/pulla/utils.py,sha256=H0clvkGRKF84b9CtfgoWc0M8oHa0VtnaPNauowfys4k,27737
18
18
  iqm/pulla/utils_cirq.py,sha256=VtdO356D2RAh2lSVdaPm3ClJ9-nvC4jzhR9hP5QSQhI,793
19
19
  iqm/pulla/utils_dd.py,sha256=SxYAuRBgvYELKjeXpFbP4mM0xCCivDk7WUHw7oEXfMo,1695
20
20
  iqm/pulla/utils_qir.py,sha256=uNEiqBczv7rX8tAqcg7TX-si0P-ZdDcPTMOVfi5eb28,11567
21
21
  iqm/pulla/utils_qiskit.py,sha256=pbb2fEJAdxqd6hW9_Qgvpvy0P2H81zTJ1QLahe4Tx6U,9482
22
- iqm_pulla-11.9.0.dist-info/AUTHORS.rst,sha256=iCStz7WP5Jk7uMnn9jRA4ybS14X4yeUW2SsWE-OTaRk,328
23
- iqm_pulla-11.9.0.dist-info/LICENSE.txt,sha256=cCj_biRA4Q8A77vxR8AuvAf-DZ5G79yxR_3lYY6TrmA,11333
24
- iqm_pulla-11.9.0.dist-info/METADATA,sha256=2zrewhcyNY_3rRKVR5PHmKx2hEyKApZZT8_R5_0yHs4,17700
25
- iqm_pulla-11.9.0.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
26
- iqm_pulla-11.9.0.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
27
- iqm_pulla-11.9.0.dist-info/RECORD,,
22
+ iqm_pulla-11.10.0.dist-info/AUTHORS.rst,sha256=iCStz7WP5Jk7uMnn9jRA4ybS14X4yeUW2SsWE-OTaRk,328
23
+ iqm_pulla-11.10.0.dist-info/LICENSE.txt,sha256=cCj_biRA4Q8A77vxR8AuvAf-DZ5G79yxR_3lYY6TrmA,11333
24
+ iqm_pulla-11.10.0.dist-info/METADATA,sha256=_x9818mN2cJSg4GgbffkBLUYXFSqs33Dt8kk3WKaT7w,17701
25
+ iqm_pulla-11.10.0.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
26
+ iqm_pulla-11.10.0.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
27
+ iqm_pulla-11.10.0.dist-info/RECORD,,