iqm-pulse 10.2.0__py3-none-any.whl → 10.4.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 CHANGED
@@ -45,7 +45,7 @@ from iqm.pulse.gate_implementation import (
45
45
  OpCalibrationDataTree,
46
46
  )
47
47
  from iqm.pulse.gates import _validate_implementation, get_implementation_class
48
- from iqm.pulse.gates.default_gates import _quantum_ops_library
48
+ from iqm.pulse.gates.default_gates import _deprecated_implementations, _deprecated_ops, _quantum_ops_library
49
49
  from iqm.pulse.playlist.channel import ChannelProperties, ProbeChannelProperties
50
50
  from iqm.pulse.playlist.instructions import (
51
51
  AcquisitionMethod,
@@ -184,8 +184,12 @@ def validate_quantum_circuit(
184
184
  def build_quantum_ops(ops: dict[str, dict[str, Any]]) -> QuantumOpTable:
185
185
  """Builds the table of known quantum operations.
186
186
 
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.
187
+ Hardcoded canonical ops table is extended by the ones in ``ops``.
188
+ In case of name collisions, the content of ``ops`` takes priority over the defaults,
189
+ with the following caveats:
190
+
191
+ * canonical implementation names cannot be redefined, and
192
+ * for canonical operations you can only change :attr:`implementations` and :attr:`defaults_for_locus`.
189
193
 
190
194
  Args:
191
195
  ops: Contents of the ``gate_definitions`` section defining
@@ -204,7 +208,23 @@ def build_quantum_ops(ops: dict[str, dict[str, Any]]) -> QuantumOpTable:
204
208
  ValueError: Operation attributes don't match defaults or are invalid.
205
209
 
206
210
  """
207
- op_table = copy.deepcopy(_quantum_ops_library)
211
+ # build the table of native ops
212
+ op_table = {}
213
+ for op_name, op in _quantum_ops_library.items():
214
+ # filter out deprecated ops, unless requested by the user in ``ops``
215
+ if op_name in _deprecated_ops and op_name not in ops:
216
+ continue
217
+
218
+ new_op = copy.deepcopy(op) # to prevent modifications to the hardcoded table
219
+ if (deprecated_impls := _deprecated_implementations.get(op_name)) is not None:
220
+ # filter out deprecated implementations
221
+ non_deprecated = {
222
+ impl_name: impl for impl_name, impl in op.implementations.items() if impl_name not in deprecated_impls
223
+ }
224
+ new_op = replace(op, implementations=non_deprecated)
225
+ op_table[op_name] = new_op
226
+
227
+ # add ops defined by the user
208
228
  for op_name, op_definition in ops.items():
209
229
  # prepare the implementations
210
230
  implementations: dict[str, type[GateImplementation]] = {}
@@ -236,9 +256,9 @@ def build_quantum_ops(ops: dict[str, dict[str, Any]]) -> QuantumOpTable:
236
256
  f"appear in the implementations dict."
237
257
  )
238
258
 
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
259
+ if (old_op := _quantum_ops_library.get(op_name)) is not None:
260
+ # modify a canonical operation
261
+ # only some fields can be modified, and they have been popped out already from op_definition
242
262
  if op_definition:
243
263
  # TODO this should be an error, but there are so many old experiment.yml files in use
244
264
  # that still have the old syntax that being strict about this would be disruptive.
@@ -247,15 +267,17 @@ def build_quantum_ops(ops: dict[str, dict[str, Any]]) -> QuantumOpTable:
247
267
  f"'{op_name}' is a canonical operation, which means the fields {set(op_definition)} "
248
268
  "provided by the user may not be changed."
249
269
  )
250
- op_table[op_name] = replace(old_op, implementations=implementations, defaults_for_locus=defaults_for_locus)
270
+
271
+ op = replace(old_op, implementations=implementations, defaults_for_locus=defaults_for_locus)
251
272
  else:
252
- # new op
253
- op_table[op_name] = QuantumOp(
273
+ # entirely new quantum operation defined by the user
274
+ op = QuantumOp(
254
275
  name=op_name,
255
276
  implementations=implementations,
256
277
  defaults_for_locus=defaults_for_locus,
257
278
  **op_definition,
258
279
  )
280
+ op_table[op_name] = op
259
281
 
260
282
  return op_table
261
283
 
@@ -42,10 +42,11 @@ from iqm.pulse.gates.cz import (
42
42
  CZ_Slepian_ACStarkCRF,
43
43
  CZ_Slepian_CRF,
44
44
  CZ_TruncatedGaussianSmoothedSquare,
45
+ FluxPulse_SmoothConstant_SmoothConstant,
45
46
  FluxPulseGate_CRF_CRF,
46
47
  FluxPulseGate_TGSS_CRF,
47
48
  )
48
- from iqm.pulse.gates.default_gates import _default_implementations, _quantum_ops_library
49
+ from iqm.pulse.gates.default_gates import _implementation_library, _quantum_ops_library
49
50
  from iqm.pulse.gates.delay import Delay
50
51
  from iqm.pulse.gates.flux_multiplexer import FluxMultiplexer_SampleLinear
51
52
  from iqm.pulse.gates.measure import Measure_Constant, Measure_Constant_Qnd, Shelved_Measure_Constant
@@ -98,6 +99,7 @@ _exposed_implementations: dict[str, type[GateImplementation]] = {
98
99
  CZ_Slepian,
99
100
  CZ_Slepian_CRF,
100
101
  CZ_CRF,
102
+ FluxPulse_SmoothConstant_SmoothConstant,
101
103
  CZ_TruncatedGaussianSmoothedSquare,
102
104
  FluxPulseGate_TGSS_CRF,
103
105
  FluxPulseGate_CRF_CRF,
@@ -155,7 +157,7 @@ def _validate_implementation(
155
157
  ValueError: A canonical implementation name is being redefined.
156
158
 
157
159
  """
158
- default_implementations = _default_implementations.get(op_name, {})
160
+ default_implementations = _implementation_library.get(op_name, {})
159
161
 
160
162
  # check if the implementation name is canonical for this op
161
163
  if (impl_class := default_implementations.get(impl_name)) is not None:
iqm/pulse/gates/cz.py CHANGED
@@ -22,6 +22,7 @@ It can be represented by the unitary matrix
22
22
  from __future__ import annotations
23
23
 
24
24
  from dataclasses import replace
25
+ import logging
25
26
  from typing import TYPE_CHECKING
26
27
 
27
28
  import numpy as np
@@ -32,14 +33,16 @@ from iqm.pulse.gate_implementation import GateImplementation, Locus, OILCalibrat
32
33
  from iqm.pulse.playlist.instructions import Block, FluxPulse, Instruction, IQPulse, VirtualRZ
33
34
  from iqm.pulse.playlist.schedule import Schedule
34
35
  from iqm.pulse.playlist.waveforms import (
36
+ Constant,
37
+ CosineFallFlex,
35
38
  CosineRiseFall,
39
+ CosineRiseFlex,
36
40
  GaussianSmoothedSquare,
37
41
  ModulatedCosineRiseFall,
38
42
  Slepian,
39
43
  TruncatedGaussianSmoothedSquare,
40
44
  Waveform,
41
45
  )
42
- from iqm.pulse.timebox import TimeBox
43
46
  from iqm.pulse.utils import phase_transformation
44
47
 
45
48
  if TYPE_CHECKING: # pragma: no cover
@@ -79,9 +82,9 @@ class FluxPulseGate(GateImplementation):
79
82
  """Flux pulse Waveform to be played in the coupler flux AWG."""
80
83
  qubit_wave: type[Waveform] | None
81
84
  """Flux pulse Waveform to be played in the qubit flux AWG."""
82
- root_parameters: dict[str, Parameter | Setting] = {
85
+ root_parameters: dict[str, Parameter | Setting | dict] = {
83
86
  "duration": Parameter("", "Gate duration", "s"),
84
- "rz": { # type: ignore[dict-item]
87
+ "rz": {
85
88
  "*": Parameter("", "Z rotation angle", "rad"), # wildcard parameter
86
89
  },
87
90
  }
@@ -261,9 +264,9 @@ class CouplerFluxPulseQubitACStarkPulseGate(GateImplementation):
261
264
  qubit_drive_wave: type[Waveform] | None
262
265
  """Qubit drive pulse waveform to be played in the qubit drive AWG."""
263
266
 
264
- root_parameters: dict[str, Parameter | Setting] = {
267
+ root_parameters: dict[str, Parameter | Setting | dict] = {
265
268
  "duration": Parameter("", "Gate duration", "s"),
266
- "rz": { # type: ignore[dict-item]
269
+ "rz": {
267
270
  "*": Parameter("", "Z rotation angle", "rad"),
268
271
  },
269
272
  }
@@ -426,3 +429,303 @@ class CZ_CRF_ACStarkCRF(
426
429
  CZ gate implemented using a cosine rise fall flux pulse for the coupler and a modulated
427
430
  cosine rise fall (CRF) AC Stark pulse on one qubit.
428
431
  """
432
+
433
+
434
+ def round_to_granularity(value: float, granularity: float, precision: float = 1e-15) -> float:
435
+ """Round a value to the nearest multiple of granularity.
436
+ If the value is within a given precision of a multiple, round to that multiple.
437
+ Otherwise, round down to the nearest lower multiple.
438
+
439
+ Args:
440
+ value:
441
+ granularity: granularity
442
+ precision: rounding precision.
443
+
444
+ Returns:
445
+ value rounded to a granularity.
446
+
447
+ """
448
+ return np.floor(value / granularity + precision) * granularity
449
+
450
+
451
+ def split_flat_top_part_into_granular_parts(
452
+ duration: float, full_width: float, rise_time: float, granularity: float, precision: float = 1e-10
453
+ ) -> tuple[float, float, float, float]:
454
+ """To save waveform memory, a (long) flat-top pulse, which is defined by its duration, full_width and rise_time,
455
+ is divided into three consecutive parts (rise, flat, and fall),
456
+ all of which conform to the granularity of the device.
457
+
458
+ Args:
459
+ duration: pulse duration in seconds.
460
+ full_width: full width of the pulse.
461
+ rise_time: rise time of the pulse.
462
+ granularity: minimum allowed pulse duration.
463
+ precision: precision of rounding to granularity,
464
+
465
+
466
+ Returns:
467
+ A tuple containing:
468
+ - flat part duration
469
+ - rise (or fall) part duration
470
+ - rise time
471
+ - flat part's non-granular leftover, which is transferred to the rise and fall parts
472
+
473
+ Raises:
474
+ ValueError: Error is raised if duration is not a multiple of granularity.
475
+ ValueError: Error is raised if pulse parameters do not obey duration >= full_width >= 2*rise_time.
476
+
477
+ """
478
+ # Check if the number of samples is within 0.005 samples of an integer number, considered safe.
479
+ if not round(duration / granularity, ndigits=2).is_integer():
480
+ raise ValueError("Duration must be a multiple of granularity.")
481
+
482
+ if (duration >= full_width) & (full_width >= 2 * rise_time):
483
+ plateau_width = full_width - 2 * rise_time
484
+
485
+ plateau_width_granular = round_to_granularity(plateau_width, granularity)
486
+ rise_duration = (duration - plateau_width_granular) / 2
487
+
488
+ if np.abs(rise_duration - np.round(rise_duration / granularity) * granularity) > precision:
489
+ plateau_width_granular -= granularity
490
+ rise_duration = (duration - plateau_width_granular) / 2
491
+
492
+ flat_part = duration - 2 * rise_duration
493
+ plateau_leftover = (full_width - 2 * rise_time - flat_part) / 2
494
+
495
+ return plateau_width_granular, rise_duration, rise_time, plateau_leftover
496
+ else:
497
+ raise ValueError(
498
+ f"Current pulse parameters (duration {duration}, full_width {full_width}, rise_time {rise_time}) "
499
+ f"are impossible, please use duration >= full_width >= 2*rise_time."
500
+ )
501
+
502
+
503
+ class FluxPulseGate_SmoothConstant(FluxPulseGate):
504
+ """Flux pulse gate implementation realized as a 3-part pulse sequence,
505
+ consisting of |cosine rise|Constant|cosine fall|. Otherwise, works similar to FluxPulseGate.
506
+
507
+ Args:
508
+ flux_pulses: mapping from flux channel name to its flux pulse
509
+ rz: mapping from drive channel name to the virtual z rotation angle, in radians, that should be performed on it
510
+
511
+ """
512
+
513
+ coupler_wave: Constant | None
514
+ """Flux pulse Waveform to be played in the coupler flux AWG. Can be only Constant or None"""
515
+ qubit_wave: Constant | None
516
+ """Flux pulse Waveform to be played in the qubit flux AWG. Can be only Constant or None"""
517
+ rise_wave: type[Waveform] = CosineRiseFlex
518
+ """Waveform, rise part of the 3-pulse sequence to be played with qubit and coupler gates."""
519
+ fall_wave: type[Waveform] = CosineFallFlex
520
+ """Waveform, fall part of the 3-pulse sequence to be played with qubit and coupler gates."""
521
+
522
+ root_parameters: dict[str, Parameter | Setting | dict] = {
523
+ "duration": Parameter("", "Gate duration", "s"),
524
+ "qubit": {
525
+ "rise_time": Parameter("", "Qubit pulse rise time", "s"),
526
+ "full_width": Parameter("", "Qubit pulse full width", "s"),
527
+ "amplitude": Parameter("", "Qubit pulse amplitude", ""),
528
+ },
529
+ "coupler": {
530
+ "rise_time": Parameter("", "Coupler pulse rise time", "s"),
531
+ "full_width": Parameter("", "Coupler pulse full width", "s"),
532
+ "amplitude": Parameter("", "Coupler pulse amplitude", ""),
533
+ },
534
+ "rz": {
535
+ "*": Parameter("", "Z rotation angle", "rad"),
536
+ },
537
+ }
538
+
539
+ def __init__(
540
+ self,
541
+ parent: QuantumOp,
542
+ name: str,
543
+ locus: Locus,
544
+ calibration_data: OILCalibrationData,
545
+ builder: ScheduleBuilder,
546
+ ) -> None:
547
+ GateImplementation.__init__(self, parent, name, locus, calibration_data, builder)
548
+ duration = calibration_data["duration"]
549
+
550
+ flux_pulses = {}
551
+ rise_pulses = {}
552
+ fall_pulses = {}
553
+
554
+ def build_flux_pulse(waveform_class: type[Waveform], component_name: str, cal_node_name: str) -> None:
555
+ """Uses a part of the gate calibration data to prepare a flux pulse for the given component."""
556
+ flux_channel = builder.get_flux_channel(component_name)
557
+
558
+ granularity = builder.channels[flux_channel].duration_to_seconds(
559
+ builder.channels[flux_channel].instruction_duration_min
560
+ )
561
+
562
+ data = calibration_data[cal_node_name]
563
+ calibration_data_constant = data.copy()
564
+ calibration_data_rise = data.copy()
565
+
566
+ plateau_width_granular, rise_duration, rise_time, plateau_leftover = (
567
+ split_flat_top_part_into_granular_parts(duration, data["full_width"], data["rise_time"], granularity)
568
+ )
569
+ calibration_data_rise["rise_time"] = rise_time
570
+ calibration_data_constant["duration"] = plateau_width_granular
571
+ calibration_data_rise["duration"] = rise_duration
572
+ calibration_data_rise["full_width"] = plateau_leftover + rise_time
573
+
574
+ if plateau_width_granular > 0:
575
+ params_for_flux_pulses = self.convert_calibration_data(
576
+ calibration_data=calibration_data_constant,
577
+ params=self.parameters[cal_node_name], # type: ignore[arg-type]
578
+ channel_props=builder.channels[flux_channel],
579
+ duration=plateau_width_granular,
580
+ )
581
+ else:
582
+ params_for_flux_pulses = {"n_samples": 0, "amplitude": calibration_data_constant["amplitude"]}
583
+
584
+ params_for_risefall = self.convert_calibration_data(
585
+ calibration_data=calibration_data_rise,
586
+ params=self.parameters[cal_node_name], # type: ignore[arg-type]
587
+ channel_props=builder.channels[flux_channel],
588
+ duration=rise_duration,
589
+ )
590
+
591
+ params_for_flux_pulses["n_samples"] = (
592
+ builder.channels[flux_channel].duration_to_int_samples(plateau_width_granular)
593
+ if plateau_width_granular > 0
594
+ else 0
595
+ )
596
+
597
+ params_for_risefall["n_samples"] = (
598
+ builder.channels[flux_channel].duration_to_int_samples(rise_duration) if rise_duration > 0 else 0
599
+ )
600
+
601
+ amplitude = params_for_flux_pulses.pop("amplitude")
602
+ params_for_risefall.pop("amplitude")
603
+
604
+ flux_pulses[flux_channel] = (
605
+ FluxPulse(
606
+ duration=params_for_flux_pulses["n_samples"],
607
+ wave=waveform_class(n_samples=params_for_flux_pulses["n_samples"]),
608
+ scale=amplitude,
609
+ )
610
+ if params_for_flux_pulses["n_samples"] > 0
611
+ else None
612
+ )
613
+
614
+ if params_for_risefall["n_samples"] > 0:
615
+ rise_pulses[flux_channel] = FluxPulse(
616
+ duration=params_for_risefall["n_samples"],
617
+ wave=self.rise_wave(**params_for_risefall),
618
+ scale=amplitude,
619
+ )
620
+ fall_pulses[flux_channel] = FluxPulse(
621
+ duration=params_for_risefall["n_samples"],
622
+ wave=self.fall_wave(**params_for_risefall),
623
+ scale=amplitude,
624
+ )
625
+ else:
626
+ rise_pulses[flux_channel] = None # type: ignore[assignment]
627
+ fall_pulses[flux_channel] = None # type: ignore[assignment]
628
+
629
+ if self.coupler_wave is not None:
630
+ build_flux_pulse(self.coupler_wave, builder.chip_topology.get_coupler_for(*locus), "coupler")
631
+
632
+ if self.qubit_wave is not None:
633
+ # the pulsed qubit is always the first one of the locus
634
+ build_flux_pulse(self.qubit_wave, locus[0], "qubit")
635
+
636
+ rz = calibration_data["rz"]
637
+ for c in locus:
638
+ if c not in rz:
639
+ raise ValueError(
640
+ f"{parent.name}.{name}: {locus}: Calibration is missing an RZ angle for locus component {c}."
641
+ )
642
+ rz_locus = {builder.get_drive_channel(c): angle for c, angle in rz.items() if c in locus}
643
+ rz_not_locus = tuple((builder.get_drive_channel(c), angle) for c, angle in rz.items() if c not in locus)
644
+
645
+ schedule: dict[str, list[Instruction]] = {
646
+ channel: [
647
+ VirtualRZ(
648
+ duration=builder.channels[channel].duration_to_int_samples(duration),
649
+ phase_increment=-angle,
650
+ )
651
+ ]
652
+ for channel, angle in rz_locus.items()
653
+ }
654
+ vzs_inserted = False # insert the long-distance Vzs to the first flux pulse (whatever that is)
655
+ for channel, flux_pulse in flux_pulses.items():
656
+ if rz_not_locus and not vzs_inserted and flux_pulse:
657
+ schedule[channel] = [replace(flux_pulse, rzs=rz_not_locus)]
658
+ vzs_inserted = True
659
+ elif duration > 0:
660
+ schedule[channel] = [
661
+ v for v in [rise_pulses[channel], flux_pulse, fall_pulses[channel]] if v is not None
662
+ ]
663
+ else:
664
+ schedule[channel] = []
665
+ affected_components = set(locus)
666
+ affected_components.add(builder.chip_topology.get_coupler_for(*locus))
667
+ self._affected_components = affected_components
668
+ self._schedule = Schedule(schedule if duration > 0 else {c: [Block(0)] for c in schedule}, duration=duration)
669
+
670
+ def __init_subclass__(
671
+ cls,
672
+ /,
673
+ coupler_wave: type[Waveform] | None = None,
674
+ qubit_wave: type[Waveform] | None = None,
675
+ rise_wave: type[Waveform] = CosineRiseFlex,
676
+ fall_wave: type[Waveform] = CosineFallFlex,
677
+ ):
678
+ if coupler_wave is None and qubit_wave is None and hasattr(cls, "coupler_wave") and hasattr(cls, "qubit_wave"):
679
+ return
680
+ if coupler_wave and (coupler_wave != Constant):
681
+ logging.getLogger(__name__).warning(
682
+ "Forcing coupler wave to be Constant",
683
+ )
684
+ coupler_wave = Constant
685
+ if qubit_wave and (qubit_wave != Constant):
686
+ logging.getLogger(__name__).warning(
687
+ "Forcing qubit wave to be Constant",
688
+ )
689
+ qubit_wave = Constant
690
+
691
+ cls.coupler_wave = coupler_wave
692
+ cls.qubit_wave = qubit_wave
693
+ cls.symmetric = cls.qubit_wave is None
694
+ cls.fall_wave = fall_wave
695
+ cls.rise_wave = rise_wave
696
+
697
+ root_parameters = {k: v for k, v in cls.root_parameters.items() if k not in cls.excluded_parameters}
698
+ parameters = {}
699
+ if coupler_wave is not None:
700
+ parameters["coupler"] = (
701
+ get_waveform_parameters(rise_wave, label_prefix="Coupler flux pulse ")
702
+ | get_waveform_parameters(fall_wave, label_prefix="Coupler flux pulse ")
703
+ | get_waveform_parameters(coupler_wave, label_prefix="Coupler flux pulse ")
704
+ )
705
+ parameters["coupler"]["amplitude"] = Parameter("", "Coupler flux pulse amplitude", "")
706
+ parameters["coupler"]["rise_time"] = Parameter("", "Coupler flux pulse rise time", "s")
707
+ parameters["coupler"]["full_width"] = Parameter("", "Coupler flux pulse full width", "s")
708
+
709
+ if qubit_wave is not None:
710
+ parameters["qubit"] = (
711
+ get_waveform_parameters(rise_wave, label_prefix="Qubit flux pulse ")
712
+ | get_waveform_parameters(fall_wave, label_prefix="Qubit flux pulse ")
713
+ | get_waveform_parameters(qubit_wave, label_prefix="Qubit flux pulse ")
714
+ )
715
+ parameters["qubit"]["amplitude"] = Parameter("", "Qubit flux pulse amplitude", "")
716
+ parameters["qubit"]["rise_time"] = Parameter("", "Qubit flux pulse rise time", "s")
717
+ parameters["qubit"]["full_width"] = Parameter("", "Qubit flux pulse full width", "s")
718
+
719
+ cls.parameters = root_parameters | {k: v for k, v in parameters.items() if k not in cls.excluded_parameters}
720
+
721
+
722
+ class FluxPulse_SmoothConstant_qubit(FluxPulseGate_SmoothConstant, qubit_wave=Constant):
723
+ """Constant flux pulse on qubit with smooth rise/fall"""
724
+
725
+
726
+ class FluxPulse_SmoothConstant_coupler(FluxPulseGate_SmoothConstant, coupler_wave=Constant):
727
+ """Constant flux pulse on coupler with smooth rise/fall."""
728
+
729
+
730
+ class FluxPulse_SmoothConstant_SmoothConstant(FluxPulseGate_SmoothConstant, coupler_wave=Constant, qubit_wave=Constant):
731
+ """Constant flux pulse on both qubit and coupler with smooth rise/fall."""
@@ -15,7 +15,7 @@
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
- from typing import TYPE_CHECKING
18
+ from typing import TYPE_CHECKING, Final
19
19
 
20
20
  import numpy as np
21
21
 
@@ -63,7 +63,7 @@ if TYPE_CHECKING:
63
63
  from iqm.pulse.gate_implementation import GateImplementation
64
64
 
65
65
 
66
- _default_implementations: dict[str, dict[str, type[GateImplementation]]] = {
66
+ _implementation_library: dict[str, dict[str, type[GateImplementation]]] = {
67
67
  "barrier": {"": Barrier},
68
68
  "delay": {"wait": Delay},
69
69
  "measure": {
@@ -105,9 +105,11 @@ _default_implementations: dict[str, dict[str, type[GateImplementation]]] = {
105
105
  "reset_wait": {"reset_wait": Reset_Wait},
106
106
  "flux_multiplexer": {"sample_linear": FluxMultiplexer_SampleLinear},
107
107
  }
108
- """Canonical implementation names for the canonical quantum operations.
109
- A collection of mappings between default implementation names and their GateImplementation classes,
110
- for different gates."""
108
+ """For each canonical quantum operation name, maps its canonical implementation implementation names
109
+ to their GateImplementation classes.
110
+
111
+ Canonical names are reserved, and the users cannot redefine them.
112
+ """
111
113
 
112
114
  _quantum_ops_library = {
113
115
  op.name: op
@@ -115,101 +117,113 @@ _quantum_ops_library = {
115
117
  QuantumOp(
116
118
  "barrier",
117
119
  0,
118
- implementations=_default_implementations["barrier"], # type: ignore[arg-type]
120
+ implementations=_implementation_library["barrier"],
119
121
  symmetric=True,
120
122
  ),
121
123
  QuantumOp(
122
124
  "delay",
123
125
  0,
124
126
  ("duration",),
125
- implementations=_default_implementations["delay"], # type: ignore[arg-type]
127
+ implementations=_implementation_library["delay"],
126
128
  symmetric=True,
127
129
  ),
128
130
  QuantumOp(
129
131
  "measure",
130
132
  0,
131
133
  ("key",),
132
- implementations=_default_implementations["measure"], # type: ignore[arg-type]
134
+ implementations=_implementation_library["measure"],
133
135
  factorizable=True,
134
136
  ),
135
137
  QuantumOp(
136
138
  "prx",
137
139
  1,
138
140
  ("angle", "phase"),
139
- implementations=_default_implementations["prx"], # type: ignore[arg-type]
141
+ implementations=_implementation_library["prx"],
140
142
  unitary=get_unitary_prx,
141
143
  ),
142
144
  QuantumOp(
143
145
  "prx_12",
144
146
  1,
145
147
  ("angle", "phase"),
146
- implementations=_default_implementations["prx_12"], # type: ignore[arg-type]
148
+ implementations=_implementation_library["prx_12"],
147
149
  ),
148
150
  QuantumOp(
149
151
  "u",
150
152
  1,
151
153
  ("theta", "phi", "lam"),
152
- implementations=_default_implementations["u"], # type: ignore[arg-type]
154
+ implementations=_implementation_library["u"],
153
155
  unitary=get_unitary_u,
154
156
  ),
155
157
  QuantumOp(
156
158
  "sx",
157
159
  1,
158
- implementations=_default_implementations["sx"], # type: ignore[arg-type]
160
+ implementations=_implementation_library["sx"],
159
161
  unitary=lambda: get_unitary_prx(np.pi / 2, 0),
160
162
  ),
161
163
  QuantumOp(
162
164
  "rz",
163
165
  1,
164
166
  ("angle",),
165
- implementations=_default_implementations["rz"], # type: ignore[arg-type]
167
+ implementations=_implementation_library["rz"],
166
168
  unitary=get_unitary_rz,
167
169
  ),
168
170
  QuantumOp(
169
171
  "rz_physical",
170
172
  1,
171
- implementations=_default_implementations["rz_physical"], # type: ignore[arg-type]
173
+ implementations=_implementation_library["rz_physical"],
172
174
  ),
173
175
  QuantumOp(
174
176
  "cz",
175
177
  2,
176
178
  (),
177
- implementations=_default_implementations["cz"], # type: ignore[arg-type]
179
+ implementations=_implementation_library["cz"],
178
180
  symmetric=True,
179
181
  unitary=lambda: np.diag([1.0, 1.0, 1.0, -1.0]),
180
182
  ),
181
183
  QuantumOp(
182
184
  "move",
183
185
  2,
184
- implementations=_default_implementations["move"], # type: ignore[arg-type]
186
+ implementations=_implementation_library["move"],
185
187
  ),
186
188
  QuantumOp(
187
189
  "cc_prx",
188
190
  1,
189
191
  ("angle", "phase", "feedback_qubit", "feedback_key"),
190
- implementations=_default_implementations["cc_prx"], # type: ignore[arg-type]
192
+ implementations=_implementation_library["cc_prx"],
191
193
  ),
192
194
  QuantumOp(
193
195
  "reset",
194
196
  0,
195
- implementations=_default_implementations["reset"], # type: ignore[arg-type]
197
+ implementations=_implementation_library["reset"],
196
198
  symmetric=True,
197
199
  factorizable=True,
198
200
  ),
199
201
  QuantumOp(
200
202
  "reset_wait",
201
203
  0,
202
- implementations=_default_implementations["reset_wait"], # type: ignore[arg-type]
204
+ implementations=_implementation_library["reset_wait"],
203
205
  symmetric=True,
204
206
  factorizable=True,
205
207
  ),
206
208
  QuantumOp(
207
209
  "flux_multiplexer",
208
210
  0,
209
- implementations=_default_implementations["flux_multiplexer"], # type: ignore[arg-type]
211
+ implementations=_implementation_library["flux_multiplexer"],
210
212
  ),
211
213
  ]
212
214
  }
213
- """Library of the current canonical quantum operations provided by iqm-pulse."""
214
- # NOTE If a canonical operation is removed in the future, consider adding a second dict for former
215
- # canonical operations so that we retain information about them.
215
+ """Canonical quantum operations provided by iqm-pulse.
216
+
217
+ Their names are reserved, and the users cannot redefine them.
218
+ """
219
+
220
+ _deprecated_ops: Final[set[str]] = set()
221
+ """Names of canonical quantum operations that are deprecated.
222
+
223
+ They are not included by default in ScheduleBuilder unless the user specifically requests them."""
224
+ _deprecated_implementations: Final[dict[str, set[str]]] = {}
225
+ """For each canonical quantum operation name, canonical implementation names that are deprecated.
226
+
227
+ They are not included by default in ScheduleBuilder unless the user specifically requests them."""
228
+ # TODO: deprecate gaussian_smoothed_square and everything with the tgss waveform as that is considered inferior to crf.
229
+ # TODO: deprecate PRX_drag_gaussian
iqm/pulse/gates/move.py CHANGED
@@ -102,9 +102,9 @@ class MOVE_CustomWaveforms(FluxPulseGate):
102
102
  The phases are calculated and applied on the qubits using :func:`.apply_move_gate_phase_corrections`.
103
103
  """
104
104
 
105
- root_parameters: dict[str, Parameter | Setting] = {
105
+ root_parameters: dict[str, Parameter | Setting | dict] = {
106
106
  "duration": Parameter("", "Gate duration", "s"),
107
- "rz": { # type: ignore[dict-item]
107
+ "rz": {
108
108
  "*": Parameter("", "Z rotation angle", "rad"), # wildcard parameter
109
109
  },
110
110
  "detuning": Parameter("", "Qubit - resonator detuning", "Hz"),
@@ -498,3 +498,95 @@ class CosineFall(Waveform):
498
498
 
499
499
  def _sample(self, sample_coords: np.ndarray) -> np.ndarray:
500
500
  return 0.5 - 0.5 * np.sin(np.pi * sample_coords)
501
+
502
+
503
+ @dataclass(frozen=True)
504
+ class CosineRiseFlex(Waveform):
505
+ r"""Cosine Rise waveform with an extra duration buffer.
506
+
507
+ The waveform is a piecewise function: |buffer|cosine rise|flat plateau|, where:
508
+ - buffer is a 'leftover' constant signal with amplitude = 0, with duration of duration - full_width
509
+ - cosine rise is a cosine rise pulse with a duration of rise_time
510
+ - flat plateau is a constant signal with amplitude = 1, with duration of full_width - rise_time
511
+
512
+ Args:
513
+ rise_time: rise time of the waveform
514
+ full_width: combined duration of the cosine rise time and the flat plateau
515
+
516
+ Raises:
517
+ ValueError: Error is raised if full_width or rise_time is more than duration
518
+
519
+ """
520
+
521
+ rise_time: float
522
+ full_width: float
523
+
524
+ def _sample(self, sample_coords: np.ndarray) -> np.ndarray:
525
+ flat_part_duration = np.abs(self.full_width) - np.abs(self.rise_time)
526
+ rise_time_duration = np.abs(self.rise_time)
527
+ dead_wait_time = 1 - np.abs(self.full_width)
528
+
529
+ if dead_wait_time >= 0:
530
+ return np.piecewise(
531
+ sample_coords,
532
+ [
533
+ sample_coords <= 0.5 - flat_part_duration - rise_time_duration,
534
+ sample_coords > 0.5 - flat_part_duration - rise_time_duration,
535
+ sample_coords >= 0.5 - flat_part_duration, # flat carry-over from the Constant
536
+ ],
537
+ [
538
+ 0,
539
+ lambda oc: 0.5 - 0.5 * np.cos(np.pi / rise_time_duration * (oc - dead_wait_time + 0.5)),
540
+ 1,
541
+ ],
542
+ )
543
+ elif (flat_part_duration + dead_wait_time > 0) and (1 - rise_time_duration >= 0):
544
+ raise ValueError("Full width is more than duration")
545
+ else:
546
+ raise ValueError("Rise time is more than duration")
547
+
548
+
549
+ @dataclass(frozen=True)
550
+ class CosineFallFlex(Waveform):
551
+ r"""Cosine fall waveform with an extra duration buffer.
552
+
553
+ The waveform is a piecewise function: |flat plateau|cosine fall|buffer|, where:
554
+ - buffer is a 'leftover' constant signal with amplitude = 0, generally with duration of duration - full_width
555
+ - cosine fall is a cosine fall pulse with a duration of rise_time
556
+ - flat plateau is a constant signal with amplitude = 1, generally with duration of full_width - rise_time
557
+
558
+ Args:
559
+ rise_time: rise time of the waveform
560
+ full_width: combined duration of the cosine fall time and the flat plateau
561
+
562
+ Raises:
563
+ ValueError: Error is raised if full_width or rise_time is more than duration
564
+
565
+ """
566
+
567
+ rise_time: float
568
+ full_width: float
569
+
570
+ def _sample(self, sample_coords: np.ndarray) -> np.ndarray:
571
+ flat_part_duration = max(np.abs(self.full_width) - np.abs(self.rise_time), 0)
572
+ rise_time_duration = np.abs(self.rise_time)
573
+ dead_wait_time = 1 - flat_part_duration - rise_time_duration
574
+
575
+ if dead_wait_time >= 0:
576
+ return np.piecewise(
577
+ sample_coords,
578
+ [
579
+ sample_coords <= -0.5 + flat_part_duration, # flat corry-over from the Constant
580
+ sample_coords > -0.5 + flat_part_duration,
581
+ sample_coords >= -0.5 + flat_part_duration + rise_time_duration,
582
+ ],
583
+ [
584
+ 1,
585
+ lambda oc: 0.5 + 0.5 * np.cos(np.pi / rise_time_duration * (oc - flat_part_duration + 0.5)),
586
+ 0,
587
+ ],
588
+ )
589
+ elif (flat_part_duration + dead_wait_time > 0) and (1 - rise_time_duration >= 0):
590
+ raise ValueError("Full width is more than duration")
591
+ else:
592
+ raise ValueError("Rise time is more than duration")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulse
3
- Version: 10.2.0
3
+ Version: 10.4.0
4
4
  Summary: A Python-based project for providing interface and implementations for control pulses.
5
5
  Author-email: IQM Finland Oy <info@meetiqm.com>
6
6
  License: Apache License
@@ -1,6 +1,6 @@
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=uHgl1Oc4ZIKuJbq_exgSVsQ3ClG3nueQCLRQU67fBAs,72472
3
+ iqm/pulse/builder.py,sha256=rpVTX6Ru67SIlpjc9FTDEUcU0qYJKKZkPpUASj-7nPM,73510
4
4
  iqm/pulse/circuit_operations.py,sha256=i-sUx-JZOKAMKWgYq1c5KIR2Ons9Uyq_meEmppAmwI8,22387
5
5
  iqm/pulse/gate_implementation.py,sha256=yyj2d1aDL0K_qouo0qLHnZ49_1qHsJEShKW14398t8Q,39546
6
6
  iqm/pulse/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -9,16 +9,16 @@ iqm/pulse/scheduler.py,sha256=62gH3AcxmsgmShcmtNaiCqFLRl1Wll6Q24zIkhw_-XM,22768
9
9
  iqm/pulse/timebox.py,sha256=Yi8I43KiSPs2s_l1r0rMVm4mw0EpSCrpq3BCUNk5vyE,16618
10
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=Hd0mKWw55afvgOhDD_dgaPNCLZR6LSUoywwjBgB5zd4,8719
12
+ iqm/pulse/gates/__init__.py,sha256=l07iD52FiKKQyVFwswRapZMqEE0XfILSc4aZ5sXGJo4,8811
13
13
  iqm/pulse/gates/barrier.py,sha256=WhYV70lf4lh4Wa9UZuMk2lp9JbUQIu8lzewRC2P7pNE,2546
14
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
15
+ iqm/pulse/gates/cz.py,sha256=72KO8OINZRVOye4g4quHAEzwNplOlIZE0hU1IjE8Gfk,33366
16
+ iqm/pulse/gates/default_gates.py,sha256=TcJjg07ThQVDgH-MMZTacaykBZM82k2cLUXE3gG-LGE,7413
17
17
  iqm/pulse/gates/delay.py,sha256=nST9dY2JFp_mpKhiSfsYa5yL4hFKcNJSAyCzXjhauQg,3767
18
18
  iqm/pulse/gates/enums.py,sha256=ATwb6vZYpfgQ1gQyFPW53JyIKrdAP3FPHm6jV-t9OAk,2532
19
19
  iqm/pulse/gates/flux_multiplexer.py,sha256=sk84ItEvkx7Z0pCHt8MCCczBe7_BHNvqS4_oeNghZw8,9757
20
20
  iqm/pulse/gates/measure.py,sha256=5yG5dQeDljZewrPdy6vwejz5m3KU-9SlXQco9oAyssw,44488
21
- iqm/pulse/gates/move.py,sha256=L8wDcc7He43sUrwQs24lNydxdM9bKN27zlEdnDucDfc,17490
21
+ iqm/pulse/gates/move.py,sha256=siy9SBIFohknYV6zEJuUZv03cDEOiS53p9jFYm-5WAA,17470
22
22
  iqm/pulse/gates/prx.py,sha256=gWT4vEL6BZvRNOkwlxSg_V8nq7baFgh2K-V8q3bqLjI,25203
23
23
  iqm/pulse/gates/reset.py,sha256=idsknRq_z2JKIq4zXc4LvdE2czE5-KgGTdLPmYVyWIg,7480
24
24
  iqm/pulse/gates/rz.py,sha256=kr8Y5ap1MN81InCSmzGkxejvUcNszl1sxpG7yacvDM0,9684
@@ -31,7 +31,7 @@ iqm/pulse/playlist/hd_drag.py,sha256=JYRy3aPu0LD9y0H3UncOiWI39eV9-TARvgLonNUsaO0
31
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
- iqm/pulse/playlist/waveforms.py,sha256=Id6SIWVMXFrpUVbIec5Wun3vD-5AC_dPh9t3s2bY66M,18813
34
+ iqm/pulse/playlist/waveforms.py,sha256=NeXypwTM3SmXjdI57OzFQfvpcWBc4eFeG2WWT-zxRAc,22553
35
35
  iqm/pulse/playlist/visualisation/__init__.py,sha256=wCNfJHIR_bqG3ZBlgm55v90Rih7VCpfctoIMfwRMgjk,567
36
36
  iqm/pulse/playlist/visualisation/base.py,sha256=svigM5LRvU4eLLE4dhkKA_ZY0NyDBsva7tHnYCvq0I4,8892
37
37
  iqm/pulse/playlist/visualisation/templates/playlist_inspection.jinja2,sha256=4XPmpISH5LLOf3YjmK3OKfvkAWj4TlzD2K4ClFTvZhI,11511
@@ -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-10.2.0.dist-info/LICENSE.txt,sha256=R6Q7eUrLyoCQgWYorQ8WJmVmWKYU3dxA3jYUp0wwQAw,11332
43
- iqm_pulse-10.2.0.dist-info/METADATA,sha256=Reav1wwhurtRr9lN2bLmXK0WHeGFZQ6yVH_Rw2oz-R4,14551
44
- iqm_pulse-10.2.0.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
45
- iqm_pulse-10.2.0.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
46
- iqm_pulse-10.2.0.dist-info/RECORD,,
42
+ iqm_pulse-10.4.0.dist-info/LICENSE.txt,sha256=R6Q7eUrLyoCQgWYorQ8WJmVmWKYU3dxA3jYUp0wwQAw,11332
43
+ iqm_pulse-10.4.0.dist-info/METADATA,sha256=-M5Kzh6QrsvqbMdbNZSIwt3rfAYZGYeQvvCFGSRsaMQ,14551
44
+ iqm_pulse-10.4.0.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
45
+ iqm_pulse-10.4.0.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
46
+ iqm_pulse-10.4.0.dist-info/RECORD,,