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/builder.py
CHANGED
|
@@ -40,11 +40,12 @@ from iqm.pulse.gate_implementation import (
|
|
|
40
40
|
CompositeCache,
|
|
41
41
|
GateImplementation,
|
|
42
42
|
Locus,
|
|
43
|
+
OICalibrationData,
|
|
43
44
|
OILCalibrationData,
|
|
44
45
|
OpCalibrationDataTree,
|
|
45
46
|
)
|
|
46
|
-
from iqm.pulse.gates import
|
|
47
|
-
from iqm.pulse.gates.default_gates import
|
|
47
|
+
from iqm.pulse.gates import _validate_implementation, get_implementation_class
|
|
48
|
+
from iqm.pulse.gates.default_gates import _quantum_ops_library
|
|
48
49
|
from iqm.pulse.playlist.channel import ChannelProperties, ProbeChannelProperties
|
|
49
50
|
from iqm.pulse.playlist.instructions import (
|
|
50
51
|
AcquisitionMethod,
|
|
@@ -72,11 +73,6 @@ from iqm.pulse.scheduler import (
|
|
|
72
73
|
extend_schedule_new,
|
|
73
74
|
)
|
|
74
75
|
from iqm.pulse.timebox import SchedulingAlgorithm, SchedulingStrategy, TimeBox
|
|
75
|
-
from iqm.pulse.utils import (
|
|
76
|
-
_process_implementations,
|
|
77
|
-
_validate_locus_defaults,
|
|
78
|
-
_validate_op_attributes,
|
|
79
|
-
)
|
|
80
76
|
|
|
81
77
|
logger = logging.getLogger(__name__)
|
|
82
78
|
|
|
@@ -120,6 +116,9 @@ class CircuitOperation:
|
|
|
120
116
|
f"but {len(self.locus)} were given: {self.locus}"
|
|
121
117
|
)
|
|
122
118
|
|
|
119
|
+
if len(self.locus) != len(set(self.locus)):
|
|
120
|
+
raise ValueError(f"Repeated locus components: {self.locus}.")
|
|
121
|
+
|
|
123
122
|
if not set(op_type.params).issubset(self.args):
|
|
124
123
|
raise ValueError(
|
|
125
124
|
f"The '{self.name}' operation requires the arguments {op_type.params}, "
|
|
@@ -169,9 +168,9 @@ def validate_quantum_circuit(
|
|
|
169
168
|
for op in operations:
|
|
170
169
|
op.validate(op_table)
|
|
171
170
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if key in measurement_keys:
|
|
171
|
+
# extra validation for specific operations
|
|
172
|
+
if op.name == "measure":
|
|
173
|
+
if (key := op.args["key"]) in measurement_keys:
|
|
175
174
|
raise ValueError(f"Measurement key '{key}' is not unique.")
|
|
176
175
|
|
|
177
176
|
measurement_keys.add(key)
|
|
@@ -182,36 +181,81 @@ def validate_quantum_circuit(
|
|
|
182
181
|
raise ValueError("Circuit contains no measurements.")
|
|
183
182
|
|
|
184
183
|
|
|
185
|
-
def build_quantum_ops(ops: dict[str, Any]) -> QuantumOpTable:
|
|
184
|
+
def build_quantum_ops(ops: dict[str, dict[str, Any]]) -> QuantumOpTable:
|
|
186
185
|
"""Builds the table of known quantum operations.
|
|
187
186
|
|
|
188
|
-
Hardcoded default native ops table is extended by the ones in
|
|
189
|
-
In case of name collisions, the content of
|
|
187
|
+
Hardcoded default native ops table is extended by the ones in ``ops``.
|
|
188
|
+
In case of name collisions, the content of ``ops`` takes priority over the defaults.
|
|
190
189
|
|
|
191
190
|
Args:
|
|
192
191
|
ops: Contents of the ``gate_definitions`` section defining
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
the quantum operations in the configuration YAML file.
|
|
193
|
+
Implementation names must be mapped to either exposed GateImplementation
|
|
194
|
+
class names, or actual GateImplementation classes.
|
|
195
|
+
NOTE: Modified by the function.
|
|
195
196
|
|
|
196
197
|
Returns:
|
|
197
|
-
Mapping from quantum operation
|
|
198
|
+
Mapping from quantum operation names to their definitions.
|
|
199
|
+
|
|
200
|
+
Raises:
|
|
201
|
+
ValueError: Requested implementation class is not exposed.
|
|
202
|
+
ValueError: A canonical implementation name is being redefined.
|
|
203
|
+
ValueError: Locus default references an undefined implementation.
|
|
204
|
+
ValueError: Operation attributes don't match defaults or are invalid.
|
|
198
205
|
|
|
199
206
|
"""
|
|
200
207
|
op_table = copy.deepcopy(_quantum_ops_library)
|
|
201
|
-
for op_name,
|
|
202
|
-
|
|
203
|
-
implementations =
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
208
|
+
for op_name, op_definition in ops.items():
|
|
209
|
+
# prepare the implementations
|
|
210
|
+
implementations: dict[str, type[GateImplementation]] = {}
|
|
211
|
+
for impl_name, impl_class_def in op_definition.pop("implementations", {}).items():
|
|
212
|
+
if isinstance(impl_class_def, str):
|
|
213
|
+
# check if the impl class name has been exposed
|
|
214
|
+
impl_class_name = impl_class_def
|
|
215
|
+
if (impl_class := get_implementation_class(impl_class_name)) is None:
|
|
216
|
+
raise ValueError(
|
|
217
|
+
f"'{op_name}': Requested implementation class '{impl_class_name}' has not been exposed."
|
|
218
|
+
)
|
|
219
|
+
elif issubclass(impl_class_def, GateImplementation):
|
|
220
|
+
# bit of a hack: also accept GateImplementation classes directly
|
|
221
|
+
impl_class = impl_class_def
|
|
222
|
+
impl_class_name = impl_class.__name__
|
|
223
|
+
else:
|
|
224
|
+
raise ValueError(f"{op_name}: {impl_class_def} is neither a str or a type[GateImplementation].")
|
|
225
|
+
|
|
226
|
+
# check if we are overriding a canonical implementation name for this op
|
|
227
|
+
_validate_implementation(op_name, impl_name, impl_class_name)
|
|
228
|
+
implementations[impl_name] = impl_class
|
|
229
|
+
|
|
230
|
+
# validate defaults_for_locus
|
|
231
|
+
defaults_for_locus: dict[Locus, str] = op_definition.pop("defaults_for_locus", {})
|
|
232
|
+
for locus, impl_name in defaults_for_locus.items():
|
|
233
|
+
if impl_name not in implementations:
|
|
234
|
+
raise ValueError(
|
|
235
|
+
f"'{op_name}': defaults_for_locus[{locus}] implementation '{impl_name}' does not "
|
|
236
|
+
f"appear in the implementations dict."
|
|
237
|
+
)
|
|
209
238
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
239
|
+
# prepare a new op, or modify the existing one
|
|
240
|
+
if (old_op := op_table.get(op_name)) is not None:
|
|
241
|
+
# known op: only some fields can be redefined, and they have been popped out already
|
|
242
|
+
if op_definition:
|
|
243
|
+
# TODO this should be an error, but there are so many old experiment.yml files in use
|
|
244
|
+
# that still have the old syntax that being strict about this would be disruptive.
|
|
245
|
+
# Now we just ignore the fields you cannot change.
|
|
246
|
+
logger.warning(
|
|
247
|
+
f"'{op_name}' is a canonical operation, which means the fields {set(op_definition)} "
|
|
248
|
+
"provided by the user may not be changed."
|
|
249
|
+
)
|
|
250
|
+
op_table[op_name] = replace(old_op, implementations=implementations, defaults_for_locus=defaults_for_locus)
|
|
213
251
|
else:
|
|
214
|
-
|
|
252
|
+
# new op
|
|
253
|
+
op_table[op_name] = QuantumOp(
|
|
254
|
+
name=op_name,
|
|
255
|
+
implementations=implementations,
|
|
256
|
+
defaults_for_locus=defaults_for_locus,
|
|
257
|
+
**op_definition,
|
|
258
|
+
)
|
|
215
259
|
|
|
216
260
|
return op_table
|
|
217
261
|
|
|
@@ -300,20 +344,22 @@ class ScheduleBuilder:
|
|
|
300
344
|
raise ValueError(f"No operation found with the name {item} in ``self.op_table``.")
|
|
301
345
|
|
|
302
346
|
def inject_calibration(self, partial_calibration: OpCalibrationDataTree) -> None:
|
|
303
|
-
"""Inject new calibration data, changing
|
|
347
|
+
"""Inject new calibration data, changing :attr:`calibration` after initialisation.
|
|
304
348
|
|
|
305
|
-
Invalidates the
|
|
349
|
+
Invalidates the GateImplementation caches for the affected operations/implementations/loci. Also invalidates
|
|
306
350
|
the cache for any factorizable gate implementation, if any of its locus components was affected.
|
|
307
351
|
|
|
308
352
|
Args:
|
|
309
|
-
partial_calibration: data to be injected. Must have the same structure as
|
|
353
|
+
partial_calibration: data to be injected. Must have the same structure as :attr:`calibration` but does not
|
|
310
354
|
have to contain all operations/implementations/loci/values. Only the parts of the data that are
|
|
311
|
-
found will be merged into
|
|
355
|
+
found will be merged into :attr:`calibration` (including any ``None`` values). :attr:`_cache` will
|
|
312
356
|
be invalidated for the found operations/implementations/loci and only if the new calibration data
|
|
313
357
|
actually differs from the previous.
|
|
314
358
|
|
|
315
359
|
"""
|
|
360
|
+
# composite gates are always flushed (though we could only flush the ones whose member gate cal is changed!)
|
|
316
361
|
self.composite_cache.flush()
|
|
362
|
+
# merge the calibration changes
|
|
317
363
|
for op, op_data in partial_calibration.items():
|
|
318
364
|
for impl, impl_data in op_data.items():
|
|
319
365
|
for locus, locus_data in impl_data.items():
|
|
@@ -326,13 +372,16 @@ class ScheduleBuilder:
|
|
|
326
372
|
and locus in self._cache[op][impl]
|
|
327
373
|
and _dicts_differ(prev_calibration, new_calibration)
|
|
328
374
|
):
|
|
375
|
+
# invalidate only the affected GateImplementations
|
|
329
376
|
del self._cache[op][impl][locus]
|
|
330
377
|
if self.op_table[op].factorizable:
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
378
|
+
# factorizable ops only have cal data for single-component loci,
|
|
379
|
+
# but we also need to flush all loci that include the single-component locus
|
|
380
|
+
locus_component = locus[0]
|
|
381
|
+
# dict size cannot change while you iterate over it, hence the list of keys
|
|
382
|
+
for cached_locus in list(self._cache[op][impl]):
|
|
383
|
+
if locus_component in set(cached_locus):
|
|
384
|
+
del self._cache[op][impl][cached_locus]
|
|
336
385
|
|
|
337
386
|
def validate_calibration(self) -> None:
|
|
338
387
|
"""Check that the calibration data matches the known quantum operations.
|
|
@@ -568,7 +617,7 @@ class ScheduleBuilder:
|
|
|
568
617
|
*,
|
|
569
618
|
use_priority_order: bool = False,
|
|
570
619
|
strict_locus: bool = False,
|
|
571
|
-
priority_calibration: OILCalibrationData | None = None,
|
|
620
|
+
priority_calibration: OILCalibrationData | OICalibrationData | None = None,
|
|
572
621
|
) -> GateImplementation:
|
|
573
622
|
"""Provide an implementation for a quantum operation at a given locus.
|
|
574
623
|
|
|
@@ -585,10 +634,11 @@ class ScheduleBuilder:
|
|
|
585
634
|
1. The locus-specific priority defined in ``QuantumOp.defaults_for_locus[locus]`` if any.
|
|
586
635
|
2. The global priority order defined in :attr:`QuantumOp.implementations`.
|
|
587
636
|
priority_calibration: Calibration data from which to load the calibration instead of the common calibration
|
|
588
|
-
data in :attr:`calibration`.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
637
|
+
data in :attr:`calibration`. Any non-None values found in ``priority_calibration``
|
|
638
|
+
will be merged to the common calibration.
|
|
639
|
+
For factorizable QuantumOps this is a mapping from single-qubit loci to their calibration data,
|
|
640
|
+
otherwise just the calibration data for a single locus.
|
|
641
|
+
Note: using ``priority_calibration`` will prevent caching.
|
|
592
642
|
|
|
593
643
|
Returns:
|
|
594
644
|
requested implementation
|
|
@@ -615,15 +665,15 @@ class ScheduleBuilder:
|
|
|
615
665
|
strict_locus: bool = False,
|
|
616
666
|
) -> tuple[str, Locus]:
|
|
617
667
|
"""Find an implementation and locus for the given quantum operation instance compatible
|
|
618
|
-
with the calibration data.
|
|
668
|
+
with both the calibration data and the implementation and locus requested by the caller.
|
|
619
669
|
|
|
620
670
|
Args:
|
|
621
671
|
op: quantum operation
|
|
622
|
-
impl_name: Name of the implementation. ``None`` means use the highest-priority
|
|
623
|
-
which we have calibration data.
|
|
624
|
-
locus: locus of the operation
|
|
625
|
-
strict_locus:
|
|
626
|
-
be changed to an equivalent one if no calibration data is available for the requested locus order
|
|
672
|
+
impl_name: Name of the requested implementation. ``None`` means use the highest-priority
|
|
673
|
+
implementation for which we have calibration data.
|
|
674
|
+
locus: requested locus of the operation
|
|
675
|
+
strict_locus: Iff False, for non-symmetric implementations of symmetric ops the locus order may
|
|
676
|
+
be changed to an equivalent one if no calibration data is available for the requested locus order.
|
|
627
677
|
|
|
628
678
|
Returns:
|
|
629
679
|
chosen implementation name, locus
|
|
@@ -647,24 +697,26 @@ class ScheduleBuilder:
|
|
|
647
697
|
calibration data available. If none can be found, returns None.
|
|
648
698
|
"""
|
|
649
699
|
if not impl_class.needs_calibration():
|
|
700
|
+
# any locus is ok
|
|
701
|
+
# FIXME This is wrong for compositegates, see SW-1016
|
|
650
702
|
return given_locus
|
|
651
703
|
if op.factorizable and len(given_locus) > 1:
|
|
704
|
+
# check delegated to subimplementations
|
|
652
705
|
return given_locus
|
|
653
706
|
# find out which loci we need to check for cal data
|
|
654
707
|
if op.symmetric:
|
|
708
|
+
# all locus orders are equivalent for perfectly calibrated symmetric ops, locus can be permuted
|
|
655
709
|
if impl_class.symmetric:
|
|
656
710
|
# Cal data for symmetric implementations uses always a sorted locus order.
|
|
657
711
|
loci = [tuple(sort_components(given_locus))]
|
|
658
712
|
elif strict_locus:
|
|
659
713
|
# If the operation is symmetric but implementation is not (e.g. fast flux CZ)
|
|
660
|
-
# the locus order can be meaningful.
|
|
714
|
+
# the locus order can be meaningful in practice.
|
|
661
715
|
# Users must be able to request implementations for any order of the locus, which may have
|
|
662
716
|
# independent cal data.
|
|
663
|
-
# User requested this implementation, we assume they also want this particular locus.
|
|
664
717
|
loci = [given_locus]
|
|
665
718
|
else:
|
|
666
|
-
# User did not request a
|
|
667
|
-
# with any implementation and locus order. We pick the first locus order that has cal data.
|
|
719
|
+
# User did not request a strict locus order, pick the first one that has cal data.
|
|
668
720
|
loci = list(itertools.permutations(given_locus))
|
|
669
721
|
else:
|
|
670
722
|
# For non-symmetric ops the locus is always strict.
|
|
@@ -716,12 +768,30 @@ class ScheduleBuilder:
|
|
|
716
768
|
impl_name: str | None,
|
|
717
769
|
locus: Locus,
|
|
718
770
|
strict_locus: bool = False,
|
|
719
|
-
|
|
771
|
+
*,
|
|
772
|
+
priority_calibration: OILCalibrationData | OICalibrationData | None = None,
|
|
720
773
|
) -> GateImplementation:
|
|
721
774
|
"""Build a factory class for the given quantum operation, implementation and locus.
|
|
722
775
|
|
|
723
776
|
The GateImplementations are built when they are first requested, and cached for later use.
|
|
724
777
|
|
|
778
|
+
The attributes :attr:`QuantumOp.factorizable`, :attr:`GateImplementation.needs_calibration` and whether
|
|
779
|
+
the implementation is a :class:`CompositeGate` interact in a nontrivial way, described in the table below.
|
|
780
|
+
|
|
781
|
+
.. list-table::
|
|
782
|
+
:header-rows: 1
|
|
783
|
+
:stub-columns: 1
|
|
784
|
+
|
|
785
|
+
* - composite / not composite
|
|
786
|
+
- factorizable
|
|
787
|
+
- not factorizable
|
|
788
|
+
* - needs_calibration
|
|
789
|
+
- not in use yet / ``measure.constant``
|
|
790
|
+
- ``cc_prx.prx_composite`` / ``prx.drag_crf``
|
|
791
|
+
* - not needs_calibration
|
|
792
|
+
- ``reset.conditional`` / not meaningful
|
|
793
|
+
- ``rz.prx_composite`` / ``rz.virtual``
|
|
794
|
+
|
|
725
795
|
Args:
|
|
726
796
|
op: quantum operation
|
|
727
797
|
impl_name: Name of the implementation. ``None`` means use the highest-priority implementation for
|
|
@@ -729,10 +799,11 @@ class ScheduleBuilder:
|
|
|
729
799
|
locus: locus of the operation
|
|
730
800
|
strict_locus: iff False, for non-symmetric implementations of symmetric ops the locus order may
|
|
731
801
|
be changed if no calibration data is available for the requested locus order
|
|
732
|
-
priority_calibration: Calibration data from which to load the calibration instead of the common
|
|
733
|
-
calibration data.
|
|
734
|
-
|
|
735
|
-
|
|
802
|
+
priority_calibration: Calibration data node from which to load the calibration instead of the common
|
|
803
|
+
calibration data. Only overrides the given parameters. For this to work, ``impl_name`` should be given,
|
|
804
|
+
since ``priority_calibration`` is implementation-specific.
|
|
805
|
+
For factorizable QuantumOps this is a mapping from single-qubit loci to their calibration data,
|
|
806
|
+
otherwise just the calibration data for a single locus.
|
|
736
807
|
|
|
737
808
|
Returns:
|
|
738
809
|
requested implementation
|
|
@@ -742,45 +813,73 @@ class ScheduleBuilder:
|
|
|
742
813
|
|
|
743
814
|
"""
|
|
744
815
|
new_impl_name, new_locus = self._find_implementation_and_locus(op, impl_name, locus, strict_locus=strict_locus)
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
816
|
+
# use caching if no priority calibration is used
|
|
817
|
+
if not priority_calibration:
|
|
818
|
+
# use a cached factory if it exists
|
|
748
819
|
op_cache = self._cache.setdefault(op.name, {})
|
|
749
820
|
impl_cache = op_cache.setdefault(new_impl_name, {})
|
|
750
821
|
if factory := impl_cache.get(new_locus):
|
|
751
822
|
return factory
|
|
752
823
|
|
|
824
|
+
# find the calibration data
|
|
753
825
|
impl_class = op.implementations[new_impl_name]
|
|
754
|
-
if
|
|
755
|
-
#
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
826
|
+
if op.factorizable and len(new_locus) > 1 and impl_class.may_have_calibration():
|
|
827
|
+
# E.g. measure.constant (needs_calibration), reset.conditional (composite, not needs_calibration)
|
|
828
|
+
# Currently there are no factorizable gates that are both composite and need calibration.
|
|
829
|
+
# For factorizable QuantumOps all the calibration data is for single-component loci,
|
|
830
|
+
# so priority_calibration is of the type OICalibrationData.
|
|
831
|
+
# Build (and possibly cache) the required single-component implementations, then
|
|
832
|
+
# use them to construct the full-locus implementation.
|
|
833
|
+
priority_calibration = priority_calibration or {}
|
|
834
|
+
factory = impl_class.construct_factorizable(
|
|
835
|
+
parent=op,
|
|
836
|
+
name=new_impl_name,
|
|
837
|
+
locus=new_locus,
|
|
838
|
+
sub_implementations={
|
|
839
|
+
c: self._get_implementation(
|
|
840
|
+
op,
|
|
841
|
+
new_impl_name,
|
|
842
|
+
(c,),
|
|
843
|
+
priority_calibration={(c,): c_cal} if (c_cal := priority_calibration.get((c,))) else None, # type: ignore
|
|
844
|
+
)
|
|
845
|
+
for c in new_locus
|
|
846
|
+
},
|
|
847
|
+
builder=self,
|
|
848
|
+
)
|
|
771
849
|
else:
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
850
|
+
if impl_class.may_have_calibration():
|
|
851
|
+
# Either needs_calibration or is CompositeGate.
|
|
852
|
+
# Find the calibration data, which is all found under new_locus.
|
|
853
|
+
if impl_class.needs_calibration():
|
|
854
|
+
cal_data = self.get_calibration(op.name, new_impl_name, new_locus)
|
|
855
|
+
else:
|
|
856
|
+
# cal data optional
|
|
857
|
+
try:
|
|
858
|
+
cal_data = self.get_calibration(op.name, new_impl_name, new_locus)
|
|
859
|
+
except ValueError:
|
|
860
|
+
cal_data = {}
|
|
861
|
+
|
|
862
|
+
if priority_calibration:
|
|
863
|
+
if op.factorizable:
|
|
864
|
+
# pick out the single-component locus
|
|
865
|
+
priority_calibration = priority_calibration[new_locus] # type: ignore[index]
|
|
866
|
+
cal_data = merge_dicts(cal_data, priority_calibration, merge_nones=False)
|
|
867
|
+
validate_locus_calibration(cal_data, impl_class, op, new_impl_name, new_locus)
|
|
868
|
+
else:
|
|
869
|
+
# no cal data needed, e.g. rz.virtual
|
|
870
|
+
cal_data = {}
|
|
871
|
+
|
|
872
|
+
# construct the factory
|
|
873
|
+
factory = impl_class(
|
|
874
|
+
parent=op,
|
|
875
|
+
name=new_impl_name,
|
|
876
|
+
locus=new_locus,
|
|
877
|
+
calibration_data=cal_data,
|
|
878
|
+
builder=self,
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
# cache the factory if no priority_calibration was used
|
|
882
|
+
if not priority_calibration:
|
|
784
883
|
impl_cache[new_locus] = factory
|
|
785
884
|
return factory
|
|
786
885
|
|
|
@@ -1311,7 +1410,7 @@ class ScheduleBuilder:
|
|
|
1311
1410
|
}
|
|
1312
1411
|
|
|
1313
1412
|
pl = Playlist()
|
|
1314
|
-
mapped_instructions: dict[str, dict[int, Any]] = {}
|
|
1413
|
+
mapped_instructions: dict[str, dict[int | Instruction, Any]] = {}
|
|
1315
1414
|
|
|
1316
1415
|
def _append_to_schedule(sc_schedule: SC_Schedule, channel_name: str, instr: Instruction) -> None:
|
|
1317
1416
|
"""Append ``instr`` to ``sc_schedule`` into the channel``channel_name``."""
|
|
@@ -1320,10 +1419,12 @@ class ScheduleBuilder:
|
|
|
1320
1419
|
# 2 dataclasses can have the same hash if their fields are identical. We must
|
|
1321
1420
|
# distinguish between different Waveform classes which may have identical fields,
|
|
1322
1421
|
# so we use the instruction itself as a key, so that the class is checked too.
|
|
1323
|
-
instr_id =
|
|
1422
|
+
instr_id = instr # type: ignore[attr-defined]
|
|
1423
|
+
is_mapped = instr_id in mapped_instructions.setdefault(channel_name, {})
|
|
1324
1424
|
except TypeError:
|
|
1325
1425
|
instr_id = instr.id # type: ignore[attr-defined]
|
|
1326
|
-
|
|
1426
|
+
is_mapped = instr_id in mapped_instructions.setdefault(channel_name, {})
|
|
1427
|
+
if not is_mapped:
|
|
1327
1428
|
mapped = _map_instruction(instr)
|
|
1328
1429
|
idx = pl.channel_descriptions[channel_name].add_instruction(mapped)
|
|
1329
1430
|
sc_schedule.instructions.setdefault(channel_name, []).append(idx)
|
iqm/pulse/circuit_operations.py
CHANGED
|
@@ -350,11 +350,6 @@ class CircuitOperationList(list):
|
|
|
350
350
|
qubit_names = self.qubits
|
|
351
351
|
if name not in self.table:
|
|
352
352
|
raise KeyError(f"QuantumOp with name {name} is not in the gate definitions table.")
|
|
353
|
-
arity = self.table[name].arity
|
|
354
|
-
if len(locus_indices) != len(set(locus_indices)):
|
|
355
|
-
raise ValueError("Repeated locus indices.")
|
|
356
|
-
if arity and arity != len(locus_indices): # arity = 0 is barrier and measure
|
|
357
|
-
raise ValueError(f"Operation {name} has {arity=} but {len(locus_indices)} target qubits were provided.")
|
|
358
353
|
|
|
359
354
|
try:
|
|
360
355
|
locus = tuple(qubit_names[idx] for idx in locus_indices)
|