iqm-pulse 12.6.1__tar.gz → 12.7.1__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.
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/CHANGELOG.rst +28 -10
- iqm_pulse-12.7.1/MANIFEST.in +4 -0
- {iqm_pulse-12.6.1/src/iqm_pulse.egg-info → iqm_pulse-12.7.1}/PKG-INFO +3 -4
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/README.rst +1 -1
- iqm_pulse-12.7.1/docs/changelog.rst +8 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/pyproject.toml +4 -5
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/requirements/base.txt +2 -2
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/builder.py +22 -20
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gate_implementation.py +1 -1
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/__init__.py +7 -1
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/default_gates.py +6 -1
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/measure.py +135 -17
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/instructions.py +121 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/timebox.py +63 -2
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1/src/iqm_pulse.egg-info}/PKG-INFO +3 -4
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm_pulse.egg-info/SOURCES.txt +1 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm_pulse.egg-info/requires.txt +0 -1
- iqm_pulse-12.7.1/tests/__init__.py +0 -0
- iqm_pulse-12.7.1/version.txt +1 -0
- iqm_pulse-12.6.1/MANIFEST.in +0 -10
- iqm_pulse-12.6.1/docs/changelog.rst +0 -2
- iqm_pulse-12.6.1/version.txt +0 -1
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/LICENSE.txt +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/API.rst +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/Makefile +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/_static/.gitignore +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/_static/css/custom.css +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/_static/images/favicon.ico +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/_static/images/feedback_timing.svg +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/_static/images/logo.png +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/_static/images/playlist_breakdown.svg +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/_static/images/pulse_timing.svg +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/_static/images/readout_timing.svg +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/_templates/autosummary-class-template.rst +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/_templates/autosummary-module-template.rst +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/concepts.rst +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/conf.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/custom_gates.rst +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/index.rst +1 -1
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/license.rst +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/pulse_timing.rst +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/references.bib +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/references.rst +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/docs/using_builder.rst +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/requirements/base.in +0 -0
- /iqm_pulse-12.6.1/src/iqm/pulse/py.typed → /iqm_pulse-12.7.1/requirements/base.in.internal +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/setup.cfg +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/setup.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/__init__.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/base_utils.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/circuit_operations.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/barrier.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/conditional.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/cz.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/delay.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/enums.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/flux_multiplexer.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/move.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/prx.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/reset.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/rz.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/sx.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/gates/u.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/__init__.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/channel.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/fast_drag.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/hd_drag.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/playlist.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/schedule.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/visualisation/__init__.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/visualisation/base.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/visualisation/templates/playlist_inspection.jinja2 +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/visualisation/templates/static/logo.png +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/visualisation/templates/static/moment.min.js +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.css +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.js +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/waveforms.py +0 -0
- /iqm_pulse-12.6.1/tests/__init__.py → /iqm_pulse-12.7.1/src/iqm/pulse/py.typed +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/quantum_ops.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/scheduler.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/utils.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/validation.py +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm_pulse.egg-info/dependency_links.txt +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm_pulse.egg-info/top_level.txt +0 -0
- {iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/tests/.pylintrc +0 -0
|
@@ -1,11 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
Version 12.7.1 (2025-12-02)
|
|
2
|
+
===========================
|
|
3
|
+
|
|
4
|
+
Bug fixes
|
|
5
|
+
---------
|
|
6
|
+
|
|
7
|
+
- Fix version ranges in python package dependencies.
|
|
4
8
|
|
|
5
|
-
Version 12.
|
|
9
|
+
Version 12.7.0 (2025-11-19)
|
|
6
10
|
===========================
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
Features
|
|
13
|
+
--------
|
|
14
|
+
|
|
15
|
+
- Playlist generation now does acq discovery
|
|
16
|
+
- Add ``Fast_Measure_CustomWaveForms`` and ``Fast_Measure_Constant`` measure implementations that block the locus qubit
|
|
17
|
+
drive and flux channels only for the duration of the physical probe pulse (plus additional calibratable dead time to
|
|
18
|
+
account for e.g. ring-down delay).
|
|
19
|
+
- The implementation ``Fast_Measure_CustomWaveForms`` is simplified such that it no longer requires additional virtual
|
|
20
|
+
probe channels.
|
|
21
|
+
|
|
22
|
+
Bug fixes
|
|
23
|
+
---------
|
|
24
|
+
|
|
25
|
+
- Fix `Fast_Measure_CustomWaveForms` not being scheduled correctly in some cases involving deep TimeBox structures.
|
|
26
|
+
- Fix `Fast_Measure_CustomWaveForms.time_trace` which was broken.
|
|
9
27
|
|
|
10
28
|
Version 12.6.0 (2025-10-23)
|
|
11
29
|
===========================
|
|
@@ -350,7 +368,7 @@ Version 8.13.0 (2025-04-07)
|
|
|
350
368
|
Features
|
|
351
369
|
--------
|
|
352
370
|
|
|
353
|
-
- Fix package version in published docs footers, :issue:`SW-1392`.
|
|
371
|
+
- Fix package version in published docs footers, :issue:`SW-1392`.
|
|
354
372
|
|
|
355
373
|
Version 8.12.0 (2025-04-03)
|
|
356
374
|
===========================
|
|
@@ -383,7 +401,7 @@ Version 8.9.0 (2025-03-28)
|
|
|
383
401
|
Features
|
|
384
402
|
--------
|
|
385
403
|
|
|
386
|
-
- 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.
|
|
404
|
+
- 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.
|
|
387
405
|
- The majority of the original functionality stays the same.
|
|
388
406
|
- The ``register_implementation`` function has been split into several different functions to improve readability and testing, as seen below::
|
|
389
407
|
|
|
@@ -395,8 +413,8 @@ Features
|
|
|
395
413
|
v
|
|
396
414
|
``validate_implementation`` --> ``set_default``
|
|
397
415
|
|
|
398
|
-
- The ``build_quantum_ops`` function in builder.py has been split into several functions as well.
|
|
399
|
-
- Trying to modify the implementation class of an existing or default gate implementation yields an error.
|
|
416
|
+
- The ``build_quantum_ops`` function in builder.py has been split into several functions as well.
|
|
417
|
+
- Trying to modify the implementation class of an existing or default gate implementation yields an error.
|
|
400
418
|
|
|
401
419
|
|
|
402
420
|
|
|
@@ -1201,7 +1219,7 @@ Version 1.5 (2024-07-05)
|
|
|
1201
1219
|
|
|
1202
1220
|
Features
|
|
1203
1221
|
--------
|
|
1204
|
-
- Bump exa-common to 25.3
|
|
1222
|
+
- Bump exa-common to 25.3
|
|
1205
1223
|
|
|
1206
1224
|
|
|
1207
1225
|
Version 1.4 (2024-07-04)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: iqm-pulse
|
|
3
|
-
Version: 12.
|
|
3
|
+
Version: 12.7.1
|
|
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://
|
|
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://
|
|
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://
|
|
6
|
+
See `API documentation <https://docs.meetiqm.com/iqm-pulse/>`_.
|
|
7
7
|
|
|
8
8
|
Copyright
|
|
9
9
|
---------
|
|
@@ -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://
|
|
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", "
|
|
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]
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
-
from collections import
|
|
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,
|
|
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
|
|
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:
|
|
1457
|
-
) -> tuple[Playlist,
|
|
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
|
|
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
|
|
1528
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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,
|
|
@@ -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
|
|
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,
|
|
@@ -32,6 +32,7 @@ from iqm.pulse.gate_implementation import (
|
|
|
32
32
|
Locus,
|
|
33
33
|
OILCalibrationData,
|
|
34
34
|
)
|
|
35
|
+
from iqm.pulse.playlist import Schedule, Segment
|
|
35
36
|
from iqm.pulse.playlist.channel import ProbeChannelProperties
|
|
36
37
|
from iqm.pulse.playlist.instructions import (
|
|
37
38
|
AcquisitionMethod,
|
|
@@ -44,7 +45,7 @@ from iqm.pulse.playlist.instructions import (
|
|
|
44
45
|
TimeTrace,
|
|
45
46
|
)
|
|
46
47
|
from iqm.pulse.playlist.waveforms import Constant, Samples
|
|
47
|
-
from iqm.pulse.timebox import MultiplexedProbeTimeBox, SchedulingStrategy, TimeBox
|
|
48
|
+
from iqm.pulse.timebox import MultiplexedProbeTimeBox, ProbeTimeBoxes, SchedulingStrategy, TimeBox
|
|
48
49
|
|
|
49
50
|
if TYPE_CHECKING: # pragma: no cover
|
|
50
51
|
from iqm.pulse.builder import ScheduleBuilder
|
|
@@ -53,6 +54,7 @@ if TYPE_CHECKING: # pragma: no cover
|
|
|
53
54
|
DEFAULT_INTEGRATION_KEY = "readout.result"
|
|
54
55
|
DEFAULT_TIME_TRACE_KEY = "readout.time_trace"
|
|
55
56
|
FEEDBACK_KEY = "feedback"
|
|
57
|
+
TIMING_TOLERANCE = 1e-12
|
|
56
58
|
|
|
57
59
|
|
|
58
60
|
class Measure_CustomWaveforms(CustomIQWaveforms):
|
|
@@ -301,16 +303,17 @@ class Measure_CustomWaveforms(CustomIQWaveforms):
|
|
|
301
303
|
for c in self.locus
|
|
302
304
|
]
|
|
303
305
|
probe_timebox = functools.reduce(lambda x, y: x + y, probe_timeboxes)
|
|
304
|
-
probe_timebox
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
306
|
+
if isinstance(probe_timebox, TimeBox): # FIXME: not needed once the measure implementations are cleaned up
|
|
307
|
+
probe_timebox.neighborhood_components[0] = copy(self._neighborhood_components)
|
|
308
|
+
if feedback_key:
|
|
309
|
+
# Block all the virtual channels from the probes involved in self.locus as we cannot know what AWG
|
|
310
|
+
# might be listening to the sent bits. NOTE: No Waits are added, the channels are just blocked in
|
|
311
|
+
# scheduling, so the impact to performance is negligible
|
|
312
|
+
probelines = {self.builder.chip_topology.component_to_probe_line[q] for q in self.locus}
|
|
313
|
+
for probe in probelines:
|
|
314
|
+
probe_timebox.neighborhood_components[0].update(
|
|
315
|
+
set(self.builder.get_virtual_feedback_channels(probe))
|
|
316
|
+
)
|
|
314
317
|
self._multiplexed_timeboxes[args] = probe_timebox
|
|
315
318
|
return self._multiplexed_timeboxes[args]
|
|
316
319
|
|
|
@@ -876,11 +879,6 @@ class ShelvedMeasureTimeBox(TimeBox):
|
|
|
876
879
|
return super().__radd__(other)
|
|
877
880
|
|
|
878
881
|
|
|
879
|
-
SHELVED_OFFSET_TOLERANCE = 1e-12
|
|
880
|
-
"""Tolerance for the absolute value of shelved measure ``second_prx_12_offset`` calibration value
|
|
881
|
-
being considered zero."""
|
|
882
|
-
|
|
883
|
-
|
|
884
882
|
class Shelved_Measure_CustomWaveforms(Measure_CustomWaveforms, CompositeGate):
|
|
885
883
|
"""Base class for shelved readout.
|
|
886
884
|
|
|
@@ -945,7 +943,7 @@ class Shelved_Measure_CustomWaveforms(Measure_CustomWaveforms, CompositeGate):
|
|
|
945
943
|
# schedule the shelved box to get an atomic schedule
|
|
946
944
|
shelved_atom = deepcopy(self.builder.resolve_timebox(shelved_box, neighborhood=0))
|
|
947
945
|
offset = self.calibration_data["second_prx_12_offset"]
|
|
948
|
-
if self.calibration_data["do_prx_12"] and abs(offset) >
|
|
946
|
+
if self.calibration_data["do_prx_12"] and abs(offset) > TIMING_TOLERANCE:
|
|
949
947
|
drive_channel_name = self.builder.get_drive_channel(self.locus[0])
|
|
950
948
|
drive_channel = self.builder.channels[drive_channel_name]
|
|
951
949
|
offset_sign = offset / abs(offset)
|
|
@@ -998,3 +996,123 @@ class Shelved_Measure_Constant(Shelved_Measure_CustomWaveforms, wave_i=Constant,
|
|
|
998
996
|
|
|
999
997
|
A measure gate implemented as a constant waveform is surrounded by two `prx_12` gates.
|
|
1000
998
|
"""
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
class Fast_Measure_CustomWaveforms(Measure_CustomWaveforms):
|
|
1002
|
+
"""Measure implementation that blocks locus qubits for a shorter duration than the probes.
|
|
1003
|
+
|
|
1004
|
+
The locus qubits are blocked only for the physical probe pulse duration plus (calibratable) extra dead time that
|
|
1005
|
+
can be used to take into account e.g. ring down delay of waiting the readout resonator to empty itself. The probe
|
|
1006
|
+
channels are still blocked as in ``Measure_CustomWaveforms``, i.e. for the duration of
|
|
1007
|
+
``acquisition_delay + integration_length + integration_dead_time``.
|
|
1008
|
+
"""
|
|
1009
|
+
|
|
1010
|
+
root_parameters = Measure_CustomWaveforms.root_parameters | {
|
|
1011
|
+
"locus_deadtime": Setting(
|
|
1012
|
+
Parameter("locus_deadtime", "Locus dead time after the probe pulse", unit="s"),
|
|
1013
|
+
0.0,
|
|
1014
|
+
),
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
def probe_timebox( # type: ignore[override]
|
|
1018
|
+
self, key: str = "", feedback_key: str = "", do_acquisition: bool = True, **kwargs
|
|
1019
|
+
) -> ProbeTimeBoxes:
|
|
1020
|
+
"""Otherwise the same as ``Measure_CustomWaveforms.probe_timebox``, but returns two TimeBoxes, the
|
|
1021
|
+
actual MultiplexedProbeTimeBox and the rest of the probe-blocking wait time in its own TimeBox. This
|
|
1022
|
+
allows the "tetris logic" in scheduling to block the locus qubits for a shorter duration.
|
|
1023
|
+
"""
|
|
1024
|
+
args = (key, feedback_key, do_acquisition)
|
|
1025
|
+
if args not in self._multiplexed_timeboxes:
|
|
1026
|
+
if len(self.locus) == 1:
|
|
1027
|
+
probe = self.builder.chip_topology.component_to_probe_line[self.locus[0]]
|
|
1028
|
+
probe_channel = self.builder.get_probe_channel(self.locus[0])
|
|
1029
|
+
try:
|
|
1030
|
+
drive_channel = self.builder.get_drive_channel(self.locus[0])
|
|
1031
|
+
except KeyError:
|
|
1032
|
+
drive_channel = ""
|
|
1033
|
+
|
|
1034
|
+
combined_probe_timebox = super().probe_timebox(
|
|
1035
|
+
key, feedback_key, do_acquisition=do_acquisition, **kwargs
|
|
1036
|
+
)
|
|
1037
|
+
readout_trigger = combined_probe_timebox.atom[probe_channel][0] # type: ignore[index]
|
|
1038
|
+
actual_probe_duration = readout_trigger.probe_pulse.duration
|
|
1039
|
+
|
|
1040
|
+
# MultiplexedProbeTimeBox that has the minimum possible duration
|
|
1041
|
+
if self.calibration_data["locus_deadtime"] > TIMING_TOLERANCE:
|
|
1042
|
+
deadtime = self.builder.channels[probe_channel].duration_to_int_samples(
|
|
1043
|
+
self.calibration_data["locus_deadtime"]
|
|
1044
|
+
)
|
|
1045
|
+
else:
|
|
1046
|
+
deadtime = 0
|
|
1047
|
+
# Must be: ReadoutTrigger.duration > ReadoutTrigger.probe_pulse.duration so if they would match, we
|
|
1048
|
+
# need to add a minimum offset to make it hold, i.e. the smallest granularity allowed by the probe
|
|
1049
|
+
# channel
|
|
1050
|
+
probe_granularity = self.builder.channels[probe_channel].instruction_duration_granularity
|
|
1051
|
+
offset = max(deadtime, probe_granularity)
|
|
1052
|
+
|
|
1053
|
+
truncated_readout_trigger = replace(readout_trigger, duration=actual_probe_duration + offset)
|
|
1054
|
+
physical_probe_box = MultiplexedProbeTimeBox.from_readout_trigger(
|
|
1055
|
+
truncated_readout_trigger,
|
|
1056
|
+
probe_channel,
|
|
1057
|
+
locus_components=self.locus,
|
|
1058
|
+
label=f"Physical probe box of {self.__class__.__name__} on {self.locus}",
|
|
1059
|
+
block_channels=[drive_channel] if drive_channel else [],
|
|
1060
|
+
block_duration=truncated_readout_trigger.duration if drive_channel else 0,
|
|
1061
|
+
)
|
|
1062
|
+
physical_probe_box.neighborhood_components[0] = combined_probe_timebox.neighborhood_components[0].copy()
|
|
1063
|
+
# extra Blocks (integration etc) for the probe channel
|
|
1064
|
+
extra_block_duration = max(combined_probe_timebox.atom.duration - truncated_readout_trigger.duration, 0) # type:ignore[union-attr]
|
|
1065
|
+
virtual_extra_wait_box = TimeBox.atomic(
|
|
1066
|
+
Schedule({probe_channel: Segment([Block(extra_block_duration)])}),
|
|
1067
|
+
locus_components=[probe],
|
|
1068
|
+
label=f"Virtual probe extra wait box of {self.__class__.__name__} on {self.locus}",
|
|
1069
|
+
)
|
|
1070
|
+
virtual_extra_wait_box.neighborhood_components[0] = {probe}
|
|
1071
|
+
final_boxes = ProbeTimeBoxes([physical_probe_box, virtual_extra_wait_box])
|
|
1072
|
+
else:
|
|
1073
|
+
# FIXME: all the return types in the measure impls need to be cleaned up
|
|
1074
|
+
final_boxes = super().probe_timebox(key, feedback_key, do_acquisition=do_acquisition, **kwargs) # type:ignore[assignment]
|
|
1075
|
+
if feedback_key:
|
|
1076
|
+
probelines = {self.builder.chip_topology.component_to_probe_line[q] for q in self.locus}
|
|
1077
|
+
for probe in probelines:
|
|
1078
|
+
final_boxes[1].neighborhood_components[0].update(
|
|
1079
|
+
set(self.builder.get_virtual_feedback_channels(probe))
|
|
1080
|
+
)
|
|
1081
|
+
self._multiplexed_timeboxes[args] = final_boxes # type:ignore[assignment]
|
|
1082
|
+
return final_boxes # type:ignore[return-value]
|
|
1083
|
+
return self._multiplexed_timeboxes[args] # type:ignore[return-value]
|
|
1084
|
+
|
|
1085
|
+
def _call(self, key: str = "", feedback_key: str = "") -> list[TimeBox]: # type:ignore[override]
|
|
1086
|
+
"""The same as ``Measure_CustomWaveforms._call``, i.e. wrap the "naked" multiplexable probe_timeboxes
|
|
1087
|
+
into a composite TimeBox.
|
|
1088
|
+
"""
|
|
1089
|
+
probe_box, wait_box = self.probe_timebox(key, feedback_key, do_acquisition=True)
|
|
1090
|
+
final_box = TimeBox.composite([probe_box], label=f"Readout on {self.locus}")
|
|
1091
|
+
final_box.neighborhood_components[0] = final_box.children[0].neighborhood_components[0]
|
|
1092
|
+
return [final_box, wait_box]
|
|
1093
|
+
|
|
1094
|
+
def _get_probe_timebox_for_time_trace(self, key: str = "", feedback_key: str = "") -> TimeBox:
|
|
1095
|
+
"""Get the probe TimeBox for TimeTrace. This is just a single MultiplexedProbeTimeBox which has the full
|
|
1096
|
+
probe duration (including integration). The faster logic wrt. qubit blocking is not important in the context of
|
|
1097
|
+
TimeTraces.
|
|
1098
|
+
"""
|
|
1099
|
+
# FIXME: not needed once we align the return types of all these measure gates
|
|
1100
|
+
# Then we can make this method also shorter for the locus qubits
|
|
1101
|
+
probe_boxes = self.probe_timebox(key, feedback_key, do_acquisition=True)
|
|
1102
|
+
total_box = TimeBox.composite(self.probe_timebox(key, feedback_key, do_acquisition=True))
|
|
1103
|
+
probe_schedule = self.builder.resolve_timebox(total_box, neighborhood=0)
|
|
1104
|
+
atomic_probe_box = MultiplexedProbeTimeBox.atomic(
|
|
1105
|
+
probe_schedule,
|
|
1106
|
+
label=f"Time Trace atomic probe box of {self.__class__.__name__} on {self.locus}",
|
|
1107
|
+
locus_components=probe_boxes[0].locus_components,
|
|
1108
|
+
)
|
|
1109
|
+
atomic_probe_box.neighborhood_components[0] = probe_boxes[0].neighborhood_components[0]
|
|
1110
|
+
return atomic_probe_box # type:ignore[return-value]
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
class Fast_Measure_Constant(Fast_Measure_CustomWaveforms, wave_i=Constant, wave_q=Constant): # type:ignore[call-arg]
|
|
1114
|
+
"""Implementation of a faster measure with constant i and q waveforms.
|
|
1115
|
+
|
|
1116
|
+
Does not block the drive and flux channels of the locus qubits during the integration, but just during the probe
|
|
1117
|
+
pulse and extra calibrated dead time after it.
|
|
1118
|
+
"""
|
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
from collections.abc import Callable
|
|
18
19
|
import dataclasses
|
|
19
20
|
from dataclasses import dataclass, field
|
|
20
21
|
import random
|
|
22
|
+
import re
|
|
21
23
|
|
|
22
24
|
from iqm.pulse.playlist.waveforms import Waveform
|
|
23
25
|
|
|
@@ -271,3 +273,122 @@ class ReadoutTrigger(Instruction):
|
|
|
271
273
|
),
|
|
272
274
|
acquisitions=self.acquisitions + other.acquisitions,
|
|
273
275
|
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@dataclass
|
|
279
|
+
class ReadoutMetrics:
|
|
280
|
+
"""Aggregates the necessary readout metrics needed for return data processing."""
|
|
281
|
+
|
|
282
|
+
num_segments: int
|
|
283
|
+
"""Number of segments in the Playlist this ReadoutMetrics object represents."""
|
|
284
|
+
integration_occurrences: dict[str, list[int]] = field(default_factory=dict)
|
|
285
|
+
"""Map each integration readout label (of the format ``"<component>__<readout key>"``) to its number of occurrences
|
|
286
|
+
in each Playlist segment."""
|
|
287
|
+
timetrace_occurrences: dict[str, list[int]] = field(default_factory=dict)
|
|
288
|
+
"""Map each time trace label (of the format ``"<component>__<readout key>"``) to its number of occurrences in each
|
|
289
|
+
Playlist segment."""
|
|
290
|
+
timetrace_lengths: dict[str, int] = field(default_factory=dict)
|
|
291
|
+
"""Map each time trace label (of the format ``"<component>__<readout key>"``) to its number of time trace samples.
|
|
292
|
+
"""
|
|
293
|
+
implementations: dict[str, set[str]] = field(default_factory=dict)
|
|
294
|
+
"""Map each integration or time trace readout label to its implementations (of the format
|
|
295
|
+
``"<measure gate name>.<implementation name>"``)."""
|
|
296
|
+
|
|
297
|
+
def extend(self, trigger: ReadoutTrigger, seg_idx: int) -> None:
|
|
298
|
+
"""Extend the metrics with a ReadoutTrigger in a given Playlist Segment.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
trigger: The readout trigger.
|
|
302
|
+
seg_idx: The index of the Playlist segment ``trigger`` belongs to.
|
|
303
|
+
|
|
304
|
+
"""
|
|
305
|
+
for acq in trigger.acquisitions:
|
|
306
|
+
if isinstance(acq, TimeTrace):
|
|
307
|
+
occurrences = self.timetrace_occurrences
|
|
308
|
+
if acq.label in self.timetrace_lengths:
|
|
309
|
+
if self.timetrace_lengths[acq.label] != acq.duration_samples:
|
|
310
|
+
raise ValueError(f"Time trace length mismatch in label {acq.label}")
|
|
311
|
+
else:
|
|
312
|
+
self.timetrace_lengths[acq.label] = acq.duration_samples
|
|
313
|
+
else:
|
|
314
|
+
occurrences = self.integration_occurrences
|
|
315
|
+
if acq.label not in occurrences:
|
|
316
|
+
occurrences[acq.label] = [0] * self.num_segments
|
|
317
|
+
occurrences[acq.label][seg_idx] += 1
|
|
318
|
+
if acq.implementation is not None:
|
|
319
|
+
if acq.label not in self.implementations:
|
|
320
|
+
self.implementations[acq.label] = set()
|
|
321
|
+
self.implementations[acq.label].add(acq.implementation)
|
|
322
|
+
|
|
323
|
+
def filter_out(
|
|
324
|
+
self,
|
|
325
|
+
labels: list[str] | None = None,
|
|
326
|
+
components: list[str] | None = None,
|
|
327
|
+
keys: list[str] | None = None,
|
|
328
|
+
) -> None:
|
|
329
|
+
"""Filter out labels from the contents in self.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
labels: The labels to filter out.
|
|
333
|
+
components: The components to filter out (all labels for these components will be removed).
|
|
334
|
+
keys: The keys to filter out (all components with these keys will be removed).
|
|
335
|
+
|
|
336
|
+
"""
|
|
337
|
+
labels = labels if labels else []
|
|
338
|
+
components = components if components else []
|
|
339
|
+
keys = keys if keys else []
|
|
340
|
+
for field_name in ["integration_occurrences", "timetrace_occurrences", "timetrace_lengths", "implementations"]:
|
|
341
|
+
field = getattr(self, field_name)
|
|
342
|
+
field_copy = field.copy()
|
|
343
|
+
for label, contents in field.items():
|
|
344
|
+
component, key = re.split("__[_]*", label)
|
|
345
|
+
if component in components or key in keys or label in labels:
|
|
346
|
+
del field_copy[label]
|
|
347
|
+
setattr(self, field_name, field_copy)
|
|
348
|
+
|
|
349
|
+
def get_labels(self, integration: bool = True, timetrace: bool = True) -> set[str]:
|
|
350
|
+
"""Get all the labels in self
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
integration: Whether to get integration labels.
|
|
354
|
+
timetrace: Whether to get time-trace labels.
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
The applicable labels in self.
|
|
358
|
+
|
|
359
|
+
"""
|
|
360
|
+
return self._get(lambda label: label, integration=integration, timetrace=timetrace)
|
|
361
|
+
|
|
362
|
+
def get_components(self, integration: bool = True, timetrace: bool = True) -> set[str]:
|
|
363
|
+
"""Get all components in the labels in self
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
integration: Whether to get components from the integration labels.
|
|
367
|
+
timetrace: Whether to get components from the time-trace labels.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
The applicable components in self.
|
|
371
|
+
|
|
372
|
+
"""
|
|
373
|
+
return self._get(lambda label: re.split("__[_]*", label)[0], integration=integration, timetrace=timetrace)
|
|
374
|
+
|
|
375
|
+
def get_keys(self, integration: bool = True, timetrace: bool = True) -> set[str]:
|
|
376
|
+
"""Get all readout keys in the labels in self
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
integration: Whether to get readout keys from the integration labels.
|
|
380
|
+
timetrace: Whether to get readout keys from the time-trace labels.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
The applicable readout keys in self.
|
|
384
|
+
|
|
385
|
+
"""
|
|
386
|
+
return self._get(lambda label: re.split("__[_]*", label)[1], integration=integration, timetrace=timetrace)
|
|
387
|
+
|
|
388
|
+
def _get(self, func: Callable, integration: bool = True, timetrace: bool = True) -> set[str]:
|
|
389
|
+
contents = []
|
|
390
|
+
if integration:
|
|
391
|
+
contents += [func(label) for label in self.integration_occurrences]
|
|
392
|
+
if timetrace:
|
|
393
|
+
contents += [func(label) for label in self.timetrace_occurrences]
|
|
394
|
+
return set(contents)
|
|
@@ -122,8 +122,9 @@ class TimeBox:
|
|
|
122
122
|
this ``TimeBox`` for any reason.
|
|
123
123
|
"""
|
|
124
124
|
|
|
125
|
-
@
|
|
125
|
+
@classmethod
|
|
126
126
|
def composite(
|
|
127
|
+
cls,
|
|
127
128
|
boxes: Iterable[TimeBox | Iterable[TimeBox]],
|
|
128
129
|
*,
|
|
129
130
|
label: str = "",
|
|
@@ -155,7 +156,7 @@ class TimeBox:
|
|
|
155
156
|
locus_components = set.union(*(box.locus_components for box in children))
|
|
156
157
|
else:
|
|
157
158
|
locus_components = set()
|
|
158
|
-
return
|
|
159
|
+
return cls(
|
|
159
160
|
label=label,
|
|
160
161
|
locus_components=locus_components,
|
|
161
162
|
atom=None,
|
|
@@ -363,6 +364,8 @@ class MultiplexedProbeTimeBox(TimeBox):
|
|
|
363
364
|
scheduling_algorithm=self.scheduling_algorithm,
|
|
364
365
|
neighborhood_components=neighborhood_components,
|
|
365
366
|
)
|
|
367
|
+
if isinstance(other, ProbeTimeBoxes):
|
|
368
|
+
return self + other[0]
|
|
366
369
|
return super().__add__(other)
|
|
367
370
|
|
|
368
371
|
@staticmethod
|
|
@@ -401,3 +404,61 @@ class MultiplexedProbeTimeBox(TimeBox):
|
|
|
401
404
|
scheduling_algorithm=SchedulingAlgorithm.HARD_BOUNDARY,
|
|
402
405
|
)
|
|
403
406
|
return box
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class ProbeTimeBoxes(list):
|
|
410
|
+
"""Allows multiplexing lists of probe TimeBoxes with each other and ``MultiplexedProbeTimeBox``es.
|
|
411
|
+
|
|
412
|
+
The first element is a ``MultiplexedProbeTimeBox`` and the second an atomic ``TimeBox`` containing ``Block``
|
|
413
|
+
instructions on probe channels (corresponding to e.g. the integration time).
|
|
414
|
+
|
|
415
|
+
If ``A`` is a ``ProbeTimeBoxes`` and ``B`` is a ``ProbeTimeBoxes``, then ``A+B`` is also a ProbeTimeBoxes instance
|
|
416
|
+
with the timings adjusted correctly and the ``MultiplexedProbeTimeBox``es in each multiplexed together.
|
|
417
|
+
If ``A`` is a ``ProbeTimeBoxes`` and ``B`` is a ``MultiplexedProbeTimeBox`` where the probe instructions are
|
|
418
|
+
multiplexed together and the duration is ``max(A[0].duration, B.duration)`` (the addition is symmetric).
|
|
419
|
+
Otherwise (if ``B`` is a list of ``Timebox``es or a non-probe ``TimeBox``), ``A+B`` works like
|
|
420
|
+
``TimeBox.__add__``.
|
|
421
|
+
"""
|
|
422
|
+
|
|
423
|
+
# FIXME: does not yet work with ShelvedProbeTimeBox
|
|
424
|
+
# All the measure gates should probably eventually return lists of TimeBoxes (that's the only way to "tetris"
|
|
425
|
+
# schedule stuff) and we should extend that to cover all the use cases, including Shelved.
|
|
426
|
+
|
|
427
|
+
def __init__(self, timeboxes: list[TimeBox]):
|
|
428
|
+
super().__init__(timeboxes)
|
|
429
|
+
if len(self) != 2:
|
|
430
|
+
raise ValueError(
|
|
431
|
+
"ProbeTimeBoxes instances must have exactly two elements, the first being the "
|
|
432
|
+
"MultiplexedProbeTimebox of the physical probe channels and the second the "
|
|
433
|
+
" extra Blocks in the probe channels."
|
|
434
|
+
)
|
|
435
|
+
if not isinstance(self[0], TimeBox) or self[0].atom is None or not isinstance(self[0], MultiplexedProbeTimeBox):
|
|
436
|
+
raise ValueError("The first child must be an atomic MultiplexedProbeTimeBox.")
|
|
437
|
+
if not isinstance(self[1], TimeBox) or self[1].atom is None:
|
|
438
|
+
raise ValueError(
|
|
439
|
+
"The second child must be an atomic TimeBox containing the extra Waits in the probe channels."
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
def __add__(self, other: TimeBox | list[TimeBox]) -> TimeBox | list[TimeBox]: # type: ignore[override]
|
|
443
|
+
"""Multiplex ``self`` with other ``ProbeTimeBoxes`` instances or with ``MultiplexedProbeTimeBox`` instances."""
|
|
444
|
+
if isinstance(other, ProbeTimeBoxes):
|
|
445
|
+
duration_self = sum(child.atom.duration for child in self)
|
|
446
|
+
duration_probe_self = self[0].atom.duration
|
|
447
|
+
duration_other = sum(child.atom.duration for child in other)
|
|
448
|
+
duration_probe_other = other[0].atom.duration
|
|
449
|
+
extra_wait_duration = max(duration_self, duration_other) - max(duration_probe_self, duration_probe_other)
|
|
450
|
+
extra_wait_channels = [*self[1].atom.channels(), *other[1].atom.channels()]
|
|
451
|
+
probe_box = self[0] + other[0]
|
|
452
|
+
extra_wait_box = TimeBox.atomic(
|
|
453
|
+
Schedule({ch: Segment([Block(extra_wait_duration)]) for ch in extra_wait_channels}),
|
|
454
|
+
locus_components=set(self[1].locus_components).union(other[1].locus_components),
|
|
455
|
+
label="Virtual probe channel Block",
|
|
456
|
+
)
|
|
457
|
+
if 0 in self[1].neighborhood_components and 0 in other[1].neighborhood_components:
|
|
458
|
+
extra_wait_box.neighborhood_components[0] = (
|
|
459
|
+
self[1].neighborhood_components[0].union(other[1].neighborhood_components[0])
|
|
460
|
+
)
|
|
461
|
+
return ProbeTimeBoxes([probe_box, extra_wait_box])
|
|
462
|
+
if isinstance(other, MultiplexedProbeTimeBox):
|
|
463
|
+
return self[0] + other
|
|
464
|
+
return list(self) + other
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: iqm-pulse
|
|
3
|
-
Version: 12.
|
|
3
|
+
Version: 12.7.1
|
|
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://
|
|
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://
|
|
228
|
+
See `API documentation <https://docs.meetiqm.com/iqm-pulse/>`_.
|
|
230
229
|
|
|
231
230
|
Copyright
|
|
232
231
|
---------
|
|
@@ -28,6 +28,7 @@ docs/_static/images/readout_timing.svg
|
|
|
28
28
|
docs/_templates/autosummary-class-template.rst
|
|
29
29
|
docs/_templates/autosummary-module-template.rst
|
|
30
30
|
requirements/base.in
|
|
31
|
+
requirements/base.in.internal
|
|
31
32
|
requirements/base.txt
|
|
32
33
|
src/iqm/pulse/__init__.py
|
|
33
34
|
src/iqm/pulse/base_utils.py
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
12.7.1
|
iqm_pulse-12.6.1/MANIFEST.in
DELETED
iqm_pulse-12.6.1/version.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
12.6.1
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iqm_pulse-12.6.1 → iqm_pulse-12.7.1}/src/iqm/pulse/playlist/visualisation/templates/static/logo.png
RENAMED
|
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
|