iqm-pulse 9.21.0__py3-none-any.whl → 10.1.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 +188 -89
- 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 +8 -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/playlist/waveforms.py +29 -1
- iqm/pulse/quantum_ops.py +29 -31
- iqm/pulse/utils.py +0 -92
- {iqm_pulse-9.21.0.dist-info → iqm_pulse-10.1.0.dist-info}/METADATA +1 -1
- {iqm_pulse-9.21.0.dist-info → iqm_pulse-10.1.0.dist-info}/RECORD +23 -23
- {iqm_pulse-9.21.0.dist-info → iqm_pulse-10.1.0.dist-info}/LICENSE.txt +0 -0
- {iqm_pulse-9.21.0.dist-info → iqm_pulse-10.1.0.dist-info}/WHEEL +0 -0
- {iqm_pulse-9.21.0.dist-info → iqm_pulse-10.1.0.dist-info}/top_level.txt +0 -0
iqm/pulse/gate_implementation.py
CHANGED
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
27
|
import abc
|
|
28
|
-
from collections.abc import Iterable
|
|
28
|
+
from collections.abc import Iterable, Mapping
|
|
29
29
|
import dataclasses
|
|
30
30
|
import re
|
|
31
|
-
from typing import TYPE_CHECKING, Any, TypeAlias, get_type_hints
|
|
31
|
+
from typing import TYPE_CHECKING, Any, TypeAlias, final, get_type_hints
|
|
32
32
|
from uuid import uuid4
|
|
33
33
|
|
|
34
34
|
import numpy as np
|
|
@@ -36,7 +36,7 @@ import numpy as np
|
|
|
36
36
|
from exa.common.data.parameter import Parameter, Setting
|
|
37
37
|
from exa.common.data.setting_node import SettingNode
|
|
38
38
|
from exa.common.qcm_data.chip_topology import ChipTopology
|
|
39
|
-
from iqm.pulse.playlist.instructions import
|
|
39
|
+
from iqm.pulse.playlist.instructions import IQPulse
|
|
40
40
|
from iqm.pulse.playlist.schedule import Schedule
|
|
41
41
|
from iqm.pulse.timebox import TimeBox
|
|
42
42
|
from iqm.pulse.utils import map_waveform_param_types
|
|
@@ -81,23 +81,31 @@ PROBE_LINES_LOCUS_MAPPING = "probe_lines"
|
|
|
81
81
|
class GateImplementation(abc.ABC):
|
|
82
82
|
"""ABC for implementing quantum gates and other quantum operations using instruction schedules.
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
Every implementation of every operation type can have its own GateImplementation subclass.
|
|
85
85
|
Each GateImplementation instance represents a particular locus for that implementation, and encapsulates
|
|
86
86
|
the calibration data it requires.
|
|
87
87
|
|
|
88
|
-
All GateImplementation subclasses
|
|
88
|
+
All GateImplementation subclasses :meth:`__init__` must have exactly the below arguments in order to be
|
|
89
89
|
usable via :meth:`.ScheduleBuilder.get_implementation`.
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
(e.g. rotation angles) as input, and returns a :class:`.TimeBox` instance which
|
|
93
|
-
an instance of the operation at that locus.
|
|
91
|
+
GateImplementation also has the :meth:`__call__` method, which takes the operation parameters
|
|
92
|
+
(e.g. rotation angles) as input, and builds, caches and then returns a :class:`.TimeBox` instance which
|
|
93
|
+
implements an instance of the operation at that locus. :meth:`__call__` normally should not be
|
|
94
|
+
reimplemented by the subclasses, instead they should define :meth:`_call` which contains the actual
|
|
95
|
+
implementation without the caching logic.
|
|
96
|
+
|
|
97
|
+
Even though it is possible, GateImplementations *should not* use other gates to implement themselves,
|
|
98
|
+
unless they are subclasses of :class:`CompositeGate`. This is to encapsulate the calibration data better,
|
|
99
|
+
and avoid unpredictable dependencies between gates.
|
|
94
100
|
|
|
95
101
|
Args:
|
|
96
102
|
parent: Quantum operation this instance implements.
|
|
97
103
|
name: Name of the implementation provided by this instance.
|
|
98
104
|
locus: Locus the operation acts on.
|
|
99
|
-
calibration_data: (Raw) calibration data for the (operation, implementation, locus) represented
|
|
100
|
-
|
|
105
|
+
calibration_data: (Raw) calibration data for the (operation, implementation, locus) represented
|
|
106
|
+
by this instance.
|
|
107
|
+
builder: Schedule builder instance that can be used to access system properties (and in the
|
|
108
|
+
case of :class:`CompositeGate`, other gates).
|
|
101
109
|
|
|
102
110
|
"""
|
|
103
111
|
|
|
@@ -120,8 +128,13 @@ class GateImplementation(abc.ABC):
|
|
|
120
128
|
self.locus = locus
|
|
121
129
|
self.calibration_data = calibration_data
|
|
122
130
|
self.builder = builder
|
|
123
|
-
self.id = str(uuid4())
|
|
131
|
+
self.id = str(uuid4())
|
|
132
|
+
"""Unique str identifier, needed for certain caching properties."""
|
|
133
|
+
self.sub_implementations: dict[str, GateImplementation] = {}
|
|
134
|
+
"""Single-component sub-implementations for factorizable gates with len(locus) > 1, otherwise empty.
|
|
135
|
+
At least one of self.sub_implementations and self.calibration_data is always empty."""
|
|
124
136
|
self._timebox_cache: dict[tuple[Any, ...], TimeBox | list[TimeBox]] = {}
|
|
137
|
+
"""Cache for :meth:`__call__` results."""
|
|
125
138
|
|
|
126
139
|
@property
|
|
127
140
|
def qualified_name(self) -> str:
|
|
@@ -130,13 +143,38 @@ class GateImplementation(abc.ABC):
|
|
|
130
143
|
|
|
131
144
|
@classmethod
|
|
132
145
|
def needs_calibration(cls) -> bool:
|
|
133
|
-
"""
|
|
146
|
+
"""True iff the implementation needs calibration data.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
True iff :attr:`OpCalibrationDataTree` must contain a node ``f"{self.parent.name}.{self.name}.{self.locus}``
|
|
150
|
+
for the calibration data this instance needs.
|
|
134
151
|
|
|
135
|
-
Returns True if the calibration dict must contain a node with keyed with
|
|
136
|
-
<operation name>: <implementation name>: <appropriate locus> in order to use this implementation.
|
|
137
152
|
"""
|
|
138
153
|
return bool(cls.parameters)
|
|
139
154
|
|
|
155
|
+
@classmethod
|
|
156
|
+
def optional_calibration_keys(cls) -> tuple[str, ...]:
|
|
157
|
+
"""Optional calibration data keys for this class, in addition to the required items in :attr:`parameters`.
|
|
158
|
+
|
|
159
|
+
These keys are not required to be present in :attr:`OILCalibrationData` when validating it.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Optional top-level calibration data keys.
|
|
163
|
+
|
|
164
|
+
"""
|
|
165
|
+
return ()
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def may_have_calibration(cls) -> bool:
|
|
169
|
+
"""True iff the implementation may have calibration data.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
True iff :attr:`OpCalibrationDataTree` may contain a node ``"{self.parent.name}.{self.name}.{self.locus}``
|
|
173
|
+
for the calibration data of this instance.
|
|
174
|
+
|
|
175
|
+
"""
|
|
176
|
+
return cls.needs_calibration() or bool(cls.optional_calibration_keys())
|
|
177
|
+
|
|
140
178
|
def __call__(self, *args, **kwargs) -> TimeBox | list[TimeBox]:
|
|
141
179
|
"""TimeBox that implements a quantum operation.
|
|
142
180
|
|
|
@@ -171,26 +209,32 @@ class GateImplementation(abc.ABC):
|
|
|
171
209
|
"""
|
|
172
210
|
return NotImplementedError # type: ignore[return-value]
|
|
173
211
|
|
|
174
|
-
|
|
175
|
-
|
|
212
|
+
@final
|
|
213
|
+
@classmethod
|
|
214
|
+
def construct_factorizable(
|
|
215
|
+
cls,
|
|
216
|
+
parent: QuantumOp,
|
|
217
|
+
name: str,
|
|
218
|
+
locus: Locus,
|
|
219
|
+
builder: ScheduleBuilder,
|
|
220
|
+
sub_implementations: dict[str, GateImplementation],
|
|
176
221
|
) -> GateImplementation:
|
|
177
|
-
"""
|
|
178
|
-
|
|
179
|
-
Inheriting classes may override this in order to add additional logic.
|
|
180
|
-
|
|
181
|
-
Args:
|
|
182
|
-
op_name: operation name
|
|
183
|
-
locus: locus the operation acts on
|
|
184
|
-
impl_name: implementation name. Uses the assigned default implementation if not specified.
|
|
185
|
-
strict_locus: iff False, for non-symmetric implementations of symmetric ops the locus order may
|
|
186
|
-
be changed if no calibration data is available for the requested locus order
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
Calibrated gate implementation.
|
|
222
|
+
"""Construct an implementation for a factorizable operation.
|
|
190
223
|
|
|
224
|
+
Instead of calibration data this method is given ``sub_implementations``, which contains single-qubit
|
|
225
|
+
implementations for all the components in ``locus``.
|
|
191
226
|
"""
|
|
192
|
-
|
|
227
|
+
impl = cls(
|
|
228
|
+
parent=parent,
|
|
229
|
+
name=name,
|
|
230
|
+
locus=locus,
|
|
231
|
+
calibration_data={},
|
|
232
|
+
builder=builder,
|
|
233
|
+
)
|
|
234
|
+
impl.sub_implementations = sub_implementations
|
|
235
|
+
return impl
|
|
193
236
|
|
|
237
|
+
@final
|
|
194
238
|
def to_timebox(self, schedule: Schedule) -> TimeBox:
|
|
195
239
|
"""Wraps the given instruction schedule into an atomic/resolved timebox."""
|
|
196
240
|
return TimeBox.atomic(
|
|
@@ -207,22 +251,30 @@ class GateImplementation(abc.ABC):
|
|
|
207
251
|
"""
|
|
208
252
|
raise NotImplementedError
|
|
209
253
|
|
|
254
|
+
@final
|
|
210
255
|
@classmethod
|
|
211
256
|
def convert_calibration_data(
|
|
212
257
|
cls,
|
|
213
258
|
calibration_data: OILCalibrationData,
|
|
214
259
|
params: NestedParams,
|
|
215
260
|
channel_props: ChannelProperties,
|
|
261
|
+
*,
|
|
216
262
|
duration: float | None = None,
|
|
263
|
+
_top_level: bool = True,
|
|
217
264
|
) -> OILCalibrationData:
|
|
218
|
-
"""Convert time-like items in the calibration data to fractions of the time duration of the gate.
|
|
265
|
+
"""Convert time- and frequency-like items in the calibration data to fractions of the time duration of the gate.
|
|
219
266
|
|
|
220
267
|
This is a convenience method for converting calibration data items involving time
|
|
221
|
-
durations measured in seconds into fractions of the duration
|
|
268
|
+
durations measured in seconds and frequencies measured in Hz into fractions of the duration
|
|
269
|
+
of the gate, e.g. to be used to parameterize :class:`.Waveform` classes.
|
|
222
270
|
|
|
223
271
|
* Values of items that are not measured in seconds or Hz are returned as is.
|
|
224
|
-
*
|
|
225
|
-
|
|
272
|
+
* Items named ``duration`` get special treatment for convenience.
|
|
273
|
+
They are not included in the converted data.
|
|
274
|
+
If the ``duration`` parameter is None, there must be a ``"duration"`` item at the top level in
|
|
275
|
+
``calibration_data`` whose value will be used instead.
|
|
276
|
+
* The ``duration`` parameter is converted to channel samples and included in the converted
|
|
277
|
+
data under the top-level key ``"n_samples"``.
|
|
226
278
|
|
|
227
279
|
Args:
|
|
228
280
|
calibration_data: (subset of) calibration data for the gate/implementation/locus
|
|
@@ -243,7 +295,7 @@ class GateImplementation(abc.ABC):
|
|
|
243
295
|
"""
|
|
244
296
|
if value is None:
|
|
245
297
|
return None
|
|
246
|
-
if
|
|
298
|
+
if unit not in {"s", "Hz"}:
|
|
247
299
|
return value
|
|
248
300
|
if unit == "s":
|
|
249
301
|
|
|
@@ -261,27 +313,30 @@ class GateImplementation(abc.ABC):
|
|
|
261
313
|
return conversion(value)
|
|
262
314
|
|
|
263
315
|
if duration is None:
|
|
264
|
-
# duration should be found in the outermost dict
|
|
316
|
+
# if not given, duration should be found in the outermost dict
|
|
265
317
|
duration = calibration_data["duration"]
|
|
266
318
|
if duration is None:
|
|
267
319
|
raise ValueError(f"Duration for {cls.__name__} has not been set.")
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
320
|
+
|
|
321
|
+
# n_samples will only be included on the top level
|
|
322
|
+
converted = (
|
|
323
|
+
{"n_samples": channel_props.duration_to_int_samples(duration) if duration > 0 else 0} if _top_level else {}
|
|
324
|
+
)
|
|
271
325
|
|
|
272
326
|
for p_name, p in params.items():
|
|
273
|
-
if p_name in calibration_data:
|
|
327
|
+
if p_name in calibration_data and p_name != "duration":
|
|
328
|
+
# duration is not included in the converted data
|
|
274
329
|
data = calibration_data[p_name]
|
|
275
330
|
if isinstance(p, Setting | Parameter):
|
|
276
331
|
value = convert(p_name, p.unit, data, duration)
|
|
277
332
|
else:
|
|
278
333
|
# recursion for nested parameter dicts
|
|
279
|
-
value = cls.convert_calibration_data(data, p, channel_props, duration=duration)
|
|
280
|
-
|
|
281
|
-
converted[p_name] = value
|
|
334
|
+
value = cls.convert_calibration_data(data, p, channel_props, duration=duration, _top_level=False)
|
|
335
|
+
converted[p_name] = value
|
|
282
336
|
|
|
283
337
|
return converted
|
|
284
338
|
|
|
339
|
+
@final
|
|
285
340
|
@classmethod
|
|
286
341
|
def get_parameters(cls, locus: Iterable[str], path: Iterable[str] = ()) -> SettingNode:
|
|
287
342
|
"""Calibration data tree the GateImplementation subclass expects for each locus.
|
|
@@ -342,7 +397,7 @@ class GateImplementation(abc.ABC):
|
|
|
342
397
|
|
|
343
398
|
@classmethod
|
|
344
399
|
def get_custom_locus_mapping(
|
|
345
|
-
cls, chip_topology: ChipTopology, component_to_channels:
|
|
400
|
+
cls, chip_topology: ChipTopology, component_to_channels: Mapping[str, Iterable[str]]
|
|
346
401
|
) -> dict[tuple[str, ...] | frozenset[str], tuple[str, ...]] | None:
|
|
347
402
|
"""Get custom locus mapping for this GateImplementation.
|
|
348
403
|
|
|
@@ -393,6 +448,7 @@ class CustomIQWaveforms(GateImplementation):
|
|
|
393
448
|
self, parent: QuantumOp, name: str, locus: Locus, calibration_data: OILCalibrationData, builder: ScheduleBuilder
|
|
394
449
|
) -> None:
|
|
395
450
|
super().__init__(parent, name, locus, calibration_data, builder)
|
|
451
|
+
# TODO why does not this check happen in __init_subclass__?
|
|
396
452
|
if getattr(self, "wave_i", None) is None or getattr(self, "wave_q", None) is None:
|
|
397
453
|
raise ValueError(
|
|
398
454
|
"You must provide valid Waveforms for both of the arguments `wave_i` and `wave_q` when inheriting"
|
|
@@ -532,7 +588,8 @@ class SinglePulseGate(GateImplementation):
|
|
|
532
588
|
"""
|
|
533
589
|
return self.builder.get_drive_channel(self.locus[0])
|
|
534
590
|
|
|
535
|
-
|
|
591
|
+
@classmethod
|
|
592
|
+
def _get_pulse(cls, **kwargs) -> IQPulse:
|
|
536
593
|
"""Return pulse based on the provided calibration data."""
|
|
537
594
|
raise NotImplementedError
|
|
538
595
|
|
|
@@ -543,32 +600,70 @@ class SinglePulseGate(GateImplementation):
|
|
|
543
600
|
|
|
544
601
|
|
|
545
602
|
class CompositeGate(GateImplementation):
|
|
546
|
-
"""
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
:
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
603
|
+
"""Base class for gate implementations that are defined in terms of other gate implementations.
|
|
604
|
+
|
|
605
|
+
Composite gates can be implemented using other pre-existing gate implementations (called its
|
|
606
|
+
*member gates*) by using :meth:`build` in the :meth:`_call` method. You *should not* call
|
|
607
|
+
:meth:`ScheduleBuilder.get_implementation` directly in composite gate code.
|
|
608
|
+
|
|
609
|
+
A CompositeGate subclass needs to declare what its member gates are, e.g. to be able to
|
|
610
|
+
verify that they are calibrated, using the :attr:`registered_gates` class attribute.
|
|
611
|
+
|
|
612
|
+
It is possibe to calibrate (some of) the member gates separately from the common calibration,
|
|
613
|
+
by listing their names in :attr:`customizable_gates` class attribute.
|
|
614
|
+
However, if no custom calibration data is provided, the composite gate will use
|
|
615
|
+
the common calibration for the member operations.
|
|
616
|
+
|
|
617
|
+
.. example::
|
|
618
|
+
|
|
619
|
+
Inheriting this class and defining ``registered_gates = ("prx", "cz")``, ``customizable_gates = ("prx",)``
|
|
620
|
+
allows one to use ``prx`` and ``cz`` gates as member operations, and calibrate ``prx`` independently of
|
|
621
|
+
the common calibration.
|
|
622
|
+
|
|
623
|
+
.. note::
|
|
624
|
+
|
|
625
|
+
:meth:`CompositeGate.needs_calibration` only tells whether the implementation class itself needs
|
|
626
|
+
calibration data, not whether the member gates need some.
|
|
627
|
+
|
|
628
|
+
"""
|
|
629
|
+
|
|
630
|
+
registered_gates: tuple[str, ...] = ()
|
|
631
|
+
"""Names of the member operations used by the composite gate.
|
|
632
|
+
There must be corresponding keys in :attr:`builder.op_table`.
|
|
633
|
+
"""
|
|
634
|
+
|
|
635
|
+
customizable_gates: tuple[str, ...] | None = None
|
|
636
|
+
"""These member operations can be calibrated separately from their common
|
|
637
|
+
calibration by adding :attr:`OCalibrationData` nodes for them under the
|
|
638
|
+
:attr:`OILCalibrationData` node of the composite gate.
|
|
639
|
+
Must be a subset of :attr:`registered_gates`.
|
|
640
|
+
By default all member operations are customizable.
|
|
559
641
|
"""
|
|
560
642
|
|
|
561
|
-
registered_gates: list[str] = []
|
|
562
|
-
"""Gates that can be calibrated separately from their common calibration existing in ``self.builder``. The gate
|
|
563
|
-
names should correspond to the keys in ``self.builder.op_table``. Other gates besides the ones given here can
|
|
564
|
-
also be constructed via ``self.builder``, but these will always use the common calibration."""
|
|
565
643
|
default_implementations: dict[str, str] = {}
|
|
566
|
-
"""Mapping from operation names to the designated default implementation of that
|
|
567
|
-
allows one to define a different default implementation from
|
|
568
|
-
|
|
569
|
-
|
|
644
|
+
"""Mapping from member operation names to the designated default implementation of that
|
|
645
|
+
operation. Filling this attribute allows one to define a different default implementation from
|
|
646
|
+
the common default in :attr:`builder.op_table` to be used in the context of this composite
|
|
647
|
+
gate. If a member operation is not found in this dict as a key, the CompositeGate will use the
|
|
648
|
+
common default as its default implementation.
|
|
570
649
|
"""
|
|
571
650
|
|
|
651
|
+
def __init_subclass__(cls):
|
|
652
|
+
if not cls.registered_gates:
|
|
653
|
+
# this would be pointless
|
|
654
|
+
raise ValueError(f"CompositeGate {cls.__name__} has no registered gates.")
|
|
655
|
+
# TODO we should also check that customizable_gates may_have_calibration (otherwise it's pointless
|
|
656
|
+
# to call them customizable), but we don't currently have access to their implementation classes here...
|
|
657
|
+
if cls.customizable_gates is None:
|
|
658
|
+
cls.customizable_gates = cls.registered_gates
|
|
659
|
+
elif not set(cls.customizable_gates) <= set(cls.registered_gates):
|
|
660
|
+
raise ValueError(f"CompositeGate {cls.__name__}: customizable_gates must be a subset of registered_gates.")
|
|
661
|
+
|
|
662
|
+
@classmethod
|
|
663
|
+
def optional_calibration_keys(cls) -> tuple[str, ...]:
|
|
664
|
+
# Custom calibration data for member gates is optional. Affects may_have_calibration.
|
|
665
|
+
return cls.customizable_gates or ()
|
|
666
|
+
|
|
572
667
|
def __init__(
|
|
573
668
|
self,
|
|
574
669
|
parent: QuantumOp,
|
|
@@ -577,14 +672,18 @@ class CompositeGate(GateImplementation):
|
|
|
577
672
|
calibration_data: OILCalibrationData,
|
|
578
673
|
builder: ScheduleBuilder,
|
|
579
674
|
) -> None:
|
|
675
|
+
# validate the registered gates
|
|
676
|
+
for op_name in self.registered_gates:
|
|
677
|
+
if (op := builder.op_table.get(op_name)) is None:
|
|
678
|
+
raise ValueError(f"Unknown registered gate '{op_name}'.")
|
|
679
|
+
if parent.factorizable:
|
|
680
|
+
if not (op.factorizable or op.arity == 1):
|
|
681
|
+
raise ValueError(
|
|
682
|
+
f"'{parent.name}' is factorizable, but registered gate '{op_name}'"
|
|
683
|
+
" is neither factorizable nor arity-1."
|
|
684
|
+
)
|
|
685
|
+
|
|
580
686
|
super().__init__(parent, name, locus, calibration_data, builder)
|
|
581
|
-
custom_defaults = {
|
|
582
|
-
op: data.get("default_implementation") for op, data in calibration_data.items() if isinstance(data, dict)
|
|
583
|
-
}
|
|
584
|
-
self._default_implementations = self.default_implementations.copy()
|
|
585
|
-
for op_name, impl in custom_defaults.items():
|
|
586
|
-
if op_name in self.registered_gates and impl is not None:
|
|
587
|
-
self._default_implementations[op_name] = impl
|
|
588
687
|
|
|
589
688
|
def __call__(self, *args, **kwargs):
|
|
590
689
|
default_cache_key = tuple(args) + tuple(kwargs.items())
|
|
@@ -601,46 +700,85 @@ class CompositeGate(GateImplementation):
|
|
|
601
700
|
return box
|
|
602
701
|
|
|
603
702
|
def build(
|
|
604
|
-
self,
|
|
703
|
+
self,
|
|
704
|
+
op_name: str,
|
|
705
|
+
locus: Locus,
|
|
706
|
+
impl_name: str | None = None,
|
|
707
|
+
*,
|
|
708
|
+
strict_locus: bool = False,
|
|
709
|
+
priority_calibration: OILCalibrationData | None = None,
|
|
605
710
|
) -> GateImplementation:
|
|
606
|
-
"""Construct a member gate
|
|
711
|
+
"""Construct an implementation for a member (registered) gate.
|
|
607
712
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
(only non-empty values will be merged). If there are no values found, the
|
|
612
|
-
common calibration will be used.
|
|
713
|
+
A custom calibration for ``op_name`` will be sought in :attr:`calibration_data`.
|
|
714
|
+
If any calibration parameters are found, they override the corresponding parameters
|
|
715
|
+
in the common calibration data.
|
|
613
716
|
|
|
614
717
|
Args:
|
|
615
|
-
op_name: operation name
|
|
718
|
+
op_name: member operation name
|
|
616
719
|
locus: locus the operation acts on
|
|
617
720
|
impl_name: Implementation name. If not given, uses the default implementation defined in the class instance
|
|
618
|
-
if any, and otherwise the common default in
|
|
721
|
+
if any, and otherwise the common default in :attr:`builder.op_table`.
|
|
619
722
|
strict_locus: iff False, for non-symmetric implementations of symmetric ops the locus order may
|
|
620
723
|
be changed if no calibration data is available for the requested locus order
|
|
724
|
+
priority_calibration: If given, overrides the custom calibration for the member gate. Deprecated,
|
|
725
|
+
should not be used.
|
|
621
726
|
|
|
622
727
|
Returns:
|
|
623
|
-
Calibrated gate implementation.
|
|
728
|
+
Calibrated member gate implementation.
|
|
624
729
|
|
|
625
730
|
"""
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if op_name in self.registered_gates:
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
731
|
+
# FIXME remove priority_calibration, it's a HACK to make a single current use case work
|
|
732
|
+
# (MOVE_NCZ_MOVE_Composite in exa-core).
|
|
733
|
+
if op_name not in self.registered_gates:
|
|
734
|
+
raise ValueError(f"'{op_name}' not found in registered_gates.")
|
|
735
|
+
|
|
736
|
+
op = self.builder.op_table[op_name]
|
|
737
|
+
|
|
738
|
+
# implementation to use: given or class default
|
|
739
|
+
impl_name = impl_name or self.default_implementations.get(op_name, None)
|
|
740
|
+
# or, finally, the global default
|
|
741
|
+
impl_name, locus = self.builder._find_implementation_and_locus(
|
|
742
|
+
op,
|
|
743
|
+
impl_name=impl_name,
|
|
744
|
+
locus=locus,
|
|
745
|
+
strict_locus=strict_locus,
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
def get_custom_oi(cal_impl: GateImplementation) -> OICalibrationData:
|
|
749
|
+
"""Return the custom calibration data node for the requested member op/implementation in
|
|
750
|
+
the calibration data tree of the given implementation, or an empty dict if it does not exist.
|
|
751
|
+
"""
|
|
752
|
+
return cal_impl.calibration_data.get(op_name, {}).get(impl_name, {})
|
|
753
|
+
|
|
754
|
+
if priority_calibration is None:
|
|
755
|
+
# Find the custom cal data for the member op (if allowed and present).
|
|
756
|
+
if op_name in (self.customizable_gates or ()):
|
|
757
|
+
impl_class = self.builder.get_implementation_class(op_name, impl_name)
|
|
758
|
+
if impl_class.may_have_calibration():
|
|
759
|
+
if self.sub_implementations:
|
|
760
|
+
# self is a len(locus) > 1 factorizable CompositeGate (e.g. reset).
|
|
761
|
+
# It has 1-qubit subimplementations that have their own cal data.
|
|
762
|
+
# It may only have factorizable or arity-1 member ops.
|
|
763
|
+
if op.factorizable:
|
|
764
|
+
# Combine the custom cal datas scattered in the sub_implementations.
|
|
765
|
+
# priority calibration for factorizable ops is OICalibrationData
|
|
766
|
+
priority_calibration = {}
|
|
767
|
+
for c in locus:
|
|
768
|
+
priority_calibration |= get_custom_oi(self.sub_implementations[c]) # type: ignore[arg-type]
|
|
769
|
+
else:
|
|
770
|
+
priority_calibration = get_custom_oi(self.sub_implementations[locus[0]]).get(locus)
|
|
771
|
+
else:
|
|
772
|
+
# self has normal cal data
|
|
773
|
+
oi = get_custom_oi(self)
|
|
774
|
+
priority_calibration = oi if op.factorizable else oi.get(locus) # type: ignore
|
|
775
|
+
|
|
638
776
|
return self.builder.get_implementation(
|
|
639
777
|
op_name,
|
|
640
778
|
locus,
|
|
641
779
|
impl_name=impl_name,
|
|
642
780
|
strict_locus=strict_locus,
|
|
643
|
-
priority_calibration=
|
|
781
|
+
priority_calibration=priority_calibration,
|
|
644
782
|
use_priority_order=True,
|
|
645
783
|
)
|
|
646
784
|
|
|
@@ -652,17 +790,21 @@ class CompositeCache:
|
|
|
652
790
|
cache ``GateImplementation._timebox_cache`` as composites can include any gates in their calls, and we cannot trust
|
|
653
791
|
that the cache is flushed correctly just based on if the composite itself has its own calibration data changed
|
|
654
792
|
(we would have to flush also when any of the composite's members get new calibration, and this cannot consistently
|
|
655
|
-
be deduced). For this reason, CompositeCache
|
|
656
|
-
data.
|
|
793
|
+
be deduced). For this reason, the CompositeCache is located in the ScheduleBuilder class, and will be flushed
|
|
794
|
+
whenever *any* gate implementation gets new calibration data.
|
|
657
795
|
"""
|
|
658
796
|
|
|
659
797
|
def __init__(self) -> None:
|
|
660
798
|
self._cache: dict[tuple[Any, ...], TimeBox] = {}
|
|
661
799
|
|
|
662
800
|
def set(
|
|
663
|
-
self,
|
|
801
|
+
self,
|
|
802
|
+
gate_implementation: GateImplementation,
|
|
803
|
+
cache_key: tuple[Any, ...],
|
|
804
|
+
timebox: TimeBox,
|
|
805
|
+
extra_id: str = "",
|
|
664
806
|
) -> None:
|
|
665
|
-
"""
|
|
807
|
+
"""Store a TimeBox in the cache.
|
|
666
808
|
|
|
667
809
|
Args:
|
|
668
810
|
gate_implementation: gate implementation that created the TimeBox.
|