iqm-pulse 12.5.0__tar.gz → 12.7.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 (86) hide show
  1. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/CHANGELOG.rst +32 -8
  2. iqm_pulse-12.7.0/MANIFEST.in +4 -0
  3. {iqm_pulse-12.5.0/src/iqm_pulse.egg-info → iqm_pulse-12.7.0}/PKG-INFO +3 -4
  4. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/README.rst +1 -1
  5. iqm_pulse-12.7.0/docs/changelog.rst +8 -0
  6. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/pyproject.toml +4 -5
  7. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/requirements/base.txt +2 -2
  8. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/builder.py +22 -20
  9. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gate_implementation.py +1 -1
  10. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/__init__.py +7 -1
  11. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/conditional.py +32 -13
  12. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/default_gates.py +6 -1
  13. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/measure.py +135 -17
  14. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/instructions.py +121 -0
  15. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/waveforms.py +3 -6
  16. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/timebox.py +63 -2
  17. iqm_pulse-12.7.0/src/iqm/pulse/utils.py +195 -0
  18. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0/src/iqm_pulse.egg-info}/PKG-INFO +3 -4
  19. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm_pulse.egg-info/SOURCES.txt +1 -0
  20. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm_pulse.egg-info/requires.txt +0 -1
  21. iqm_pulse-12.7.0/tests/__init__.py +0 -0
  22. iqm_pulse-12.7.0/version.txt +1 -0
  23. iqm_pulse-12.5.0/MANIFEST.in +0 -10
  24. iqm_pulse-12.5.0/docs/changelog.rst +0 -2
  25. iqm_pulse-12.5.0/src/iqm/pulse/utils.py +0 -109
  26. iqm_pulse-12.5.0/version.txt +0 -1
  27. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/LICENSE.txt +0 -0
  28. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/API.rst +0 -0
  29. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/Makefile +0 -0
  30. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/_static/.gitignore +0 -0
  31. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/_static/css/custom.css +0 -0
  32. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/_static/images/favicon.ico +0 -0
  33. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/_static/images/feedback_timing.svg +0 -0
  34. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/_static/images/logo.png +0 -0
  35. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/_static/images/playlist_breakdown.svg +0 -0
  36. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/_static/images/pulse_timing.svg +0 -0
  37. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/_static/images/readout_timing.svg +0 -0
  38. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/_templates/autosummary-class-template.rst +0 -0
  39. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/_templates/autosummary-module-template.rst +0 -0
  40. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/concepts.rst +0 -0
  41. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/conf.py +0 -0
  42. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/custom_gates.rst +0 -0
  43. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/index.rst +1 -1
  44. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/license.rst +0 -0
  45. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/pulse_timing.rst +0 -0
  46. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/references.bib +0 -0
  47. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/references.rst +0 -0
  48. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/docs/using_builder.rst +0 -0
  49. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/requirements/base.in +0 -0
  50. /iqm_pulse-12.5.0/src/iqm/pulse/py.typed → /iqm_pulse-12.7.0/requirements/base.in.internal +0 -0
  51. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/setup.cfg +0 -0
  52. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/setup.py +0 -0
  53. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/__init__.py +0 -0
  54. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/base_utils.py +0 -0
  55. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/circuit_operations.py +0 -0
  56. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/barrier.py +0 -0
  57. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/cz.py +0 -0
  58. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/delay.py +0 -0
  59. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/enums.py +0 -0
  60. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/flux_multiplexer.py +0 -0
  61. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/move.py +0 -0
  62. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/prx.py +0 -0
  63. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/reset.py +0 -0
  64. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/rz.py +0 -0
  65. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/sx.py +0 -0
  66. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/gates/u.py +0 -0
  67. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/__init__.py +0 -0
  68. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/channel.py +0 -0
  69. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/fast_drag.py +0 -0
  70. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/hd_drag.py +0 -0
  71. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/playlist.py +0 -0
  72. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/schedule.py +0 -0
  73. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/visualisation/__init__.py +0 -0
  74. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/visualisation/base.py +0 -0
  75. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/visualisation/templates/playlist_inspection.jinja2 +0 -0
  76. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/visualisation/templates/static/logo.png +0 -0
  77. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/visualisation/templates/static/moment.min.js +0 -0
  78. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.css +0 -0
  79. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.js +0 -0
  80. /iqm_pulse-12.5.0/tests/__init__.py → /iqm_pulse-12.7.0/src/iqm/pulse/py.typed +0 -0
  81. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/quantum_ops.py +0 -0
  82. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/scheduler.py +0 -0
  83. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm/pulse/validation.py +0 -0
  84. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm_pulse.egg-info/dependency_links.txt +0 -0
  85. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/src/iqm_pulse.egg-info/top_level.txt +0 -0
  86. {iqm_pulse-12.5.0 → iqm_pulse-12.7.0}/tests/.pylintrc +0 -0
