iqm-pulse 9.20.0__py3-none-any.whl → 10.0.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/pulse/builder.py +193 -92
- iqm/pulse/circuit_operations.py +0 -5
- iqm/pulse/gate_implementation.py +242 -100
- iqm/pulse/gates/__init__.py +67 -183
- iqm/pulse/gates/conditional.py +3 -1
- iqm/pulse/gates/cz.py +7 -15
- iqm/pulse/gates/default_gates.py +17 -117
- iqm/pulse/gates/flux_multiplexer.py +2 -2
- iqm/pulse/gates/measure.py +46 -41
- iqm/pulse/gates/prx.py +0 -5
- iqm/pulse/gates/reset.py +8 -16
- iqm/pulse/gates/rz.py +1 -1
- iqm/pulse/gates/sx.py +1 -1
- iqm/pulse/gates/u.py +1 -1
- iqm/pulse/playlist/instructions.py +7 -7
- iqm/pulse/quantum_ops.py +29 -31
- iqm/pulse/utils.py +0 -92
- {iqm_pulse-9.20.0.dist-info → iqm_pulse-10.0.0.dist-info}/METADATA +1 -1
- {iqm_pulse-9.20.0.dist-info → iqm_pulse-10.0.0.dist-info}/RECORD +22 -22
- {iqm_pulse-9.20.0.dist-info → iqm_pulse-10.0.0.dist-info}/LICENSE.txt +0 -0
- {iqm_pulse-9.20.0.dist-info → iqm_pulse-10.0.0.dist-info}/WHEEL +0 -0
- {iqm_pulse-9.20.0.dist-info → iqm_pulse-10.0.0.dist-info}/top_level.txt +0 -0
iqm/pulse/gates/measure.py
CHANGED
|
@@ -61,11 +61,11 @@ class Measure_CustomWaveforms(CustomIQWaveforms):
|
|
|
61
61
|
``class MyGate(Measure_CustomWaveforms, i_wave=Something, q_wave=SomethingElse)``.
|
|
62
62
|
|
|
63
63
|
The ``measure`` operation is factorizable, and its :attr:`arity` is 0, which together mean that it can operate
|
|
64
|
-
on loci of any
|
|
65
|
-
``len(locus) > 1
|
|
66
|
-
:class:`.TimeBox` is constructed from the calibrated single-component
|
|
64
|
+
on loci of any length, but is calibrated only on single component loci. When the gate is constructed in the
|
|
65
|
+
``len(locus) > 1`` case (e.g. ``builder.get_implementation('measure', ('QB1', 'QB2', 'QB3'))()``), the resulting
|
|
66
|
+
:class:`.TimeBox` is constructed from the calibrated single-component implementations.
|
|
67
67
|
|
|
68
|
-
For each measured component, the readout
|
|
68
|
+
For each measured component, the readout :class:`.IQPulse` will be modulated with the
|
|
69
69
|
intermediate frequency (IF), computed as the difference between the readout
|
|
70
70
|
frequency of that component and the probe line center frequency, and offset in phase
|
|
71
71
|
by the readout phase of the component.
|
|
@@ -80,6 +80,7 @@ class Measure_CustomWaveforms(CustomIQWaveforms):
|
|
|
80
80
|
"frequency": Parameter("", "Readout pulse frequency", "Hz"),
|
|
81
81
|
"phase": Parameter("", "Readout pulse phase", "rad"),
|
|
82
82
|
"amplitude_i": Parameter("", "Readout channel I amplitude", ""),
|
|
83
|
+
# TODO do we really need these defaults? are they used anywhere?
|
|
83
84
|
"amplitude_q": Setting(Parameter("", "Readout channel Q amplitude", ""), 0.0),
|
|
84
85
|
"integration_length": Parameter("", "Integration length", "s"),
|
|
85
86
|
"integration_weights_I": Setting(
|
|
@@ -105,36 +106,38 @@ class Measure_CustomWaveforms(CustomIQWaveforms):
|
|
|
105
106
|
self._time_traces: dict[tuple[str, float | None, float | None, str], TimeBox] = {}
|
|
106
107
|
"""Cache for :meth:`time_trace`."""
|
|
107
108
|
|
|
108
|
-
if len(locus) == 1:
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
if len(locus) == 1:
|
|
110
|
+
# prepare the single-component measurement
|
|
111
|
+
probe_line: ProbeChannelProperties = builder.channels[ # type: ignore[assignment]
|
|
112
|
+
builder.get_probe_channel(locus[0])
|
|
111
113
|
]
|
|
112
|
-
|
|
113
|
-
if_freq = (calibration_data["frequency"] - c_freq) / self._probe_line.sample_rate
|
|
114
|
+
# readout duration is determined by the acquisition, probe pulses are truncated to fit this window
|
|
114
115
|
self._duration = (
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
probe_line.duration_to_int_samples(
|
|
117
|
+
probe_line.round_duration_to_granularity(
|
|
117
118
|
calibration_data["acquisition_delay"] + calibration_data["integration_length"]
|
|
118
119
|
)
|
|
119
120
|
)
|
|
120
|
-
+
|
|
121
|
+
+ probe_line.integration_stop_dead_time
|
|
121
122
|
)
|
|
122
|
-
|
|
123
|
+
self._probe_offset = probe_line.integration_start_dead_time
|
|
124
|
+
# "duration" is only used by the probe pulse
|
|
123
125
|
waveform_params = self.convert_calibration_data(
|
|
124
126
|
calibration_data,
|
|
125
|
-
{k: v for k, v in self.parameters.items() if k not in self.root_parameters
|
|
126
|
-
|
|
127
|
+
{k: v for k, v in self.parameters.items() if k not in self.root_parameters},
|
|
128
|
+
probe_line,
|
|
127
129
|
)
|
|
130
|
+
# unconverted cal data that corresponds to a root param (not duration)
|
|
128
131
|
root_params = {k: v for k, v in calibration_data.items() if k in self.root_parameters and k != "duration"}
|
|
132
|
+
# do some conversions TODO are these consistent?
|
|
133
|
+
root_params["integration_length"] = probe_line.duration_to_int_samples(root_params["integration_length"])
|
|
134
|
+
root_params["acquisition_delay"] = round(probe_line.duration_to_samples(root_params["acquisition_delay"]))
|
|
129
135
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
self._acquisition_method =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
# we need to store the possible cal_data == priority calibration in order to propagate it to the factored
|
|
136
|
-
# single-component measure calls in :meth:`probe_timebox`
|
|
137
|
-
self._prio_calibration = calibration_data or None
|
|
136
|
+
if_freq = (calibration_data["frequency"] - probe_line.center_frequency) / probe_line.sample_rate
|
|
137
|
+
|
|
138
|
+
self._probe_instruction, self._acquisition_method = self._build_instructions(
|
|
139
|
+
waveform_params, root_params, if_freq
|
|
140
|
+
)
|
|
138
141
|
|
|
139
142
|
def _build_instructions(
|
|
140
143
|
self, waveform_params: OILCalibrationData, root_params: OILCalibrationData, if_freq: float
|
|
@@ -160,7 +163,7 @@ class Measure_CustomWaveforms(CustomIQWaveforms):
|
|
|
160
163
|
modulation_frequency=if_freq,
|
|
161
164
|
)
|
|
162
165
|
|
|
163
|
-
integration_length =
|
|
166
|
+
integration_length = root_params["integration_length"]
|
|
164
167
|
weights_i = root_params.get("integration_weights_I")
|
|
165
168
|
weights_q = root_params.get("integration_weights_Q")
|
|
166
169
|
if weights_i is not None and weights_i.size and weights_q is not None and weights_q.size:
|
|
@@ -195,16 +198,15 @@ class Measure_CustomWaveforms(CustomIQWaveforms):
|
|
|
195
198
|
)
|
|
196
199
|
|
|
197
200
|
acquisition_type = root_params.get("acquisition_type", self.root_parameters["acquisition_type"].value) # type: ignore[union-attr]
|
|
198
|
-
acquisition_delay = round(self._probe_line.duration_to_samples(root_params["acquisition_delay"]))
|
|
199
201
|
acquisition_label = "TO_BE_REPLACED"
|
|
200
202
|
if acquisition_type == "complex":
|
|
201
203
|
acquisition_method = ComplexIntegration(
|
|
202
|
-
label=acquisition_label, delay_samples=acquisition_delay, weights=weights
|
|
204
|
+
label=acquisition_label, delay_samples=root_params["acquisition_delay"], weights=weights
|
|
203
205
|
)
|
|
204
206
|
elif acquisition_type == "threshold":
|
|
205
207
|
acquisition_method = ThresholdStateDiscrimination(
|
|
206
208
|
label=acquisition_label,
|
|
207
|
-
delay_samples=acquisition_delay,
|
|
209
|
+
delay_samples=root_params["acquisition_delay"],
|
|
208
210
|
weights=weights,
|
|
209
211
|
threshold=root_params["integration_threshold"],
|
|
210
212
|
)
|
|
@@ -243,12 +245,11 @@ class Measure_CustomWaveforms(CustomIQWaveforms):
|
|
|
243
245
|
replacements = {"label": f"{self.locus[0]}__{label_key}"}
|
|
244
246
|
if feedback_key and isinstance(self._acquisition_method, ThresholdStateDiscrimination):
|
|
245
247
|
# TODO: use the actual ``feedback_key`` when AWGs support multiple feedback labels
|
|
246
|
-
|
|
247
|
-
replacements["feedback_signal_label"] = feedback_bit
|
|
248
|
+
replacements["feedback_signal_label"] = f"{self.locus[0]}__{FEEDBACK_KEY}"
|
|
248
249
|
acquisitions = (replace(self._acquisition_method, **replacements),) if do_acquisition else () # type: ignore[arg-type]
|
|
249
250
|
multiplexed_iq = MultiplexedIQPulse(
|
|
250
|
-
duration=self._probe_instruction.duration + self.
|
|
251
|
-
entries=((self._probe_instruction, self.
|
|
251
|
+
duration=self._probe_instruction.duration + self._probe_offset,
|
|
252
|
+
entries=((self._probe_instruction, self._probe_offset),),
|
|
252
253
|
)
|
|
253
254
|
readout_trigger = ReadoutTrigger(
|
|
254
255
|
duration=self._duration, probe_pulse=multiplexed_iq, acquisitions=acquisitions
|
|
@@ -286,12 +287,12 @@ class Measure_CustomWaveforms(CustomIQWaveforms):
|
|
|
286
287
|
label=f"{self.__class__.__name__} on {self.locus}",
|
|
287
288
|
)
|
|
288
289
|
else:
|
|
290
|
+
# factorizability: use the sub-implementations
|
|
289
291
|
# _skip_override used for child classes build on `Measure_CustomWaveforms` to not call `.probe_timebox`
|
|
290
292
|
# from the parent class, but from this class instead.
|
|
293
|
+
# TODO remove, make probe_timebox private or something.
|
|
291
294
|
probe_timeboxes = [
|
|
292
|
-
self.
|
|
293
|
-
self.parent.name, (c,), impl_name=self.name, priority_calibration=self._prio_calibration
|
|
294
|
-
).probe_timebox(key, feedback_key, do_acquisition, _skip_override=True)
|
|
295
|
+
self.sub_implementations[c].probe_timebox(key, feedback_key, do_acquisition, _skip_override=True) # type: ignore[attr-defined]
|
|
295
296
|
for c in self.locus
|
|
296
297
|
]
|
|
297
298
|
probe_timebox = functools.reduce(lambda x, y: x + y, probe_timeboxes)
|
|
@@ -446,10 +447,14 @@ class ProbePulse_CustomWaveforms(CustomIQWaveforms):
|
|
|
446
447
|
With given :class:`.Waveform` waveform definitions ``Something`` and ``SomethingElse``,
|
|
447
448
|
you may define a measurement implementation that uses them as follows:
|
|
448
449
|
``class MyGate(ProbePulse_CustomWaveforms, i_wave=Something, q_wave=SomethingElse)``.
|
|
450
|
+
The measurement :class:`.IQPulse` instruction will not be automatically modulated
|
|
451
|
+
by any frequency, so any modulations should be included in the I and Q waveforms themselves.
|
|
452
|
+
|
|
453
|
+
Due to device limitations this implementation also has to integrate the readout signal
|
|
454
|
+
(using arbitrary weights), even though it does not make much sense.
|
|
449
455
|
|
|
450
456
|
Contrary to the ``Measure_CustomWaveforms`` class, this implementation acts on proble lines directly (i.e. its
|
|
451
|
-
``locus`` is a single probe line).
|
|
452
|
-
by any frequency, so any modulations should be included in the I and Q waveforms themselves.
|
|
457
|
+
``locus`` is a single probe line).
|
|
453
458
|
"""
|
|
454
459
|
|
|
455
460
|
root_parameters = {
|
|
@@ -465,8 +470,8 @@ class ProbePulse_CustomWaveforms(CustomIQWaveforms):
|
|
|
465
470
|
self, parent: QuantumOp, name: str, locus: Locus, calibration_data: OILCalibrationData, builder: ScheduleBuilder
|
|
466
471
|
):
|
|
467
472
|
super().__init__(parent, name, locus, calibration_data, builder)
|
|
468
|
-
self._probe_line: ProbeChannelProperties =
|
|
469
|
-
|
|
473
|
+
self._probe_line: ProbeChannelProperties = builder.channels[ # type: ignore[assignment]
|
|
474
|
+
builder.component_channels[locus[0]]["readout"]
|
|
470
475
|
]
|
|
471
476
|
self._duration = (
|
|
472
477
|
self._probe_line.duration_to_int_samples(
|
|
@@ -479,7 +484,7 @@ class ProbePulse_CustomWaveforms(CustomIQWaveforms):
|
|
|
479
484
|
|
|
480
485
|
waveform_params = self.convert_calibration_data(
|
|
481
486
|
calibration_data,
|
|
482
|
-
{k: v for k, v in self.parameters.items() if k not in self.root_parameters
|
|
487
|
+
{k: v for k, v in self.parameters.items() if k not in self.root_parameters},
|
|
483
488
|
self._probe_line,
|
|
484
489
|
)
|
|
485
490
|
root_params = {k: v for k, v in calibration_data.items() if k in self.root_parameters and k != "duration"}
|
|
@@ -602,8 +607,8 @@ class ProbePulse_CustomWaveforms_noIntegration(CustomIQWaveforms):
|
|
|
602
607
|
"""Cache for :meth:`probe_timebox`."""
|
|
603
608
|
|
|
604
609
|
if len(locus) == 1: # factorizable gates only need calibration on 1-loci
|
|
605
|
-
self._probe_line: ProbeChannelProperties =
|
|
606
|
-
|
|
610
|
+
self._probe_line: ProbeChannelProperties = builder.channels[ # type: ignore[assignment]
|
|
611
|
+
builder.get_probe_channel(locus[0])
|
|
607
612
|
]
|
|
608
613
|
c_freq = self._probe_line.center_frequency
|
|
609
614
|
if_freq = (calibration_data["frequency"] - c_freq) / self._probe_line.sample_rate
|
iqm/pulse/gates/prx.py
CHANGED
|
@@ -211,11 +211,6 @@ class PRX_SinglePulse_GateImplementation(SinglePulseGate, PrxGateImplementation)
|
|
|
211
211
|
timebox.neighborhood_components[0] = set(self.locus)
|
|
212
212
|
return timebox
|
|
213
213
|
|
|
214
|
-
@property
|
|
215
|
-
def iq_pulse(self) -> IQPulse:
|
|
216
|
-
"""Alias for ``self.pulse`` for backward compatibility"""
|
|
217
|
-
return self.pulse # type: ignore[return-value]
|
|
218
|
-
|
|
219
214
|
|
|
220
215
|
class PRX_CustomWaveforms(PRX_SinglePulse_GateImplementation, CustomIQWaveforms):
|
|
221
216
|
"""Base class for PRX gates implemented using a single IQ pulse and hot-swappable waveforms."""
|
iqm/pulse/gates/reset.py
CHANGED
|
@@ -18,7 +18,7 @@ The reset operation is a non-unitary quantum channel that sets the state of a qu
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
-
from collections.abc import Iterable
|
|
21
|
+
from collections.abc import Iterable, Mapping
|
|
22
22
|
|
|
23
23
|
from exa.common.data.parameter import Parameter
|
|
24
24
|
from exa.common.qcm_data.chip_topology import ChipTopology
|
|
@@ -53,7 +53,7 @@ class Reset_Conditional(CompositeGate):
|
|
|
53
53
|
blocked.
|
|
54
54
|
"""
|
|
55
55
|
|
|
56
|
-
registered_gates =
|
|
56
|
+
registered_gates = ("measure", "cc_prx")
|
|
57
57
|
|
|
58
58
|
def _call(self) -> TimeBox: # type: ignore[override]
|
|
59
59
|
# find locus components that are resettable via conditional reset
|
|
@@ -120,15 +120,8 @@ class Reset_Wait(GateImplementation):
|
|
|
120
120
|
if len(self.locus) == 1:
|
|
121
121
|
waits_box = self.builder.wait(self.locus, self.calibration_data["duration"], rounding=True)
|
|
122
122
|
else:
|
|
123
|
-
|
|
124
|
-
waits_box = TimeBox.composite(
|
|
125
|
-
[
|
|
126
|
-
self.builder.get_implementation( # type: ignore[attr-defined]
|
|
127
|
-
self.parent.name, (q,), impl_name=self.name, priority_calibration=prio_calibration
|
|
128
|
-
).wait_box()
|
|
129
|
-
for q in self.locus
|
|
130
|
-
]
|
|
131
|
-
)
|
|
123
|
+
# factorizability: use the sub-implementations
|
|
124
|
+
waits_box = TimeBox.composite([self.sub_implementations[c].wait_box() for c in self.locus]) # type: ignore[attr-defined]
|
|
132
125
|
return waits_box
|
|
133
126
|
|
|
134
127
|
def _call(self) -> TimeBox:
|
|
@@ -147,14 +140,13 @@ class Reset_Wait(GateImplementation):
|
|
|
147
140
|
return TimeBox.composite([waits_box])
|
|
148
141
|
|
|
149
142
|
def duration_in_seconds(self) -> float:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
)
|
|
143
|
+
if len(self.locus) == 1:
|
|
144
|
+
return self.calibration_data["duration"]
|
|
145
|
+
return max(self.sub_implementations[c].calibration_data["duration"] for c in self.locus)
|
|
154
146
|
|
|
155
147
|
@classmethod
|
|
156
148
|
def get_custom_locus_mapping(
|
|
157
|
-
cls, chip_topology: ChipTopology, component_to_channels:
|
|
149
|
+
cls, chip_topology: ChipTopology, component_to_channels: Mapping[str, Iterable[str]]
|
|
158
150
|
) -> dict[tuple[str, ...] | frozenset[str], tuple[str, ...]] | None:
|
|
159
151
|
"""Supported loci: all components that have channels."""
|
|
160
152
|
return {(c,): (c,) for c in chip_topology.all_components}
|
iqm/pulse/gates/rz.py
CHANGED
|
@@ -251,7 +251,7 @@ class RZ_ACStarkShift_smoothConstant( # type: ignore[call-arg] # type: ignore[
|
|
|
251
251
|
class RZ_PRX_Composite(CompositeGate):
|
|
252
252
|
"""RZ gate implemented as a sequence of PRX gates."""
|
|
253
253
|
|
|
254
|
-
registered_gates =
|
|
254
|
+
registered_gates = ("prx",)
|
|
255
255
|
|
|
256
256
|
def __init__(self, parent, name, locus, calibration_data, builder):
|
|
257
257
|
super().__init__(parent, name, locus, calibration_data, builder)
|
iqm/pulse/gates/sx.py
CHANGED
|
@@ -38,7 +38,7 @@ if TYPE_CHECKING: # pragma: no cover
|
|
|
38
38
|
class SXGate(CompositeGate):
|
|
39
39
|
"""SX gate implementation based on PRX gate, by limiting the angle to pi / 2."""
|
|
40
40
|
|
|
41
|
-
registered_gates =
|
|
41
|
+
registered_gates = ("prx",)
|
|
42
42
|
|
|
43
43
|
def _call(self) -> TimeBox: # type: ignore[override]
|
|
44
44
|
"""Call PRX gate with angle equals to pi / 2."""
|
iqm/pulse/gates/u.py
CHANGED
|
@@ -91,7 +91,7 @@ class UGate(CompositeGate):
|
|
|
91
91
|
Assumes the chosen PRX implementation uses resonant driving, and that the virtual RZ technique can be used.
|
|
92
92
|
"""
|
|
93
93
|
|
|
94
|
-
registered_gates =
|
|
94
|
+
registered_gates = ("prx",)
|
|
95
95
|
|
|
96
96
|
def _call(self, theta: float, phi: float = 0.0, lam: float = 0.0) -> TimeBox: # type: ignore[override]
|
|
97
97
|
r"""Convert pulses into timebox, via Euler decomposition.
|
|
@@ -136,17 +136,17 @@ class IQPulse(Instruction):
|
|
|
136
136
|
scale_q: float = 0.0
|
|
137
137
|
"""Scaling factor for the Q quadrature."""
|
|
138
138
|
phase: float = 0.0
|
|
139
|
-
"""Phase of the pulse relative to the channel
|
|
139
|
+
"""Phase of the pulse relative to the channel upconversion oscillator ("carrier wave"), in radians."""
|
|
140
140
|
modulation_frequency: float = 0.0
|
|
141
141
|
"""Modulation frequency of the waveforms, in units of the sampling rate.
|
|
142
|
-
This modulation is additional to the channel frequency.
|
|
142
|
+
This modulation is additional to the channel upconversion frequency.
|
|
143
143
|
The default value of 0.0 does not modulate.
|
|
144
144
|
Note that the phase of this modulation resets for every instruction, that is, successive instances of the same
|
|
145
145
|
modulated pulse are not phase coherent.
|
|
146
146
|
"""
|
|
147
147
|
phase_increment: float = 0.0
|
|
148
|
-
"""
|
|
149
|
-
are played after it
|
|
148
|
+
"""Phase increment for the channel upconversion oscillator ("carrier wave"), affecting this pulse and
|
|
149
|
+
all pulses that are played after it on the channel, in radians.
|
|
150
150
|
"""
|
|
151
151
|
|
|
152
152
|
def validate(self):
|
|
@@ -165,9 +165,9 @@ class ConditionalInstruction(Instruction):
|
|
|
165
165
|
"""Choice between multiple Instructions, depending on a condition."""
|
|
166
166
|
|
|
167
167
|
condition: str
|
|
168
|
-
"""
|
|
168
|
+
"""Can be evaluated to an integer >= 0 representing an outcome."""
|
|
169
169
|
outcomes: tuple[Instruction, ...]
|
|
170
|
-
"""
|
|
170
|
+
"""Maps possible outcomes of the condition to the corresponding instructions."""
|
|
171
171
|
|
|
172
172
|
def validate(self):
|
|
173
173
|
super().validate()
|
|
@@ -186,7 +186,7 @@ class ConditionalInstruction(Instruction):
|
|
|
186
186
|
|
|
187
187
|
@dataclass(frozen=True)
|
|
188
188
|
class MultiplexedIQPulse(Instruction):
|
|
189
|
-
"""
|
|
189
|
+
"""Play the sum of multiple IQ pulses.
|
|
190
190
|
|
|
191
191
|
Each component pulse can have an arbitrary delay from the beginning of this instruction.
|
|
192
192
|
Outside the interval of the MultiplexedIQPulse, the component pulses are truncated.
|
iqm/pulse/quantum_ops.py
CHANGED
|
@@ -27,7 +27,7 @@ import numpy as np
|
|
|
27
27
|
from iqm.pulse.base_utils import merge_dicts
|
|
28
28
|
|
|
29
29
|
if TYPE_CHECKING: # pragma: no cover
|
|
30
|
-
from iqm.pulse.gate_implementation import GateImplementation, OILCalibrationData, OpCalibrationDataTree
|
|
30
|
+
from iqm.pulse.gate_implementation import GateImplementation, Locus, OILCalibrationData, OpCalibrationDataTree
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
@dataclass(frozen=True)
|
|
@@ -73,7 +73,7 @@ class QuantumOp:
|
|
|
73
73
|
|
|
74
74
|
name: str
|
|
75
75
|
"""Unique name of the operation."""
|
|
76
|
-
arity: int
|
|
76
|
+
arity: int = 1
|
|
77
77
|
"""Number of locus components the operation acts on.
|
|
78
78
|
Each locus component corresponds to a quantum subsystem in the definition of the operation.
|
|
79
79
|
The computational subspace always consists of the lowest two levels of the subsystem.
|
|
@@ -92,7 +92,7 @@ class QuantumOp:
|
|
|
92
92
|
"""True iff the operation is always factorizable to independent single-subsystem operations, which
|
|
93
93
|
is also how it is implemented, for example parallel single-qubit measurements.
|
|
94
94
|
In this case the operation calibration data is for individual subsystems as well."""
|
|
95
|
-
defaults_for_locus: dict[
|
|
95
|
+
defaults_for_locus: dict[Locus, str] = field(default_factory=dict)
|
|
96
96
|
"""Optionally define the implementation default individually per each locus. Maps the locus to the default
|
|
97
97
|
gate implementation name. If a locus is not found in this dict (by default, the dict is empty), falls back to the
|
|
98
98
|
global order defined in ``implementations``. The implementations must be first registered in ``implementations``."""
|
|
@@ -140,7 +140,6 @@ class QuantumOp:
|
|
|
140
140
|
ValueError: ``default`` is unknown or is a special implementation.
|
|
141
141
|
|
|
142
142
|
"""
|
|
143
|
-
# breakpoint()
|
|
144
143
|
if (impl := self.implementations.get(default)) is None:
|
|
145
144
|
raise ValueError(f"Operation '{self.name}' has no implementation named '{default}'.")
|
|
146
145
|
|
|
@@ -233,7 +232,7 @@ def validate_op_calibration(calibration: OpCalibrationDataTree, ops: QuantumOpTa
|
|
|
233
232
|
|
|
234
233
|
|
|
235
234
|
def validate_locus_calibration(
|
|
236
|
-
cal_data: OILCalibrationData, impl: type[GateImplementation], op: QuantumOp, impl_name: str, locus:
|
|
235
|
+
cal_data: OILCalibrationData, impl: type[GateImplementation], op: QuantumOp, impl_name: str, locus: Locus
|
|
237
236
|
) -> None:
|
|
238
237
|
"""Validates calibration for a particular gate implementation at particular locus.
|
|
239
238
|
|
|
@@ -250,17 +249,14 @@ def validate_locus_calibration(
|
|
|
250
249
|
"""
|
|
251
250
|
if not locus:
|
|
252
251
|
return # default cal data for all loci
|
|
253
|
-
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
del cal_data_copy[gate]
|
|
262
|
-
# since OILCalibrationData can have nested dicts, we do a recursive diff
|
|
263
|
-
_diff_dicts(cal_data_copy, impl.parameters, [], op.name, impl_name, locus)
|
|
252
|
+
|
|
253
|
+
# Some implementations have optional calibration parameters which we ignore here,
|
|
254
|
+
# e.g. customizable member gate cal data for CompositeGates.
|
|
255
|
+
# Since OILCalibrationData can have nested dicts, we do a recursive diff.
|
|
256
|
+
try:
|
|
257
|
+
_diff_dicts(cal_data, impl.parameters, set(impl.optional_calibration_keys()), [])
|
|
258
|
+
except ValueError as exc:
|
|
259
|
+
raise ValueError(f"{op.name}.{impl_name} at {locus}: {exc}") from exc
|
|
264
260
|
|
|
265
261
|
n_components = len(locus)
|
|
266
262
|
arity = op.arity
|
|
@@ -274,29 +270,31 @@ def validate_locus_calibration(
|
|
|
274
270
|
raise ValueError(f"{op.name}.{impl_name} at {locus}: locus must have {arity} component(s)")
|
|
275
271
|
|
|
276
272
|
|
|
277
|
-
def _diff_dicts(
|
|
278
|
-
|
|
279
|
-
|
|
273
|
+
def _diff_dicts(
|
|
274
|
+
cal_data: dict[str, Any], impl_parameters: dict[str, Any], ignored_keys: set[str], path: list[str]
|
|
275
|
+
) -> None:
|
|
276
|
+
"""Compare calibration data to the expected parameters."""
|
|
277
|
+
all_parameters = set(impl_parameters)
|
|
278
|
+
# some gate params have default values
|
|
280
279
|
defaults = {k: v.value for k, v in impl_parameters.items() if hasattr(v, "value")}
|
|
280
|
+
# FIXME what is the defaults logic below? is it correct? needs a comment
|
|
281
281
|
have = {k for k, v in cal_data.items() if v is not None or (k in defaults and defaults[k] is None)}
|
|
282
|
-
need =
|
|
282
|
+
need = all_parameters - set(defaults)
|
|
283
283
|
if need == {"*"}:
|
|
284
|
-
return
|
|
285
|
-
if diff := have -
|
|
286
|
-
raise ValueError(f"Unknown calibration data
|
|
284
|
+
return # wildcard parameters are optional at any level
|
|
285
|
+
if diff := have - all_parameters - ignored_keys:
|
|
286
|
+
raise ValueError(f"Unknown calibration data {'.'.join(path)}.{diff}")
|
|
287
287
|
if diff := need - have:
|
|
288
|
-
raise ValueError(f"Missing calibration data
|
|
288
|
+
raise ValueError(f"Missing calibration data {'.'.join(path)}.{diff}")
|
|
289
289
|
for key, data in cal_data.items():
|
|
290
|
+
if key in ignored_keys:
|
|
291
|
+
continue
|
|
290
292
|
required_value = impl_parameters[key]
|
|
291
293
|
new_path = path + [key]
|
|
292
294
|
if isinstance(required_value, dict):
|
|
293
295
|
if isinstance(data, dict):
|
|
294
|
-
_diff_dicts(data, required_value,
|
|
296
|
+
_diff_dicts(data, required_value, set(), new_path)
|
|
295
297
|
else:
|
|
296
|
-
raise ValueError(
|
|
297
|
-
f"Calibration data for {op_name}.{impl_name} at {locus}: '{'.'.join(new_path)}' should be a dict"
|
|
298
|
-
)
|
|
298
|
+
raise ValueError(f"Calibration data item '{'.'.join(new_path)}' should be a dict")
|
|
299
299
|
elif isinstance(data, dict):
|
|
300
|
-
raise ValueError(
|
|
301
|
-
f"Calibration data for {op_name}.{impl_name} at {locus}: '{'.'.join(new_path)}' should be a scalar"
|
|
302
|
-
)
|
|
300
|
+
raise ValueError(f"Calibration data item '{'.'.join(new_path)}' should be a scalar")
|
iqm/pulse/utils.py
CHANGED
|
@@ -17,14 +17,11 @@
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
from collections.abc import Iterable
|
|
21
|
-
import copy
|
|
22
20
|
from typing import get_args, get_origin
|
|
23
21
|
|
|
24
22
|
import numpy as np
|
|
25
23
|
|
|
26
24
|
from exa.common.data.parameter import CollectionType, DataType
|
|
27
|
-
from iqm.pulse.quantum_ops import QuantumOpTable
|
|
28
25
|
|
|
29
26
|
|
|
30
27
|
def map_waveform_param_types(type_hint: type) -> tuple[DataType, CollectionType]:
|
|
@@ -110,92 +107,3 @@ def phase_transformation(psi_1: float = 0.0, psi_2: float = 0.0) -> tuple[float,
|
|
|
110
107
|
|
|
111
108
|
"""
|
|
112
109
|
return psi_2, -(psi_1 + psi_2)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _validate_locus_defaults(op_name: str, definition: dict, implementations: dict) -> None:
|
|
116
|
-
"""Validate that locus defaults reference valid implementations.
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
op_name: Name of the gate we want to validate
|
|
120
|
-
definition: Dictionary containing the parameters of the new operation
|
|
121
|
-
implementations: Available implementations for the operation
|
|
122
|
-
|
|
123
|
-
Raises:
|
|
124
|
-
ValueError: If locus default references invalid implementation
|
|
125
|
-
|
|
126
|
-
"""
|
|
127
|
-
for locus, impl_name in definition["defaults_for_locus"].items():
|
|
128
|
-
if impl_name not in implementations:
|
|
129
|
-
raise ValueError(
|
|
130
|
-
f"defaults_for_locus[{locus}] implementation '{impl_name}' does not "
|
|
131
|
-
f"appear in the implementations field of {op_name}."
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def _validate_op_attributes(op_name: str, definition: dict, op_table: QuantumOpTable) -> None:
|
|
136
|
-
"""Assert that the attributes of the operation (other than default implementation info) are not changed.
|
|
137
|
-
|
|
138
|
-
Args:
|
|
139
|
-
op_name: Name of the quantum operation to be added to table of quantum operations
|
|
140
|
-
definition: Dictionary containing the parameters of the new operation
|
|
141
|
-
op_table: Table of default or existing quantum operations
|
|
142
|
-
|
|
143
|
-
Raises:
|
|
144
|
-
ValueError: If attributes don't match defaults or are invalid
|
|
145
|
-
|
|
146
|
-
"""
|
|
147
|
-
for attribute_name, value in definition.items():
|
|
148
|
-
if not hasattr(op_table[op_name], attribute_name):
|
|
149
|
-
raise ValueError(f"QuantumOp {op_name} has no attribute '{attribute_name}'.")
|
|
150
|
-
|
|
151
|
-
default_value = getattr(op_table[op_name], attribute_name)
|
|
152
|
-
if isinstance(value, Iterable):
|
|
153
|
-
default_value = tuple(default_value)
|
|
154
|
-
value = tuple(value) # noqa: PLW2901
|
|
155
|
-
if attribute_name not in ("implementations", "defaults_for_locus") and default_value != value:
|
|
156
|
-
raise ValueError(
|
|
157
|
-
"The QuantumOp definition provided by the user has a different value in the"
|
|
158
|
-
f" attribute {attribute_name} compared to the QuantumOp definition in iqm-pulse "
|
|
159
|
-
f" ({value} != {default_value}). "
|
|
160
|
-
"Changing the attributes of default QuantumOps is not allowed, other than"
|
|
161
|
-
"implementations and defaults_for_locus."
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def _process_implementations(
|
|
166
|
-
op_name: str, new_implementations: dict, exposed_implementations: dict, _default_implementations: dict
|
|
167
|
-
) -> dict:
|
|
168
|
-
"""Processes implementation definitions into implementation classes.
|
|
169
|
-
|
|
170
|
-
Args:
|
|
171
|
-
op_name: Name of the quantum operation
|
|
172
|
-
new_implementations: Dictionary mapping implementation names to class names
|
|
173
|
-
exposed_implementations: Dictionary containing the exposed implementations
|
|
174
|
-
_default_implementations: Dictionary containing the default mappings between implementations
|
|
175
|
-
and implementation classes for different gates
|
|
176
|
-
|
|
177
|
-
Returns:
|
|
178
|
-
Dictionary mapping implementation names to implementation classes
|
|
179
|
-
|
|
180
|
-
Raises:
|
|
181
|
-
ValueError: If implementation class is not exposed
|
|
182
|
-
ValueError: If implementation is overriding a default implementation
|
|
183
|
-
|
|
184
|
-
"""
|
|
185
|
-
implementations = {}
|
|
186
|
-
default_implementations = copy.deepcopy(_default_implementations).get(op_name, {})
|
|
187
|
-
for new_impl_name, new_impl_cls in new_implementations.items():
|
|
188
|
-
if (impl := exposed_implementations.get(new_impl_cls)) is None:
|
|
189
|
-
raise ValueError(f"Implementation {new_impl_cls} has not been exposed.")
|
|
190
|
-
# check if the implementation is overriding a default implementation
|
|
191
|
-
for def_impl_name, def_impl_cls in default_implementations.items():
|
|
192
|
-
if new_impl_name == def_impl_name:
|
|
193
|
-
def_impl_class_name = def_impl_cls if isinstance(def_impl_cls, str) else def_impl_cls.__name__
|
|
194
|
-
if new_impl_cls != def_impl_class_name:
|
|
195
|
-
raise ValueError(
|
|
196
|
-
f"'{def_impl_class_name}' is the default GateImplementation class for implementation '{def_impl_name}' and cannot be overridden. Consider adding a new implementation with a different name." # noqa: E501
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
implementations[new_impl_name] = impl
|
|
200
|
-
|
|
201
|
-
return implementations
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
iqm/pulse/__init__.py,sha256=FxgHlv3bF8bQCREwOJ1yu_nsrfJq0g0HdovvT1wUnCI,881
|
|
2
2
|
iqm/pulse/base_utils.py,sha256=tLbmnWGmHsjcA8QFCiqvHfgSJJNWS4AVTcD2y4M2ipU,2641
|
|
3
|
-
iqm/pulse/builder.py,sha256=
|
|
4
|
-
iqm/pulse/circuit_operations.py,sha256=
|
|
5
|
-
iqm/pulse/gate_implementation.py,sha256=
|
|
3
|
+
iqm/pulse/builder.py,sha256=YB-oiCqOka32Xyyjydomzq_tIwaE_4LsHkyLI4zGWCk,72482
|
|
4
|
+
iqm/pulse/circuit_operations.py,sha256=i-sUx-JZOKAMKWgYq1c5KIR2Ons9Uyq_meEmppAmwI8,22387
|
|
5
|
+
iqm/pulse/gate_implementation.py,sha256=yyj2d1aDL0K_qouo0qLHnZ49_1qHsJEShKW14398t8Q,39546
|
|
6
6
|
iqm/pulse/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
iqm/pulse/quantum_ops.py,sha256
|
|
7
|
+
iqm/pulse/quantum_ops.py,sha256=UTQ08Eo5vFpV_lOoQNtlwRTchES6BdGHQ5pzSdb1ToY,14584
|
|
8
8
|
iqm/pulse/scheduler.py,sha256=62gH3AcxmsgmShcmtNaiCqFLRl1Wll6Q24zIkhw_-XM,22768
|
|
9
9
|
iqm/pulse/timebox.py,sha256=Yi8I43KiSPs2s_l1r0rMVm4mw0EpSCrpq3BCUNk5vyE,16618
|
|
10
|
-
iqm/pulse/utils.py,sha256=
|
|
10
|
+
iqm/pulse/utils.py,sha256=0J4KQNvKtZahme7sP7vLa1FtENepC-eb9_A5WaghvVU,3710
|
|
11
11
|
iqm/pulse/validation.py,sha256=-tZWrW201t4nbTQWeZ8M9DixzoN8B0Q63IP57BfDAz0,10733
|
|
12
|
-
iqm/pulse/gates/__init__.py,sha256=
|
|
12
|
+
iqm/pulse/gates/__init__.py,sha256=Hd0mKWw55afvgOhDD_dgaPNCLZR6LSUoywwjBgB5zd4,8719
|
|
13
13
|
iqm/pulse/gates/barrier.py,sha256=WhYV70lf4lh4Wa9UZuMk2lp9JbUQIu8lzewRC2P7pNE,2546
|
|
14
|
-
iqm/pulse/gates/conditional.py,sha256=
|
|
15
|
-
iqm/pulse/gates/cz.py,sha256=
|
|
16
|
-
iqm/pulse/gates/default_gates.py,sha256=
|
|
14
|
+
iqm/pulse/gates/conditional.py,sha256=NE-1GYlFcfaCmRuhqGEIhtoJmBArhyY5KhVnO4y4zng,6289
|
|
15
|
+
iqm/pulse/gates/cz.py,sha256=CkTHqw6417cvZ7FDjx-e727tNpHG3PCdrpmaH7RtgOE,19855
|
|
16
|
+
iqm/pulse/gates/default_gates.py,sha256=KWcwWowkBlLkpqxcPiX6dxLpAb7DZr3FLskaba5__I4,7303
|
|
17
17
|
iqm/pulse/gates/delay.py,sha256=nST9dY2JFp_mpKhiSfsYa5yL4hFKcNJSAyCzXjhauQg,3767
|
|
18
18
|
iqm/pulse/gates/enums.py,sha256=ATwb6vZYpfgQ1gQyFPW53JyIKrdAP3FPHm6jV-t9OAk,2532
|
|
19
|
-
iqm/pulse/gates/flux_multiplexer.py,sha256=
|
|
20
|
-
iqm/pulse/gates/measure.py,sha256=
|
|
19
|
+
iqm/pulse/gates/flux_multiplexer.py,sha256=sk84ItEvkx7Z0pCHt8MCCczBe7_BHNvqS4_oeNghZw8,9757
|
|
20
|
+
iqm/pulse/gates/measure.py,sha256=5yG5dQeDljZewrPdy6vwejz5m3KU-9SlXQco9oAyssw,44488
|
|
21
21
|
iqm/pulse/gates/move.py,sha256=L8wDcc7He43sUrwQs24lNydxdM9bKN27zlEdnDucDfc,17490
|
|
22
|
-
iqm/pulse/gates/prx.py,sha256=
|
|
23
|
-
iqm/pulse/gates/reset.py,sha256=
|
|
24
|
-
iqm/pulse/gates/rz.py,sha256=
|
|
25
|
-
iqm/pulse/gates/sx.py,sha256=
|
|
26
|
-
iqm/pulse/gates/u.py,sha256=
|
|
22
|
+
iqm/pulse/gates/prx.py,sha256=Vl7t26mDRjj-6sC8jmpAgzTAtaTubVx6Ib_lpx_HmCk,24978
|
|
23
|
+
iqm/pulse/gates/reset.py,sha256=idsknRq_z2JKIq4zXc4LvdE2czE5-KgGTdLPmYVyWIg,7480
|
|
24
|
+
iqm/pulse/gates/rz.py,sha256=kr8Y5ap1MN81InCSmzGkxejvUcNszl1sxpG7yacvDM0,9684
|
|
25
|
+
iqm/pulse/gates/sx.py,sha256=3s7lo5ib2HZh-kzZd95-JqL41LxCX5GA4QZ0h7u3WCQ,1736
|
|
26
|
+
iqm/pulse/gates/u.py,sha256=EUX84XkawqLhwRaJ1MxSKB33VpC8XDsFu875uHNo0WI,5403
|
|
27
27
|
iqm/pulse/playlist/__init__.py,sha256=XW-7Po_vCzhkk1Js06K8P-5srPpBuPHkqpc1yhL_Zx4,997
|
|
28
28
|
iqm/pulse/playlist/channel.py,sha256=RAkGyjTU66MiF-ccf06FFZAXpUykWEltbfNXw4xJpYc,17164
|
|
29
29
|
iqm/pulse/playlist/fast_drag.py,sha256=80Knv0Q9IaGiVdc0TrHlxNi1zJtTUZvWIVO38reYcA0,19600
|
|
30
30
|
iqm/pulse/playlist/hd_drag.py,sha256=JYRy3aPu0LD9y0H3UncOiWI39eV9-TARvgLonNUsaO0,13562
|
|
31
|
-
iqm/pulse/playlist/instructions.py,sha256=
|
|
31
|
+
iqm/pulse/playlist/instructions.py,sha256=PyIjJJN58rbfIFqgRaynsnsHXywy6xBpkAtQuifgoJY,9930
|
|
32
32
|
iqm/pulse/playlist/playlist.py,sha256=7MIX4iRfh2Nit8AnpNyqv5A5ZmK7JR-v201DXVL6kUk,685
|
|
33
33
|
iqm/pulse/playlist/schedule.py,sha256=VYrVyPlB04Rm8iOYBXu2uOpsLBqczJRx3L2VU53CxVU,17989
|
|
34
34
|
iqm/pulse/playlist/waveforms.py,sha256=Xa7P3V0rRIn7sQIoLKqXaWwtAbyQWjdVGW6bKOGNcOQ,17898
|
|
@@ -39,8 +39,8 @@ iqm/pulse/playlist/visualisation/templates/static/logo.png,sha256=rbfQZ6_UEaztpY
|
|
|
39
39
|
iqm/pulse/playlist/visualisation/templates/static/moment.min.js,sha256=4iQZ6BVL4qNKlQ27TExEhBN1HFPvAvAMbFavKKosSWQ,53324
|
|
40
40
|
iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.css,sha256=svzNasPg1yR5gvEaRei2jg-n4Pc3sVyMUWeS6xRAh6U,19837
|
|
41
41
|
iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.js,sha256=OqCqCyA6JnxPz3PGXq_P_9VuSqWptgNt5Ev3T-xjefQ,570288
|
|
42
|
-
iqm_pulse-
|
|
43
|
-
iqm_pulse-
|
|
44
|
-
iqm_pulse-
|
|
45
|
-
iqm_pulse-
|
|
46
|
-
iqm_pulse-
|
|
42
|
+
iqm_pulse-10.0.0.dist-info/LICENSE.txt,sha256=R6Q7eUrLyoCQgWYorQ8WJmVmWKYU3dxA3jYUp0wwQAw,11332
|
|
43
|
+
iqm_pulse-10.0.0.dist-info/METADATA,sha256=5-Re6d58u6Ek98qU8ea3CyYKoH5kh2eGw7lkTvGZQXI,14551
|
|
44
|
+
iqm_pulse-10.0.0.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
|
|
45
|
+
iqm_pulse-10.0.0.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
|
|
46
|
+
iqm_pulse-10.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|