iqm-pulla 11.17.0__py3-none-any.whl → 12.0.1__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/cpc/compiler/compiler.py +21 -25
- iqm/cpc/compiler/dd.py +1 -1
- iqm/cpc/compiler/standard_stages.py +19 -5
- iqm/cpc/compiler/station_settings.py +9 -7
- iqm/cpc/interface/compiler.py +7 -98
- iqm/pulla/calibration.py +26 -47
- iqm/pulla/interface.py +3 -58
- iqm/pulla/pulla.py +136 -196
- iqm/pulla/utils.py +21 -17
- iqm/pulla/utils_qir.py +3 -2
- iqm/pulla/utils_qiskit.py +37 -46
- {iqm_pulla-11.17.0.dist-info → iqm_pulla-12.0.1.dist-info}/METADATA +11 -20
- iqm_pulla-12.0.1.dist-info/RECORD +27 -0
- iqm_pulla-11.17.0.dist-info/RECORD +0 -27
- {iqm_pulla-11.17.0.dist-info → iqm_pulla-12.0.1.dist-info}/AUTHORS.rst +0 -0
- {iqm_pulla-11.17.0.dist-info → iqm_pulla-12.0.1.dist-info}/LICENSE.txt +0 -0
- {iqm_pulla-11.17.0.dist-info → iqm_pulla-12.0.1.dist-info}/WHEEL +0 -0
- {iqm_pulla-11.17.0.dist-info → iqm_pulla-12.0.1.dist-info}/top_level.txt +0 -0
iqm/cpc/compiler/compiler.py
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
This is the core module of ``CPC``. It contains the functionality to define a compiler, whose job is to
|
|
17
17
|
convert quantum circuits and calibration data into configuration settings and instruction schedules that
|
|
18
|
-
can be executed by the IQM
|
|
18
|
+
can be executed by the IQM Server on quantum hardware.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
from __future__ import annotations
|
|
@@ -42,7 +42,7 @@ from iqm.cpc.interface.compiler import (
|
|
|
42
42
|
MoveGateFrameTrackingMode,
|
|
43
43
|
MoveGateValidationMode,
|
|
44
44
|
)
|
|
45
|
-
from iqm.pulla.interface import
|
|
45
|
+
from iqm.pulla.interface import CalibrationSetValues
|
|
46
46
|
from iqm.pulla.utils import (
|
|
47
47
|
_update_channel_props_from_calibration,
|
|
48
48
|
build_settings,
|
|
@@ -181,13 +181,15 @@ class CompilationStage:
|
|
|
181
181
|
|
|
182
182
|
|
|
183
183
|
class Compiler:
|
|
184
|
-
"""
|
|
184
|
+
"""Compiles quantum circuits (via time boxes) into instruction schedules.
|
|
185
|
+
|
|
186
|
+
Stateful object that contains a calibration set, a schedule builder, and a set
|
|
185
187
|
of compilation stages.
|
|
186
188
|
|
|
187
189
|
The compiler's state does not include the data to be compiled.
|
|
188
190
|
|
|
189
191
|
Args:
|
|
190
|
-
|
|
192
|
+
calibration_set_values: Calibration data.
|
|
191
193
|
chip_topology: Physical layout and connectivity of the quantum chip.
|
|
192
194
|
channel_properties: Control channel properties for the station.
|
|
193
195
|
component_channels: Mapping from QPU component name to a mapping from ``('drive', 'flux', 'readout')``
|
|
@@ -210,7 +212,7 @@ class Compiler:
|
|
|
210
212
|
def __init__( # noqa: PLR0913
|
|
211
213
|
self,
|
|
212
214
|
*,
|
|
213
|
-
|
|
215
|
+
calibration_set_values: CalibrationSetValues,
|
|
214
216
|
chip_topology: ChipTopology,
|
|
215
217
|
channel_properties: dict[str, ChannelProperties],
|
|
216
218
|
component_channels: dict[str, dict[str, str]],
|
|
@@ -220,14 +222,14 @@ class Compiler:
|
|
|
220
222
|
pp_stages: Collection[CompilationStage] | None = None,
|
|
221
223
|
strict: bool = False, # consider extending to e.g. errors: Literal["raise", "warning", "ignore"] = "warning"
|
|
222
224
|
):
|
|
223
|
-
self.
|
|
225
|
+
self._calibration_set_values = calibration_set_values
|
|
224
226
|
self.component_mapping = component_mapping
|
|
225
227
|
self.options = options
|
|
226
228
|
self.stages = stages or []
|
|
227
229
|
self.pp_stages = pp_stages or []
|
|
228
230
|
|
|
229
231
|
self.builder: ScheduleBuilder = initialize_schedule_builder(
|
|
230
|
-
|
|
232
|
+
calibration_set_values, chip_topology, channel_properties, component_channels
|
|
231
233
|
)
|
|
232
234
|
try:
|
|
233
235
|
self.builder.validate_calibration()
|
|
@@ -241,11 +243,11 @@ class Compiler:
|
|
|
241
243
|
Must be called automatically by any method that modifies the calibration set, or the op_table
|
|
242
244
|
"""
|
|
243
245
|
_updated_channel_properties = _update_channel_props_from_calibration(
|
|
244
|
-
self.builder.channels, self.builder.component_channels, self.
|
|
246
|
+
self.builder.channels, self.builder.component_channels, self._calibration_set_values
|
|
245
247
|
)
|
|
246
248
|
self.builder = ScheduleBuilder(
|
|
247
249
|
self.builder.op_table,
|
|
248
|
-
calset_to_cal_data_tree(self.
|
|
250
|
+
calset_to_cal_data_tree(self._calibration_set_values),
|
|
249
251
|
self.builder.chip_topology,
|
|
250
252
|
_updated_channel_properties,
|
|
251
253
|
self.builder.component_channels,
|
|
@@ -255,18 +257,18 @@ class Compiler:
|
|
|
255
257
|
except ValueError as exc:
|
|
256
258
|
raise CalibrationError(f"{exc}") from exc
|
|
257
259
|
|
|
258
|
-
def
|
|
260
|
+
def get_calibration_set_values(self) -> CalibrationSetValues:
|
|
259
261
|
"""Returns a copy of the current local calibration set."""
|
|
260
|
-
return deepcopy(self.
|
|
262
|
+
return deepcopy(self._calibration_set_values)
|
|
261
263
|
|
|
262
|
-
def
|
|
264
|
+
def set_calibration_set_values(self, calibration_set_values: CalibrationSetValues) -> None:
|
|
263
265
|
"""Sets the current calibration set to a given calibration set, then refreshes the compiler.
|
|
264
266
|
|
|
265
267
|
Args:
|
|
266
|
-
|
|
268
|
+
calibration_set_values: The calibration set to be set as the current calibration set.
|
|
267
269
|
|
|
268
270
|
"""
|
|
269
|
-
self.
|
|
271
|
+
self._calibration_set_values = calibration_set_values
|
|
270
272
|
self._refresh()
|
|
271
273
|
|
|
272
274
|
@property
|
|
@@ -323,7 +325,7 @@ class Compiler:
|
|
|
323
325
|
locus_str = "__".join(locus)
|
|
324
326
|
for param, value in params.items():
|
|
325
327
|
path = f"gates.{gate_name}.{impl_name}.{locus_str}.{param}"
|
|
326
|
-
self.
|
|
328
|
+
self._calibration_set_values[path] = value
|
|
327
329
|
|
|
328
330
|
self._refresh()
|
|
329
331
|
|
|
@@ -422,7 +424,7 @@ class Compiler:
|
|
|
422
424
|
Used automatically by :meth:`compile`.
|
|
423
425
|
"""
|
|
424
426
|
return {
|
|
425
|
-
"calibration_set": self.
|
|
427
|
+
"calibration_set": self._calibration_set_values,
|
|
426
428
|
"builder": self.builder,
|
|
427
429
|
"component_mapping": self.component_mapping,
|
|
428
430
|
"options": self.options,
|
|
@@ -430,9 +432,7 @@ class Compiler:
|
|
|
430
432
|
"chip_topology": self.builder.chip_topology,
|
|
431
433
|
}
|
|
432
434
|
|
|
433
|
-
def compile(
|
|
434
|
-
self, data: Iterable[Any], context: dict[str, Any] | None = None
|
|
435
|
-
) -> tuple[Iterable[Any], dict[str, Any]]:
|
|
435
|
+
def compile(self, data: Iterable[Any], context: dict[str, Any] | None = None) -> tuple[Any, dict[str, Any]]:
|
|
436
436
|
"""Run all compiler stages.
|
|
437
437
|
|
|
438
438
|
Initial context will be derived using :meth:`compiler_context` unless a custom
|
|
@@ -470,7 +470,7 @@ class Compiler:
|
|
|
470
470
|
|
|
471
471
|
def run_stages(
|
|
472
472
|
self, stages: Collection[CompilationStage], data: Iterable[Any], context: dict[str, Any]
|
|
473
|
-
) -> tuple[
|
|
473
|
+
) -> tuple[Any, dict[str, Any]]:
|
|
474
474
|
"""Run the given stages in given order on the given data.
|
|
475
475
|
|
|
476
476
|
Args:
|
|
@@ -514,20 +514,16 @@ class Compiler:
|
|
|
514
514
|
calibration_set = context["calibration_set"]
|
|
515
515
|
circuit_metrics = context["circuit_metrics"]
|
|
516
516
|
options = context["options"]
|
|
517
|
-
custom_settings = context.get("custom_settings")
|
|
518
517
|
except Exception as exc:
|
|
519
518
|
raise InsufficientContextError(f"Missing context data for building settings: {exc}") from exc
|
|
520
519
|
|
|
521
520
|
settings = build_settings(
|
|
522
521
|
shots=shots,
|
|
523
|
-
|
|
522
|
+
calibration_set_values=calibration_set,
|
|
524
523
|
builder=builder,
|
|
525
524
|
circuit_metrics=circuit_metrics,
|
|
526
525
|
options=options,
|
|
527
526
|
)
|
|
528
|
-
# if custom_settings are given, use them to override similarly named generated settings
|
|
529
|
-
if custom_settings is not None:
|
|
530
|
-
settings = SettingNode.merge(custom_settings, settings)
|
|
531
527
|
|
|
532
528
|
# fill in the schedule durations to the metrics
|
|
533
529
|
end_delay = calibration_set["controllers.options.end_delay"]
|
iqm/cpc/compiler/dd.py
CHANGED
|
@@ -18,7 +18,6 @@ import logging
|
|
|
18
18
|
import math
|
|
19
19
|
|
|
20
20
|
from iqm.cpc.compiler.errors import ClientError
|
|
21
|
-
from iqm.cpc.interface.compiler import DDStrategy, PRXSequence
|
|
22
21
|
from iqm.pulla.utils import (
|
|
23
22
|
InstructionLocation,
|
|
24
23
|
locate_instructions,
|
|
@@ -30,6 +29,7 @@ from iqm.pulse.playlist.instructions import Instruction, Wait
|
|
|
30
29
|
from iqm.pulse.playlist.schedule import Schedule, Segment
|
|
31
30
|
from iqm.pulse.scheduler import Block
|
|
32
31
|
from iqm.pulse.timebox import TimeBox
|
|
32
|
+
from iqm.station_control.interface.models.circuit import DDStrategy, PRXSequence
|
|
33
33
|
|
|
34
34
|
cpc_logger = logging.getLogger("cpc")
|
|
35
35
|
|
|
@@ -69,9 +69,11 @@ Breakdown of compiler passes of each stage:
|
|
|
69
69
|
"""
|
|
70
70
|
|
|
71
71
|
from collections import Counter
|
|
72
|
-
from collections.abc import Iterable
|
|
72
|
+
from collections.abc import Iterable, Sequence
|
|
73
73
|
from copy import copy, deepcopy
|
|
74
|
-
from
|
|
74
|
+
from dataclasses import replace
|
|
75
|
+
import logging
|
|
76
|
+
from typing import Any, TypeAlias
|
|
75
77
|
|
|
76
78
|
import numpy as np
|
|
77
79
|
|
|
@@ -105,7 +107,6 @@ from iqm.pulla.interface import (
|
|
|
105
107
|
HERALDING_KEY,
|
|
106
108
|
MEASUREMENT_MODE_KEY,
|
|
107
109
|
RESTRICTED_MEASUREMENT_KEYS,
|
|
108
|
-
CalibrationErrors,
|
|
109
110
|
)
|
|
110
111
|
from iqm.pulse.base_utils import merge_dicts
|
|
111
112
|
from iqm.pulse.builder import CircuitOperation, ScheduleBuilder, validate_quantum_circuit
|
|
@@ -116,6 +117,12 @@ from iqm.pulse.playlist.playlist import Playlist
|
|
|
116
117
|
from iqm.pulse.quantum_ops import QuantumOpTable
|
|
117
118
|
from iqm.pulse.timebox import MultiplexedProbeTimeBox, TimeBox
|
|
118
119
|
|
|
120
|
+
CalibrationErrors: TypeAlias = dict[tuple[str, str, Locus], str]
|
|
121
|
+
"""Map from OIL tuple to a CalibrationError message."""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
logger = logging.getLogger(__name__)
|
|
125
|
+
|
|
119
126
|
|
|
120
127
|
# Compilation functions
|
|
121
128
|
def _map_old_operation_names(instructions: Iterable[CircuitOperation]) -> None:
|
|
@@ -519,8 +526,15 @@ def validate_execution_options(circuits: Iterable[Circuit_], options: CircuitExe
|
|
|
519
526
|
MoveGateValidationMode.ALLOW_PRX,
|
|
520
527
|
]:
|
|
521
528
|
raise CircuitError("Full MOVE gate frame tracking requires MOVE gate validation to be 'strict' or 'allow_prx'.")
|
|
529
|
+
if options.convert_terminal_measurements and options.active_reset_cycles is not None:
|
|
530
|
+
options = replace(options, convert_terminal_measurements=False)
|
|
531
|
+
logger.warning(
|
|
532
|
+
"When using active reset, the terminal measurements must also be calibrated to minimize leakage."
|
|
533
|
+
" Thus the terminal measurements cannot be converted to use the fidelity-optimized"
|
|
534
|
+
" `measure_fidelity` operation."
|
|
535
|
+
)
|
|
522
536
|
|
|
523
|
-
return circuits
|
|
537
|
+
return circuits, {"options": options}
|
|
524
538
|
|
|
525
539
|
|
|
526
540
|
@compiler_pass
|
|
@@ -764,7 +778,7 @@ def clean_schedule(schedules: Iterable[Schedule], builder: ScheduleBuilder) -> l
|
|
|
764
778
|
|
|
765
779
|
|
|
766
780
|
@compiler_pass
|
|
767
|
-
def build_playlist(schedules:
|
|
781
|
+
def build_playlist(schedules: Sequence[Schedule], builder: ScheduleBuilder) -> tuple[Playlist, dict[str, Any]]:
|
|
768
782
|
"""Build the playlist from the schedules."""
|
|
769
783
|
playlist = builder.build_playlist(schedules)[0]
|
|
770
784
|
return playlist, {"schedules": schedules} # save the schedules for building settings, debugging, etc
|
|
@@ -77,7 +77,7 @@ from typing import TYPE_CHECKING
|
|
|
77
77
|
from exa.common.data.parameter import CollectionType, DataType, Parameter, Setting
|
|
78
78
|
from exa.common.data.setting_node import SettingNode
|
|
79
79
|
from iqm.cpc.compiler.errors import CalibrationError
|
|
80
|
-
from iqm.pulla.interface import
|
|
80
|
+
from iqm.pulla.interface import CalibrationSetValues
|
|
81
81
|
|
|
82
82
|
if TYPE_CHECKING:
|
|
83
83
|
from exa.common.data.value import ObservationValue
|
|
@@ -118,7 +118,7 @@ class Map:
|
|
|
118
118
|
|
|
119
119
|
def find_observation(
|
|
120
120
|
observation_path: str,
|
|
121
|
-
|
|
121
|
+
calibration_set_values: CalibrationSetValues,
|
|
122
122
|
*,
|
|
123
123
|
required: bool = True,
|
|
124
124
|
) -> ObservationValue:
|
|
@@ -126,7 +126,7 @@ def find_observation(
|
|
|
126
126
|
|
|
127
127
|
Args:
|
|
128
128
|
observation_path: observation we want to find in ``calibration_set``
|
|
129
|
-
|
|
129
|
+
calibration_set_values: mapping of observation paths to observation values
|
|
130
130
|
required: iff ``True`` and the observation cannot be found, raise an error
|
|
131
131
|
|
|
132
132
|
Returns:
|
|
@@ -137,7 +137,7 @@ def find_observation(
|
|
|
137
137
|
found in ``calibration_set``
|
|
138
138
|
|
|
139
139
|
"""
|
|
140
|
-
obs_value =
|
|
140
|
+
obs_value = calibration_set_values.get(observation_path)
|
|
141
141
|
if obs_value is None and required:
|
|
142
142
|
raise CalibrationError(f"Missing calibration observation: {observation_path}")
|
|
143
143
|
return obs_value # type: ignore[return-value]
|
|
@@ -371,7 +371,7 @@ def build_station_settings(
|
|
|
371
371
|
circuit_couplers: Iterable[str],
|
|
372
372
|
measured_probe_lines: Iterable[str],
|
|
373
373
|
shots: int,
|
|
374
|
-
|
|
374
|
+
calibration_set_values: CalibrationSetValues,
|
|
375
375
|
boundary_qubits: Iterable[str],
|
|
376
376
|
boundary_couplers: Iterable[str],
|
|
377
377
|
flux_pulsed_qubits: Collection[str],
|
|
@@ -384,7 +384,7 @@ def build_station_settings(
|
|
|
384
384
|
circuit_couplers: coupler names used in the circuit
|
|
385
385
|
measured_probe_lines: probe line names used in the measurements
|
|
386
386
|
shots: number of times to repeat each circuit's execution
|
|
387
|
-
|
|
387
|
+
calibration_set_values: calibration set as a mapping from observation paths to observation values
|
|
388
388
|
boundary_qubits: physical qubits connected to the boundary_couplers but not in circuit_qubits
|
|
389
389
|
boundary_couplers: coupler names of couplers connected to the circuit boundary but not in circuit_couplers
|
|
390
390
|
flux_pulsed_qubits: names of qubits that have flux pulse capability
|
|
@@ -406,7 +406,9 @@ def build_station_settings(
|
|
|
406
406
|
|
|
407
407
|
"""
|
|
408
408
|
for obs_map in observation_maps:
|
|
409
|
-
value = find_observation(
|
|
409
|
+
value = find_observation(
|
|
410
|
+
obs_map.observation_path(component), calibration_set_values, required=obs_map.required
|
|
411
|
+
)
|
|
410
412
|
if value is not None:
|
|
411
413
|
_create_and_add_setting(obs_map.settings_path(component), obs_map.parameter, value, root)
|
|
412
414
|
|
iqm/cpc/interface/compiler.py
CHANGED
|
@@ -22,6 +22,13 @@ from exa.common.data.setting_node import SettingNode
|
|
|
22
22
|
from iqm.pulse import Circuit
|
|
23
23
|
from iqm.pulse.builder import Locus
|
|
24
24
|
from iqm.pulse.playlist.playlist import Playlist
|
|
25
|
+
from iqm.station_control.interface.models import (
|
|
26
|
+
DDMode,
|
|
27
|
+
DDStrategy,
|
|
28
|
+
HeraldingMode,
|
|
29
|
+
MoveGateFrameTrackingMode,
|
|
30
|
+
MoveGateValidationMode,
|
|
31
|
+
)
|
|
25
32
|
|
|
26
33
|
CircuitBatch: TypeAlias = list[Circuit]
|
|
27
34
|
"""Type that represents a list of quantum circuits to be executed together in a single batch."""
|
|
@@ -42,78 +49,6 @@ class MeasurementMode(StrEnum):
|
|
|
42
49
|
This is typically how measurement is calibrated."""
|
|
43
50
|
|
|
44
51
|
|
|
45
|
-
class HeraldingMode(StrEnum):
|
|
46
|
-
"""Heralding mode for circuit execution."""
|
|
47
|
-
|
|
48
|
-
NONE = "none"
|
|
49
|
-
"""Do not do any heralding."""
|
|
50
|
-
ZEROS = "zeros"
|
|
51
|
-
"""Perform a heralding measurement on all the components used in each circuit (if they have
|
|
52
|
-
measurement data available in the calset), only retain shots where all the components
|
|
53
|
-
are measured to be in the zero state."""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class DDMode(StrEnum):
|
|
57
|
-
"""Dynamical Decoupling (DD) mode for circuit execution."""
|
|
58
|
-
|
|
59
|
-
DISABLED = "disabled"
|
|
60
|
-
"""Do not apply dynamical decoupling."""
|
|
61
|
-
ENABLED = "enabled"
|
|
62
|
-
"""Apply dynamical decoupling."""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
PRXSequence: TypeAlias = list[tuple[float, float]]
|
|
66
|
-
"""A sequence of PRX gates. A generic PRX gate is defined by rotation angle and phase angle, Theta and Phi
|
|
67
|
-
respectively."""
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
@dataclass
|
|
71
|
-
class DDStrategy:
|
|
72
|
-
"""Describes a particular dynamical decoupling strategy.
|
|
73
|
-
|
|
74
|
-
The current standard DD stategy can be found in :attr:`.STANDARD_DD_STRATEGY`,
|
|
75
|
-
but users can use this class to provide their own dynamical decoupling strategies.
|
|
76
|
-
|
|
77
|
-
See :cite:`Ezzell_2022` for information on DD sequences.
|
|
78
|
-
"""
|
|
79
|
-
|
|
80
|
-
merge_contiguous_waits: bool = True
|
|
81
|
-
"""Merge contiguous ``Wait`` instructions into one if they are separated only by ``Block`` instructions."""
|
|
82
|
-
|
|
83
|
-
target_qubits: frozenset[str] | None = None
|
|
84
|
-
"""Qubits on which dynamical decoupling should be applied. If ``None``, all qubits are targeted."""
|
|
85
|
-
|
|
86
|
-
skip_leading_wait: bool = True
|
|
87
|
-
"""Skip processing leading ``Wait`` instructions."""
|
|
88
|
-
|
|
89
|
-
skip_trailing_wait: bool = True
|
|
90
|
-
"""Skip processing trailing ``Wait`` instructions."""
|
|
91
|
-
|
|
92
|
-
gate_sequences: list[tuple[int, str | PRXSequence, str]] = field(default_factory=list)
|
|
93
|
-
"""Available decoupling gate sequences to chose from in this strategy.
|
|
94
|
-
|
|
95
|
-
Each sequence is defined by a tuple of ``(ratio, gate pattern, align)``:
|
|
96
|
-
|
|
97
|
-
* ratio: Minimal duration for the sequence (in PRX gate durations).
|
|
98
|
-
|
|
99
|
-
* gate pattern: Gate pattern can be defined in two ways. It can be a string containing "X" and "Y" characters,
|
|
100
|
-
encoding a PRX gate sequence. For example, "YXYX" corresponds to the
|
|
101
|
-
XY4 sequence, "XYXYYXYX" to the EDD sequence, etc. If more flexibility is needed, a gate pattern can be
|
|
102
|
-
defined as a sequence of PRX gate argument tuples (that contain a rotation angle and a phase angle). For
|
|
103
|
-
example, sequence "YX" could be written as ``[(math.pi, math.pi / 2), (math.pi, 0)]``.
|
|
104
|
-
|
|
105
|
-
* align: Controls the alignment of the sequence within the time window it is inserted in. Supported values:
|
|
106
|
-
|
|
107
|
-
- "asap": Corresponds to a ASAP-aligned sequence with no waiting time before the first pulse.
|
|
108
|
-
- "center": Corresponds to a symmetric sequence.
|
|
109
|
-
- "alap": Corresponds to a ALAP-aligned sequence.
|
|
110
|
-
|
|
111
|
-
The Dynamical Decoupling algorithm uses the best fitting gate sequence by first sorting them
|
|
112
|
-
by ``ratio`` in descending order. Then the longest fitting pattern is determined by comparing ``ratio``
|
|
113
|
-
with the duration of the time window divided by the PRX gate duration.
|
|
114
|
-
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
52
|
class CircuitBoundaryMode(StrEnum):
|
|
118
53
|
"""Circuit boundary mode for circuit compilation."""
|
|
119
54
|
|
|
@@ -130,32 +65,6 @@ class CircuitBoundaryMode(StrEnum):
|
|
|
130
65
|
"""Circuit boundary consists of all the QPU elements that are not used in the circuit."""
|
|
131
66
|
|
|
132
67
|
|
|
133
|
-
class MoveGateValidationMode(StrEnum):
|
|
134
|
-
"""MOVE gate validation mode for circuit compilation."""
|
|
135
|
-
|
|
136
|
-
STRICT = "strict"
|
|
137
|
-
"""Perform standard MOVE gate validation: MOVE(qubit, resonator) gates must only
|
|
138
|
-
appear in sandwiches (pairs). Inside a sandwich there must be no gates acting on the
|
|
139
|
-
MOVE qubit, and no other MOVE gates acting on the resonator."""
|
|
140
|
-
ALLOW_PRX = "allow_prx"
|
|
141
|
-
"""Allow PRX gates on the MOVE qubit inside MOVE sandwiches during validation."""
|
|
142
|
-
NONE = "none"
|
|
143
|
-
"""Do not perform any MOVE gate validation."""
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
class MoveGateFrameTrackingMode(StrEnum):
|
|
147
|
-
"""MOVE gate frame tracking mode for circuit compilation."""
|
|
148
|
-
|
|
149
|
-
FULL = "full"
|
|
150
|
-
"""Perform complete MOVE gate frame tracking, applying both the explicit z rotations
|
|
151
|
-
on the resonator and the dynamic phase correction due to qubit-resonator detuning to
|
|
152
|
-
the qubit at the end of a MOVE sandwich."""
|
|
153
|
-
NO_DETUNING_CORRECTION = "no_detuning_correction"
|
|
154
|
-
"""Do not apply the detuning correction at the end of a MOVE sandwich."""
|
|
155
|
-
NONE = "none"
|
|
156
|
-
"""Do not perform any MOVE gate frame tracking."""
|
|
157
|
-
|
|
158
|
-
|
|
159
68
|
@dataclass(frozen=True)
|
|
160
69
|
class CircuitExecutionOptions:
|
|
161
70
|
"""Various discrete options for quantum circuit execution."""
|
iqm/pulla/calibration.py
CHANGED
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
from copy import deepcopy
|
|
18
18
|
import logging
|
|
19
|
-
import
|
|
19
|
+
from uuid import UUID
|
|
20
20
|
|
|
21
|
-
from iqm.
|
|
21
|
+
from iqm.iqm_server_client.iqm_server_client import _IQMServerClient
|
|
22
|
+
|
|
23
|
+
from iqm.pulla.interface import CalibrationSetValues
|
|
22
24
|
from iqm.pulla.utils import calset_from_observations
|
|
23
|
-
from iqm.station_control.
|
|
24
|
-
from iqm.station_control.interface.models import ObservationSetData
|
|
25
|
-
from iqm.station_control.interface.station_control import StationControlInterface
|
|
25
|
+
from iqm.station_control.interface.models import StrUUID
|
|
26
26
|
|
|
27
27
|
logger = logging.getLogger(__name__)
|
|
28
28
|
|
|
@@ -30,66 +30,45 @@ CalibrationDataFetchException = RuntimeError
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class CalibrationDataProvider:
|
|
33
|
-
"""Access calibration info via
|
|
33
|
+
"""Access calibration info via IQM Server and cache data in memory."""
|
|
34
34
|
|
|
35
|
-
def __init__(self,
|
|
36
|
-
self.
|
|
37
|
-
self._calibration_sets: dict[
|
|
35
|
+
def __init__(self, iqm_server_client: _IQMServerClient):
|
|
36
|
+
self._iqm_server_client = iqm_server_client
|
|
37
|
+
self._calibration_sets: dict[UUID, CalibrationSetValues] = {}
|
|
38
38
|
|
|
39
|
-
def
|
|
39
|
+
def get_calibration_set_values(self, calibration_set_id: UUID) -> CalibrationSetValues:
|
|
40
40
|
"""Get the calibration set contents from the database and cache it."""
|
|
41
|
-
logger.debug("Get the calibration set from the database: cal_set_id=%s",
|
|
41
|
+
logger.debug("Get the calibration set from the database: cal_set_id=%s", calibration_set_id)
|
|
42
42
|
try:
|
|
43
|
-
if
|
|
44
|
-
cal_set_values = self.
|
|
45
|
-
self._calibration_sets[
|
|
46
|
-
return deepcopy(self._calibration_sets[
|
|
43
|
+
if calibration_set_id not in self._calibration_sets:
|
|
44
|
+
cal_set_values = self._get_calibration_set_values(calibration_set_id)
|
|
45
|
+
self._calibration_sets[calibration_set_id] = cal_set_values
|
|
46
|
+
return deepcopy(self._calibration_sets[calibration_set_id])
|
|
47
47
|
except Exception as e:
|
|
48
48
|
raise CalibrationDataFetchException("Could not fetch calibration set from the database.") from e
|
|
49
49
|
|
|
50
|
-
def
|
|
51
|
-
"""Get the
|
|
52
|
-
logger.debug("Get the
|
|
50
|
+
def get_default_calibration_set(self) -> tuple[CalibrationSetValues, UUID]:
|
|
51
|
+
"""Get the default calibration set id from the database, return it and the set contents."""
|
|
52
|
+
logger.debug("Get the default calibration set")
|
|
53
53
|
try:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
else:
|
|
57
|
-
latest_calibration_set = self._get_latest_calibration_set(chip_label)
|
|
58
|
-
latest_cal_set_id = latest_calibration_set.observation_set_id
|
|
59
|
-
calset = self.get_calibration_set(latest_cal_set_id)
|
|
54
|
+
default_calibration_set = self._iqm_server_client.get_calibration_set("default")
|
|
55
|
+
default_calibration_set_values = self.get_calibration_set_values(default_calibration_set.observation_set_id)
|
|
60
56
|
except Exception as e:
|
|
61
57
|
raise CalibrationDataFetchException(
|
|
62
|
-
f"Could not fetch
|
|
58
|
+
f"Could not fetch default calibration set id from the database: {e}"
|
|
63
59
|
) from e
|
|
64
|
-
return
|
|
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]
|
|
60
|
+
return default_calibration_set_values, default_calibration_set.observation_set_id
|
|
76
61
|
|
|
77
|
-
def
|
|
62
|
+
def _get_calibration_set_values(self, calibration_set_id: StrUUID) -> CalibrationSetValues:
|
|
78
63
|
"""Get saved calibration set observations by UUID.
|
|
79
64
|
|
|
80
65
|
Args:
|
|
81
|
-
calibration_set_id: UUID of the calibration set to retrieve.
|
|
66
|
+
calibration_set_id: UUID of the calibration set to retrieve or "default".
|
|
82
67
|
|
|
83
68
|
Returns:
|
|
84
69
|
Dictionary of observations belonging to the given calibration set.
|
|
85
70
|
|
|
86
71
|
"""
|
|
87
|
-
|
|
88
|
-
|
|
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)
|
|
72
|
+
calibration_set = self._iqm_server_client.get_calibration_set(calibration_set_id)
|
|
73
|
+
calibration_set_values = calset_from_observations(calibration_set.observations)
|
|
95
74
|
return calibration_set_values
|
iqm/pulla/interface.py
CHANGED
|
@@ -12,18 +12,11 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
"""Common data types and exceptions for the IQM Pulla interface.
|
|
15
|
+
"""Common data types and exceptions for the IQM Pulla interface."""
|
|
16
16
|
|
|
17
|
-
Many of these must be identical to those in iqm-client.
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
from dataclasses import dataclass
|
|
21
|
-
from enum import StrEnum
|
|
22
17
|
from typing import TypeAlias
|
|
23
|
-
from uuid import UUID
|
|
24
18
|
|
|
25
19
|
from exa.common.data.value import ObservationValue
|
|
26
|
-
from iqm.cpc.interface.compiler import Locus
|
|
27
20
|
|
|
28
21
|
|
|
29
22
|
class CHADRetrievalException(Exception):
|
|
@@ -38,56 +31,8 @@ class ChipLabelRetrievalException(Exception):
|
|
|
38
31
|
"""Exception for chip label retrieval failures."""
|
|
39
32
|
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class TaskStatus(StrEnum):
|
|
46
|
-
"""Status of a Station Control task."""
|
|
47
|
-
|
|
48
|
-
READY = "READY"
|
|
49
|
-
"""Task has completed successfully"""
|
|
50
|
-
|
|
51
|
-
FAILED = "FAILED"
|
|
52
|
-
"""Task has failed"""
|
|
53
|
-
|
|
54
|
-
PROGRESS = "PROGRESS"
|
|
55
|
-
"""Task is being executed"""
|
|
56
|
-
|
|
57
|
-
PENDING = "PENDING"
|
|
58
|
-
"""Task is waiting to be executed"""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
CalibrationSet: TypeAlias = dict[str, ObservationValue]
|
|
62
|
-
CalibrationSetId: TypeAlias = UUID
|
|
63
|
-
|
|
64
|
-
CircuitMeasurementResults: TypeAlias = dict[str, list[list[int]]]
|
|
65
|
-
"""Measurement results from a single circuit/schedule. For each measurement operation in the circuit,
|
|
66
|
-
maps the measurement key to the corresponding results. The outer list elements correspond to shots,
|
|
67
|
-
and the inner list elements to the qubits measured in the measurement operation."""
|
|
68
|
-
|
|
69
|
-
CircuitMeasurementResultsBatch: TypeAlias = list[CircuitMeasurementResults]
|
|
70
|
-
"""Type that represents measurement results for a batch of circuits."""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@dataclass
|
|
74
|
-
class StationControlResult:
|
|
75
|
-
"""Result of a station control task"""
|
|
76
|
-
|
|
77
|
-
sweep_id: UUID
|
|
78
|
-
"""ID of the executed sweep"""
|
|
79
|
-
task_id: UUID # TODO? Rename to job_id
|
|
80
|
-
"""ID of the station control task"""
|
|
81
|
-
status: TaskStatus
|
|
82
|
-
"""Status of the station control task"""
|
|
83
|
-
start_time: str | None = None
|
|
84
|
-
"""Time when the sweep began in the station control"""
|
|
85
|
-
end_time: str | None = None
|
|
86
|
-
"""Time when the sweep ended in the station control"""
|
|
87
|
-
result: CircuitMeasurementResultsBatch | None = None
|
|
88
|
-
"""Sweep results converted to the circuit measurement results expected by the client"""
|
|
89
|
-
message: str | None = None
|
|
90
|
-
"""Information about task failure"""
|
|
34
|
+
CalibrationSetValues: TypeAlias = dict[str, ObservationValue]
|
|
35
|
+
"""Map from observation name to its value."""
|
|
91
36
|
|
|
92
37
|
|
|
93
38
|
ACQUISITION_LABEL_KEY = "m{idx}"
|