@@ -1,6 +1,30 @@
1
- =========
2
- Changelog
3
- =========
1
+ Version 12.7.0 (2025-11-19)
2
+ ===========================
3
+
4
+ Features
5
+ --------
6
+
7
+ - Playlist generation now does acq discovery
8
+ - Add ``Fast_Measure_CustomWaveForms`` and ``Fast_Measure_Constant`` measure implementations that block the locus qubit
9
+ drive and flux channels only for the duration of the physical probe pulse (plus additional calibratable dead time to
10
+ account for e.g. ring-down delay).
11
+ - The implementation ``Fast_Measure_CustomWaveForms`` is simplified such that it no longer requires additional virtual
12
+ probe channels.
13
+
14
+ Bug fixes
15
+ ---------
16
+
17
+ - Fix `Fast_Measure_CustomWaveForms` not being scheduled correctly in some cases involving deep TimeBox structures.
18
+ - Fix `Fast_Measure_CustomWaveForms.time_trace` which was broken.
19
+
20
+ Version 12.6.0 (2025-10-23)
21
+ ===========================
22
+
23
+ Features
24
+ --------
25
+
26
+ - ``cc_prx`` gate now supports ``prx`` gate implementations that consists of multiple drive IQ pulses,
27
+ e.g. ones that use the ``sx`` gate. As a result, the ``reset`` operation now also works with them.
4
28
 
5
29
  Version 12.5.0 (2025-10-09)
6
30
  ===========================
@@ -336,7 +360,7 @@ Version 8.13.0 (2025-04-07)
336
360
  Features
337
361
  --------
338
362
 
339
- - Fix package version in published docs footers, :issue:`SW-1392`.
363
+ - Fix package version in published docs footers, :issue:`SW-1392`.
340
364
 
341
365
  Version 8.12.0 (2025-04-03)
342
366
  ===========================
@@ -369,7 +393,7 @@ Version 8.9.0 (2025-03-28)
369
393
  Features
370
394
  --------
371
395
 
372
- - Reworked the way default gates (operations) are defined so they are decoupled from their implementations. This separation allows for the deletion of default implementations without losing information about its designated name.
396
+ - Reworked the way default gates (operations) are defined so they are decoupled from their implementations. This separation allows for the deletion of default implementations without losing information about its designated name.
373
397
  - The majority of the original functionality stays the same.
374
398
  - The ``register_implementation`` function has been split into several different functions to improve readability and testing, as seen below::
375
399
 
@@ -381,8 +405,8 @@ Features
381
405
  v
382
406
  ``validate_implementation`` --> ``set_default``
383
407
 
384
- - The ``build_quantum_ops`` function in builder.py has been split into several functions as well.
385
- - Trying to modify the implementation class of an existing or default gate implementation yields an error.
408
+ - The ``build_quantum_ops`` function in builder.py has been split into several functions as well.
409
+ - Trying to modify the implementation class of an existing or default gate implementation yields an error.
386
410
 
387
411
 
388
412
 
@@ -1187,7 +1211,7 @@ Version 1.5 (2024-07-05)
1187
1211
 
1188
1212
  Features
1189
1213
  --------
1190
- - Bump exa-common to 25.3
1214
+ - Bump exa-common to 25.3
1191
1215
 
1192
1216
 
1193
1217
  Version 1.4 (2024-07-04)
@@ -0,0 +1,4 @@
1
+ include version.txt
2
+ include LICENSE.txt
3
+ prune tests/*
4
+ exclude .gitlab-ci.yml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulse
3
- Version: 12.5.0
3
+ Version: 12.7.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
@@ -204,7 +204,7 @@ License: Apache License
204
204
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
205
205
  See the License for the specific language governing permissions and
206
206
  limitations under the License.
207
- Project-URL: Documentation, https://iqm-finland.github.io/docs/iqm-pulse/
207
+ Project-URL: Documentation, https://docs.meetiqm.com/iqm-pulse/
208
208
  Project-URL: Homepage, https://pypi.org/project/iqm-pulse/
209
209
  Classifier: Development Status :: 4 - Beta
210
210
  Classifier: Programming Language :: Python :: 3 :: Only
@@ -213,7 +213,6 @@ Classifier: Intended Audience :: Science/Research
213
213
  Requires-Python: >=3.11
214
214
  Description-Content-Type: text/x-rst
215
215
  License-File: LICENSE.txt
216
- Requires-Dist: iqm-exa-common<28,>=27
217
216
  Requires-Dist: iqm-data-definitions<3.0,>=2.18
218
217
  Requires-Dist: python-rapidjson==1.20
219
218
  Requires-Dist: jinja2==3.0.3
@@ -226,7 +225,7 @@ IQM Pulse library
226
225
 
227
226
  A Python-based project for providing interface and implementations for control pulses.
228
227
 
229
- See `API documentation <https://iqm-finland.github.io/docs/iqm-pulse/>`_.
228
+ See `API documentation <https://docs.meetiqm.com/iqm-pulse/>`_.
230
229
 
231
230
  Copyright
232
231
  ---------
@@ -3,7 +3,7 @@ IQM Pulse library
3
3
 
4
4
  A Python-based project for providing interface and implementations for control pulses.
5
5
 
6
- See `API documentation <https://iqm-finland.github.io/docs/iqm-pulse/>`_.
6
+ See `API documentation <https://docs.meetiqm.com/iqm-pulse/>`_.
7
7
 
8
8
  Copyright
9
9
  ---------
@@ -0,0 +1,8 @@
1
+ .. _changelog:
2
+
3
+ =========
4
+ CHANGELOG
5
+ =========
6
+
7
+ .. include:: ../CHANGELOG.rst
8
+ :literal:
@@ -1,4 +1,3 @@
1
- # This file is managed by monotool.py, do not edit by hand
2
1
  [build-system]
3
2
  build-backend = "setuptools.build_meta"
4
3
  requires = [ "setuptools", "setuptools_scm[toml]",]
@@ -18,7 +17,7 @@ email = "info@meetiqm.com"
18
17
  file = "LICENSE.txt"
19
18
 
20
19
  [project.urls]
21
- Documentation = "https://iqm-finland.github.io/docs/iqm-pulse/"
20
+ Documentation = "https://docs.meetiqm.com/iqm-pulse/"
22
21
  Homepage = "https://pypi.org/project/iqm-pulse/"
23
22
 
24
23
  [tool.mypy]
@@ -57,7 +56,7 @@ nb_diff_ignore = [ "/metadata/language_info", "/metadata/widgets", "/cells/*/exe
57
56
 
58
57
  [tool.ruff.lint]
59
58
  ignore = [ "D203", "D213",]
60
- select = [ "E4", "E7", "E9", "E5", "F", "Q", "PL", "I", "D", "UP007", "UP006", "UP035", "ANN001", "ANN201", "ANN202",]
59
+ select = [ "E4", "E7", "E9", "E5", "F", "Q", "PL", "I", "D", "UP007", "UP006", "UP035", "ANN001", "ANN201", "ANN202", "NPY201",]
61
60
  unfixable = [ "F401",]
62
61
 
63
62
  [tool.ruff.lint.isort]
@@ -68,10 +67,10 @@ known-third-party = [ "exa", "iqm",]
68
67
  relative-imports-order = "closest-to-furthest"
69
68
 
70
69
  [tool.ruff.lint.per-file-ignores]
71
- "**/__init__.py" = [ "F401", "PLR0402",]
70
+ "**/__init__.py" = [ "D104", "F401", "PLR0402",]
72
71
  "**/docs/*" = [ "E402", "D100",]
73
72
  "**/setup.py" = [ "D100", "D103", "I001", "ANN201",]
74
- "**/src/*" = [ "PLR2004", "D400", "D415", "D205", "D401", "D417", "D100", "D101", "D107", "D102", "D105", "D103", "D404", "D104",]
73
+ "**/src/*" = [ "PLR2004", "D400", "D415", "D205", "D401", "D417", "D100", "D107", "D105", "D102", "D404",]
75
74
  "**/tests/*" = [ "F632", "PLR2004", "PLR0402", "PLC0414", "D", "ANN001", "ANN201", "ANN202",]
76
75
 
77
76
  [tool.ruff.lint.pylint]
@@ -1,7 +1,7 @@
1
- iqm-exa-common>=27,<28
2
1
  iqm-data-definitions >= 2.18, < 3.0
3
2
  python-rapidjson == 1.20
4
3
  jinja2 == 3.0.3
5
4
  numpy >= 1.26.4,<3.0
6
5
  scipy >= 1.11.4,<1.16
7
- scipy-stubs
6
+ scipy-stubs
7
+
@@ -15,8 +15,7 @@
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
- from collections import defaultdict
19
- from collections.abc import Iterable
18
+ from collections.abc import Iterable, Sequence
20
19
  import copy
21
20
  from dataclasses import dataclass, field, replace
22
21
  import itertools
@@ -56,6 +55,7 @@ from iqm.pulse.playlist.instructions import (
56
55
  Instruction,
57
56
  IQPulse,
58
57
  MultiplexedIQPulse,
58
+ ReadoutMetrics,
59
59
  ReadoutTrigger,
60
60
  RealPulse,
61
61
  ThresholdStateDiscrimination,
@@ -492,6 +492,10 @@ class ScheduleBuilder:
492
492
  """Probe line channel for the probe line ``component`` belongs to.
493
493
 
494
494
  See :meth:`.get_drive_channel`.
495
+
496
+ Args:
497
+ component: name of a QPU component (typically qubit) to probe
498
+
495
499
  """
496
500
  probe_line = self.chip_topology.component_to_probe_line.get(component, None)
497
501
  if probe_line is None:
@@ -1038,7 +1042,7 @@ class ScheduleBuilder:
1038
1042
 
1039
1043
  def timeboxes_to_front_padded_playlist(
1040
1044
  self, boxes: Iterable[TimeBox], *, neighborhood: int = 0
1041
- ) -> tuple[Playlist, dict[str, set[str]]]:
1045
+ ) -> tuple[Playlist, ReadoutMetrics]:
1042
1046
  """Temporary helper function, for converting a sequence of TimeBoxes to a Playlist.
1043
1047
 
1044
1048
  Each individual TimeBox in ``boxes`` is resolved into a Schedule, and then
@@ -1057,8 +1061,7 @@ class ScheduleBuilder:
1057
1061
  blocks only the defined locus components and any other components which have occupied channels.
1058
1062
 
1059
1063
  Returns:
1060
- playlist that implements ``boxes`` and the mapping from readout labels to the set of used measurement
1061
- implementations (of the format ``<QuantumOp name>.<impl name>``).
1064
+ playlist that implements ``boxes`` and the readout metrics for that playlist.
1062
1065
 
1063
1066
  """
1064
1067
  schedules = [self.resolve_timebox(box, neighborhood=neighborhood).cleanup() for box in boxes]
@@ -1102,7 +1105,7 @@ class ScheduleBuilder:
1102
1105
  playlist that implements ``boxes``
1103
1106
 
1104
1107
  """
1105
- return self.build_playlist(self.timebox_to_schedule(box, neighborhood=neighborhood) for box in boxes)[0]
1108
+ return self.build_playlist([self.timebox_to_schedule(box, neighborhood=neighborhood) for box in boxes])[0]
1106
1109
 
1107
1110
  def timebox_to_schedule(
1108
1111
  self,
@@ -1453,8 +1456,8 @@ class ScheduleBuilder:
1453
1456
  schedule.append(ch, Block(T))
1454
1457
 
1455
1458
  def build_playlist( # noqa: PLR0915
1456
- self, schedules: Iterable[Schedule], finish_schedules: bool = True
1457
- ) -> tuple[Playlist, dict[str, set[str]]]:
1459
+ self, schedules: Sequence[Schedule], finish_schedules: bool = True
1460
+ ) -> tuple[Playlist, ReadoutMetrics]:
1458
1461
  """Build a playlist from a number of instruction schedules.
1459
1462
 
1460
1463
  This involves compressing the schedules so that no duplicate information
@@ -1468,8 +1471,7 @@ class ScheduleBuilder:
1468
1471
  unless some process has already finalised them before calling this function.
1469
1472
 
1470
1473
  Returns:
1471
- playlist containing the schedules and the mapping from readout labels to the set of used measurement
1472
- implementations (of the format ``<QuantumOp name>.<impl name>``).
1474
+ playlist containing the schedules and the readout metrics for this playlist.
1473
1475
 
1474
1476
  Raises:
1475
1477
  ValueError: if the schedules contain channels with non-uniform sampling rates
@@ -1492,6 +1494,7 @@ class ScheduleBuilder:
1492
1494
 
1493
1495
  pl = Playlist()
1494
1496
  mapped_instructions: dict[str, dict[int | Instruction, Any]] = {}
1497
+ readout_metrics = ReadoutMetrics(num_segments=len(schedules))
1495
1498
 
1496
1499
  def _append_to_schedule(sc_schedule: SC_Schedule, channel_name: str, instr: Instruction) -> None:
1497
1500
  """Append ``instr`` to ``sc_schedule`` into the channel``channel_name``."""
@@ -1517,15 +1520,15 @@ class ScheduleBuilder:
1517
1520
 
1518
1521
  # add the schedules in the playlist
1519
1522
 
1520
- label_to_implementation: dict[str, set[str]] = defaultdict(set)
1521
-
1522
1523
  def _map_instruction(inst: Instruction) -> sc_instructions.Instruction:
1523
1524
  """TODO only necessary until SC has been updated to use the iqm.pulse Instruction class."""
1524
1525
  operation: Any
1525
1526
 
1526
1527
  def _map_acquisition(acq: AcquisitionMethod) -> sc_instructions.AcquisitionMethod:
1527
- if acq.implementation is not None:
1528
- label_to_implementation[acq.label].add(acq.implementation)
1528
+ if isinstance(acq, TimeTrace):
1529
+ return sc_instructions.TimeTrace(
1530
+ label=acq.label, delay_samples=acq.delay_samples, duration_samples=acq.duration_samples
1531
+ )
1529
1532
  if isinstance(acq, ThresholdStateDiscrimination):
1530
1533
  return sc_instructions.ThresholdStateDiscrimination(
1531
1534
  label=acq.label,
@@ -1540,10 +1543,7 @@ class ScheduleBuilder:
1540
1543
  delay_samples=acq.delay_samples,
1541
1544
  weights=_map_instruction(acq.weights).operation,
1542
1545
  )
1543
- if isinstance(acq, TimeTrace):
1544
- return sc_instructions.TimeTrace(
1545
- label=acq.label, delay_samples=acq.delay_samples, duration_samples=acq.duration_samples
1546
- )
1546
+
1547
1547
  raise ValueError(f"Unknown AcquisitionMethod {acq}")
1548
1548
 
1549
1549
  if isinstance(inst, Wait):
@@ -1587,7 +1587,7 @@ class ScheduleBuilder:
1587
1587
  return sc_instructions.Instruction(duration_samples=int(inst.duration), operation=operation)
1588
1588
 
1589
1589
  # NOTE that there is no implicit right-alignment or equal duration for schedules, unlike in old-style playlists!
1590
- for schedule in schedules:
1590
+ for seg_idx, schedule in enumerate(schedules):
1591
1591
  finished_schedule = self._finish_schedule(schedule) if finish_schedules else schedule
1592
1592
  sc_schedule = SC_Schedule()
1593
1593
  for channel_name, segment in finished_schedule.items():
@@ -1596,6 +1596,8 @@ class ScheduleBuilder:
1596
1596
  pl.add_channel(channel)
1597
1597
  prev_wait = None
1598
1598
  for instruction in segment:
1599
+ if isinstance(instruction, ReadoutTrigger):
1600
+ readout_metrics.extend(instruction, seg_idx)
1599
1601
  # convert all NONSOLID instructions into Waits
1600
1602
  if finish_schedules and (isinstance(instruction, NONSOLID) or isinstance(instruction, Wait)):
1601
1603
  if instruction.duration > 0:
@@ -1614,7 +1616,7 @@ class ScheduleBuilder:
1614
1616
  if prev_wait:
1615
1617
  _append_to_schedule(sc_schedule, channel_name, prev_wait)
1616
1618
  pl.segments.append(sc_schedule)
1617
- return pl, label_to_implementation
1619
+ return pl, readout_metrics
1618
1620
 
1619
1621
  def _set_gate_implementation_shortcut(self, op_name: str) -> None:
1620
1622
  """Create shortcut for `self.get_implementation(<op_name>, ...)` as `self.<op_name>(...)`.
@@ -602,7 +602,7 @@ class SinglePulseGate(GateImplementation):
602
602
  return self.builder.channels[self.channel].duration_to_seconds(self.pulse.duration)
603
603
 
604
604
 
605
- def init_subclass_composite(gate_class: type[CompositeGate]) -> None:
605
+ def init_subclass_composite(gate_class: type[CompositeGate]) -> None: # noqa: D103
606
606
  if not gate_class.registered_gates:
607
607
  # this would be pointless
608
608
  raise ValueError(f"CompositeGate {gate_class.__name__} has no registered gates.")
@@ -49,7 +49,12 @@ from iqm.pulse.gates.cz import (
49
49
  from iqm.pulse.gates.default_gates import _implementation_library, _quantum_ops_library
50
50
  from iqm.pulse.gates.delay import Delay
51
51
  from iqm.pulse.gates.flux_multiplexer import FluxMultiplexer_SampleLinear
52
- from iqm.pulse.gates.measure import Measure_Constant, Measure_Constant_Qnd, Shelved_Measure_Constant
52
+ from iqm.pulse.gates.measure import (
53
+ Fast_Measure_Constant,
54
+ Measure_Constant,
55
+ Measure_Constant_Qnd,
56
+ Shelved_Measure_Constant,
57
+ )
53
58
  from iqm.pulse.gates.move import MOVE_CRF_CRF, MOVE_SLEPIAN_CRF, MOVE_TGSS_CRF
54
59
  from iqm.pulse.gates.prx import (
55
60
  Constant_PRX_with_smooth_rise_fall,
@@ -105,6 +110,7 @@ _exposed_implementations: dict[str, type[GateImplementation]] = {
105
110
  FluxPulseGate_CRF_CRF,
106
111
  Measure_Constant,
107
112
  Measure_Constant_Qnd,
113
+ Fast_Measure_Constant,
108
114
  Shelved_Measure_Constant,
109
115
  PRX_ModulatedDRAGCosineRiseFall,
110
116
  MOVE_CRF_CRF,
@@ -18,10 +18,10 @@ import numpy as np
18
18
  from exa.common.data.parameter import CollectionType, Parameter
19
19
  from iqm.pulse.gate_implementation import CompositeGate
20
20
  from iqm.pulse.gates.measure import FEEDBACK_KEY
21
- from iqm.pulse.gates.prx import PRX_SinglePulse_GateImplementation
22
- from iqm.pulse.playlist.instructions import Block, ConditionalInstruction, Wait
21
+ from iqm.pulse.playlist.instructions import Block, ConditionalInstruction, IQPulse, Wait
23
22
  from iqm.pulse.playlist.schedule import Schedule
24
23
  from iqm.pulse.timebox import TimeBox
24
+ from iqm.pulse.utils import fuse_iq_pulses
25
25
 
26
26
 
27
27
  class CCPRX_Composite(CompositeGate):
@@ -30,6 +30,12 @@ class CCPRX_Composite(CompositeGate):
30
30
  Applies a PRX gate conditioned on a discriminated readout result obtained in the same segment (active feedback).
31
31
  Applies a PRX gate if the result is 1, and a Wait of equal duration if the result is 0.
32
32
  Uses the default implementation of PRX underneath, so no extra calibration is needed.
33
+
34
+ .. note::
35
+
36
+ Assumes that the PRX gate implementation used only consists of IQPulse instructions on the drive channel
37
+ of its locus qubit.
38
+
33
39
  """
34
40
 
35
41
  registered_gates = ("prx",)
@@ -70,30 +76,43 @@ class CCPRX_Composite(CompositeGate):
70
76
 
71
77
  """
72
78
  qubit = self.locus[0]
73
- awg_name = self.builder.get_drive_channel(qubit)
79
+ drive_channel = self.builder.get_drive_channel(qubit)
80
+ prx_gate = self.build("prx", self.locus)
81
+
82
+ # NOTE assumes that the PRX gate only has IQPulse instructions on drive_channel
83
+ timebox: TimeBox = prx_gate(angle, phase) # type: ignore[assignment]
84
+ if not timebox.atom:
85
+ raise RuntimeError("Received non-atomic PRX timebox.")
86
+ prx_instructions = timebox.atom[drive_channel]
87
+ iq_pulses = [inst for inst in prx_instructions if isinstance(inst, IQPulse)]
88
+ if len(iq_pulses) != len(prx_instructions):
89
+ raise RuntimeError(f"PRX drive channel has non-IQPulse instructions: {prx_instructions}")
90
+
91
+ if len(iq_pulses) > 1:
92
+ iq_pulse = fuse_iq_pulses(iq_pulses)
93
+ else:
94
+ iq_pulse = iq_pulses[0]
74
95
 
75
- prx_gate: PRX_SinglePulse_GateImplementation = self.build("prx", self.locus) # type: ignore[assignment]
76
- # FIXME assumes PRX gates only use this one implementation as the default,
77
- # with just a drive channel and a single IQPulse.
78
- pulse = prx_gate(angle, phase).atom[prx_gate.channel][0] # type: ignore[union-attr,index]
79
- wait = Wait(pulse.duration) # idling, can be replaced with a DD sequence later on
96
+ wait = Wait(iq_pulse.duration) # idling, can be replaced with a DD sequence later on
80
97
 
81
98
  # TODO: use the actual inputted label when the HW supports many labels per drive channel
82
99
  default_label = f"{feedback_qubit}__{FEEDBACK_KEY}"
83
- pulse_instruction = ConditionalInstruction(
84
- duration=pulse.duration, condition=default_label, outcomes=(wait, pulse)
100
+ conditional_instruction = ConditionalInstruction(
101
+ duration=iq_pulse.duration,
102
+ condition=default_label,
103
+ outcomes=(wait, iq_pulse),
85
104
  )
86
105
  delays = self.calibration_data["control_delays"]
87
106
  if len(delays) == 0:
88
107
  raise ValueError(f"'control_delays' for '{self.name}' on {qubit} is empty (not calibrated).")
89
108
 
90
- possible_sources = [c for c in self.builder.get_virtual_feedback_channels(qubit) if awg_name in c]
109
+ possible_sources = [c for c in self.builder.get_virtual_feedback_channels(qubit) if drive_channel in c]
91
110
  if len(delays) != len(possible_sources):
92
111
  raise ValueError(
93
112
  f"Not the correct amount of calibration values for 'control_delays'. Need {len(possible_sources)}"
94
113
  f"values, got {delays}."
95
114
  )
96
- virtual_channel_name = self.builder.get_virtual_feedback_channel_for(awg_name, feedback_qubit)
115
+ virtual_channel_name = self.builder.get_virtual_feedback_channel_for(drive_channel, feedback_qubit)
97
116
  delay = delays[possible_sources.index(virtual_channel_name)]
98
117
  virtual_channel = self.builder.channels[virtual_channel_name]
99
118
  delay_samples = virtual_channel.duration_to_int_samples(
@@ -106,7 +125,7 @@ class CCPRX_Composite(CompositeGate):
106
125
  )
107
126
  delay_box.neighborhood_components = {0: {virtual_channel_name}}
108
127
  cond = TimeBox.atomic(
109
- Schedule({virtual_channel_name: [Block(0)], prx_gate.channel: [pulse_instruction]}),
128
+ Schedule({virtual_channel_name: [Block(0)], drive_channel: [conditional_instruction]}),
110
129
  locus_components=[qubit],
111
130
  label=f"Conditional PRX for {qubit}",
112
131
  )
@@ -38,7 +38,11 @@ from iqm.pulse.gates.cz import (
38
38
  )
39
39
  from iqm.pulse.gates.delay import Delay
40
40
  from iqm.pulse.gates.flux_multiplexer import FluxMultiplexer_SampleLinear
41
- from iqm.pulse.gates.measure import Measure_Constant, Shelved_Measure_Constant
41
+ from iqm.pulse.gates.measure import (
42
+ Fast_Measure_Constant,
43
+ Measure_Constant,
44
+ Shelved_Measure_Constant,
45
+ )
42
46
  from iqm.pulse.gates.move import MOVE_CRF_CRF, MOVE_SLEPIAN_CRF, MOVE_TGSS_CRF
43
47
  from iqm.pulse.gates.prx import (
44
48
  PRX_DRAGCosineRiseFall,
@@ -68,6 +72,7 @@ _implementation_library: dict[str, dict[str, type[GateImplementation]]] = {
68
72
  "delay": {"wait": Delay},
69
73
  "measure": {
70
74
  "constant": Measure_Constant,
75
+ "fast_constant": Fast_Measure_Constant,
71
76
  },
72
77
  "measure_fidelity": {
73
78
  "constant": Measure_Constant,