iqm-pulse 10.2.0__tar.gz → 10.3.0__tar.gz

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.
Files changed (82) hide show
  1. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/CHANGELOG.rst +9 -0
  2. {iqm_pulse-10.2.0/src/iqm_pulse.egg-info → iqm_pulse-10.3.0}/PKG-INFO +1 -1
  3. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/__init__.py +2 -0
  4. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/cz.py +308 -5
  5. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/move.py +2 -2
  6. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/waveforms.py +92 -0
  7. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0/src/iqm_pulse.egg-info}/PKG-INFO +1 -1
  8. iqm_pulse-10.3.0/version.txt +1 -0
  9. iqm_pulse-10.2.0/version.txt +0 -1
  10. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/LICENSE.txt +0 -0
  11. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/MANIFEST.in +0 -0
  12. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/README.rst +0 -0
  13. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/API.rst +0 -0
  14. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/Makefile +0 -0
  15. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/_static/.gitignore +0 -0
  16. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/_static/css/custom.css +0 -0
  17. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/_static/images/favicon.ico +0 -0
  18. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/_static/images/feedback_timing.svg +0 -0
  19. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/_static/images/logo.png +0 -0
  20. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/_static/images/playlist_breakdown.svg +0 -0
  21. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/_static/images/pulse_timing.svg +0 -0
  22. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/_static/images/readout_timing.svg +0 -0
  23. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/_templates/autosummary-class-template.rst +0 -0
  24. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/_templates/autosummary-module-template.rst +0 -0
  25. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/changelog.rst +0 -0
  26. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/concepts.rst +0 -0
  27. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/conf.py +0 -0
  28. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/custom_gates.rst +0 -0
  29. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/index.rst +0 -0
  30. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/license.rst +0 -0
  31. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/pulse_timing.rst +0 -0
  32. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/references.bib +0 -0
  33. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/references.rst +0 -0
  34. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/docs/using_builder.rst +0 -0
  35. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/pyproject.toml +0 -0
  36. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/requirements/base.in +0 -0
  37. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/requirements/base.txt +0 -0
  38. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/setup.cfg +0 -0
  39. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/setup.py +0 -0
  40. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/__init__.py +0 -0
  41. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/base_utils.py +0 -0
  42. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/builder.py +0 -0
  43. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/circuit_operations.py +0 -0
  44. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gate_implementation.py +0 -0
  45. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/barrier.py +0 -0
  46. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/conditional.py +0 -0
  47. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/default_gates.py +0 -0
  48. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/delay.py +0 -0
  49. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/enums.py +0 -0
  50. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/flux_multiplexer.py +0 -0
  51. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/measure.py +0 -0
  52. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/prx.py +0 -0
  53. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/reset.py +0 -0
  54. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/rz.py +0 -0
  55. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/sx.py +0 -0
  56. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/gates/u.py +0 -0
  57. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/__init__.py +0 -0
  58. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/channel.py +0 -0
  59. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/fast_drag.py +0 -0
  60. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/hd_drag.py +0 -0
  61. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/instructions.py +0 -0
  62. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/playlist.py +0 -0
  63. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/schedule.py +0 -0
  64. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/visualisation/__init__.py +0 -0
  65. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/visualisation/base.py +0 -0
  66. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/visualisation/templates/playlist_inspection.jinja2 +0 -0
  67. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/visualisation/templates/static/logo.png +0 -0
  68. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/visualisation/templates/static/moment.min.js +0 -0
  69. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.css +0 -0
  70. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.js +0 -0
  71. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/py.typed +0 -0
  72. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/quantum_ops.py +0 -0
  73. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/scheduler.py +0 -0
  74. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/timebox.py +0 -0
  75. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/utils.py +0 -0
  76. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm/pulse/validation.py +0 -0
  77. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm_pulse.egg-info/SOURCES.txt +0 -0
  78. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm_pulse.egg-info/dependency_links.txt +0 -0
  79. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm_pulse.egg-info/requires.txt +0 -0
  80. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/src/iqm_pulse.egg-info/top_level.txt +0 -0
  81. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/tests/.pylintrc +0 -0
  82. {iqm_pulse-10.2.0 → iqm_pulse-10.3.0}/tests/__init__.py +0 -0
@@ -2,6 +2,15 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ Version 10.3.0 (2025-08-11)
6
+ ===========================
7
+
8
+ Feature
9
+ -------
10
+
11
+ - Added ``FluxPulse_SmoothConstant_SmoothConstant `` which automatically splits pulses into three parts: rise, Constant, and fall.
12
+ - Changed Flux pulse argument ``parameter`` type to remove ignores all over the code.
13
+
5
14
  Version 10.2.0 (2025-08-08)
6
15
  ===========================
7
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulse
3
- Version: 10.2.0
3
+ Version: 10.3.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
@@ -42,6 +42,7 @@ 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
  )
@@ -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,
@@ -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."""
@@ -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.3.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
@@ -0,0 +1 @@
1
+ 10.3.0
@@ -1 +0,0 @@
1
- 10.2.0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes