iqm-pulse 9.15.0__tar.gz → 9.17.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 (81) hide show
  1. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/CHANGELOG.rst +16 -0
  2. {iqm_pulse-9.15.0/src/iqm_pulse.egg-info → iqm_pulse-9.17.0}/PKG-INFO +1 -1
  3. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/builder.py +65 -78
  4. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gate_implementation.py +11 -15
  5. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/cz.py +3 -2
  6. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/measure.py +3 -4
  7. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/prx.py +27 -30
  8. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/rz.py +5 -5
  9. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/hd_drag.py +3 -3
  10. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/scheduler.py +3 -3
  11. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0/src/iqm_pulse.egg-info}/PKG-INFO +1 -1
  12. iqm_pulse-9.17.0/version.txt +1 -0
  13. iqm_pulse-9.15.0/version.txt +0 -1
  14. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/LICENSE.txt +0 -0
  15. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/MANIFEST.in +0 -0
  16. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/README.rst +0 -0
  17. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/API.rst +0 -0
  18. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/Makefile +0 -0
  19. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/_static/.gitignore +0 -0
  20. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/_static/css/custom.css +0 -0
  21. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/_static/images/favicon.ico +0 -0
  22. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/_static/images/feedback_timing.svg +0 -0
  23. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/_static/images/logo.png +0 -0
  24. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/_static/images/playlist_breakdown.svg +0 -0
  25. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/_static/images/pulse_timing.svg +0 -0
  26. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/_static/images/readout_timing.svg +0 -0
  27. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/_templates/autosummary-class-template.rst +0 -0
  28. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/_templates/autosummary-module-template.rst +0 -0
  29. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/changelog.rst +0 -0
  30. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/concepts.rst +0 -0
  31. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/conf.py +0 -0
  32. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/custom_gates.rst +0 -0
  33. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/index.rst +0 -0
  34. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/license.rst +0 -0
  35. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/pulse_timing.rst +0 -0
  36. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/references.bib +0 -0
  37. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/references.rst +0 -0
  38. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/docs/using_builder.rst +0 -0
  39. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/pyproject.toml +0 -0
  40. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/requirements/base.in +0 -0
  41. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/requirements/base.txt +0 -0
  42. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/setup.cfg +0 -0
  43. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/setup.py +0 -0
  44. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/__init__.py +0 -0
  45. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/base_utils.py +0 -0
  46. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/circuit_operations.py +0 -0
  47. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/__init__.py +0 -0
  48. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/barrier.py +0 -0
  49. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/conditional.py +0 -0
  50. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/default_gates.py +0 -0
  51. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/delay.py +0 -0
  52. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/enums.py +0 -0
  53. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/flux_multiplexer.py +0 -0
  54. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/move.py +0 -0
  55. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/reset.py +0 -0
  56. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/sx.py +0 -0
  57. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/gates/u.py +0 -0
  58. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/__init__.py +0 -0
  59. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/channel.py +0 -0
  60. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/fast_drag.py +0 -0
  61. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/instructions.py +0 -0
  62. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/playlist.py +0 -0
  63. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/schedule.py +0 -0
  64. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/visualisation/__init__.py +0 -0
  65. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/visualisation/base.py +0 -0
  66. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/visualisation/templates/playlist_inspection.jinja2 +0 -0
  67. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/visualisation/templates/static/logo.png +0 -0
  68. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/visualisation/templates/static/moment.min.js +0 -0
  69. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.css +0 -0
  70. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/visualisation/templates/static/vis-timeline-graph2d.min.js +0 -0
  71. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/playlist/waveforms.py +0 -0
  72. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/quantum_ops.py +0 -0
  73. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/timebox.py +0 -0
  74. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/utils.py +0 -0
  75. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm/pulse/validation.py +0 -0
  76. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm_pulse.egg-info/SOURCES.txt +0 -0
  77. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm_pulse.egg-info/dependency_links.txt +0 -0
  78. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm_pulse.egg-info/requires.txt +0 -0
  79. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/src/iqm_pulse.egg-info/top_level.txt +0 -0
  80. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/tests/.pylintrc +0 -0
  81. {iqm_pulse-9.15.0 → iqm_pulse-9.17.0}/tests/__init__.py +0 -0
@@ -2,6 +2,22 @@
2
2
  Changelog
3
3
  =========
4
4
 
5
+ Version 9.17.0 (2025-07-01)
6
+ ===========================
7
+
8
+ Features
9
+ --------
10
+
11
+ - Faster playlist creation in ScheduleBuilder
12
+
13
+ Version 9.16.0 (2025-07-01)
14
+ ===========================
15
+
16
+ Bug fixes
17
+ ---------
18
+
19
+ - Fix type errors raised by mypy.
20
+
5
21
  Version 9.15.0 (2025-06-17)
6
22
  ===========================
7
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulse
3
- Version: 9.15.0
3
+ Version: 9.17.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
@@ -18,7 +18,6 @@ from __future__ import annotations
18
18
  from collections.abc import Iterable
19
19
  import copy
20
20
  from dataclasses import dataclass, field, replace
21
- import functools
22
21
  import itertools
23
22
  import logging
24
23
  from types import MethodType
@@ -63,7 +62,7 @@ from iqm.pulse.playlist.instructions import (
63
62
  Wait,
64
63
  )
65
64
  from iqm.pulse.playlist.playlist import Playlist
66
- from iqm.pulse.playlist.schedule import TOLERANCE, Schedule, Segment
65
+ from iqm.pulse.playlist.schedule import Schedule, Segment
67
66
  from iqm.pulse.quantum_ops import QuantumOp, QuantumOpTable, validate_locus_calibration, validate_op_calibration
68
67
  from iqm.pulse.scheduler import (
69
68
  NONSOLID,
@@ -758,8 +757,8 @@ class ScheduleBuilder:
758
757
  if priority_calibration:
759
758
  if op.name in priority_calibration:
760
759
  # first check if full OILCalibrationData structure was given
761
- op_data = priority_calibration.get(op.name)
762
- prio_data = op_data.get(new_impl_name, {}).get(new_locus, {}) # type: ignore
760
+ op_data = priority_calibration.get(op.name, {})
761
+ prio_data = op_data.get(new_impl_name, {}).get(new_locus, {})
763
762
  else:
764
763
  # if not assume just the data for this locus was given
765
764
  prio_data = priority_calibration
@@ -956,14 +955,8 @@ class ScheduleBuilder:
956
955
  def _finish_schedule(self, schedule: Schedule) -> Schedule:
957
956
  """Finishes the instruction schedule.
958
957
 
959
- * filters out zero-duration Blocks and Waits
960
- * converts all spacer instructions used during scheduling to Waits
961
- * merges consequent Waits
962
958
  * removes channels that only have Waits in them
963
-
964
- This should be the final step of the schedule building process, after this the
965
- resulting Schedule can no longer be consistently extended with another (since
966
- all the spacer instructions are gone).
959
+ * fuses long-distance Rz corrections to the correct drive pulses
967
960
 
968
961
  Args:
969
962
  schedule: schedule to finish
@@ -973,46 +966,15 @@ class ScheduleBuilder:
973
966
 
974
967
  """
975
968
  has_long_distance_rzs = False
976
-
977
- def segment_pass(inst: Instruction) -> Instruction:
978
- """Convert Blocks and Nothings to Waits. Also checks for FluxPulses that have long-distance VirtualZ
979
- corrections in order to determine if these need handling (for performance reasons, we want to do this
980
- step only on-demand).
981
- """
982
- nonlocal has_long_distance_rzs
983
- if isinstance(inst, NONSOLID) and not isinstance(inst, Wait):
984
- return Wait(inst.duration)
985
- if isinstance(inst, FluxPulse) and inst.rzs:
986
- has_long_distance_rzs = True
987
- return inst
988
-
989
- def not_zero_block(inst: Instruction) -> bool:
990
- """Zero-duration Block instructions have a legitimate use (in barrier ops),
991
- zero-duration Wait instructions are allowed for convenience.
992
- Here we filter them out.
993
- """
994
- return not (inst.duration < TOLERANCE and isinstance(inst, (Block, Wait)))
995
-
996
- def merge_waits(result: list[Instruction], new: Instruction) -> list[Instruction]:
997
- """Merge consequent Wait instructions."""
998
- if not result:
999
- return [new] # first iteration
1000
- last = result[-1]
1001
- if isinstance(last, Wait) and isinstance(new, Wait):
1002
- result[-1] = Wait(int(last.duration + new.duration)) # replace last with the combined wait
1003
- else:
1004
- result.append(new)
1005
- return result
1006
-
1007
969
  for channel in schedule.channels():
1008
- seg = schedule[channel]
1009
- schedule[channel] = Segment(
1010
- functools.reduce(merge_waits, map(segment_pass, filter(not_zero_block, seg)), []),
1011
- duration=seg.duration,
1012
- )
1013
- if not has_long_distance_rzs:
1014
- return schedule.cleanup()
1015
- return self._fuse_long_distance_virtual_rzs(schedule)
970
+ if "flux" in channel:
971
+ for inst in schedule[channel]:
972
+ if isinstance(inst, FluxPulse) and inst.rzs:
973
+ has_long_distance_rzs = True
974
+ break
975
+ if has_long_distance_rzs:
976
+ return self._fuse_long_distance_virtual_rzs(schedule)
977
+ return schedule.cleanup()
1016
978
 
1017
979
  def _fuse_long_distance_virtual_rzs(self, schedule: Schedule) -> Schedule:
1018
980
  """Fuse long-distance (i.e. out-of-gate-locus) VirtualRZ corrections with the next drive pulse
@@ -1036,7 +998,11 @@ class ScheduleBuilder:
1036
998
  for drive_channel, rz_angle in rzs.items():
1037
999
  drive_sample_counter = 0
1038
1000
  for drive_inst_idx, drive_inst in enumerate(schedule[drive_channel]):
1039
- if not isinstance(drive_inst, Wait) and drive_sample_counter >= sample_counter:
1001
+ if (
1002
+ not isinstance(drive_inst, Wait)
1003
+ and not isinstance(drive_inst, NONSOLID)
1004
+ and drive_sample_counter >= sample_counter
1005
+ ):
1040
1006
  fuse(
1041
1007
  schedule_copy[drive_channel][drive_inst_idx],
1042
1008
  drive_inst_idx,
@@ -1116,10 +1082,11 @@ class ScheduleBuilder:
1116
1082
  ):
1117
1083
  # convert the locus durations to seconds (no probe channels have been used so far)
1118
1084
  scheduling_in_seconds = True
1119
- component_durations_seconds = {
1120
- component: self._channel_templates["other"].duration_to_seconds(duration) # type: ignore
1121
- for component, duration in component_durations.items()
1122
- }
1085
+ if self._channel_templates["other"] is not None:
1086
+ component_durations_seconds = {
1087
+ component: self._channel_templates["other"].duration_to_seconds(duration)
1088
+ for component, duration in component_durations.items()
1089
+ }
1123
1090
 
1124
1091
  child_components = self._get_neighborhood_hard_boundary(child, 0)
1125
1092
  neighborhood_components = self._get_neighborhood_hard_boundary(child, neighborhood)
@@ -1344,38 +1311,58 @@ class ScheduleBuilder:
1344
1311
  }
1345
1312
 
1346
1313
  pl = Playlist()
1347
- mapped_instructions: dict[int | Instruction, Any] = {}
1348
- # add the schedules in the playlist
1314
+ mapped_instructions: dict[str, dict[int, Any]] = {}
1315
+
1316
+ def _append_to_schedule(sc_schedule: SC_Schedule, channel_name: str, instr: Instruction) -> None:
1317
+ """Append ``instr`` to ``sc_schedule`` into the channel``channel_name``."""
1318
+ try:
1319
+ # Check if instr can be used as a dictionary key, and use it if possible.
1320
+ # 2 dataclasses can have the same hash if their fields are identical. We must
1321
+ # distinguish between different Waveform classes which may have identical fields,
1322
+ # so we use the instruction itself as a key, so that the class is checked too.
1323
+ instr_id = hash(instr)
1324
+ except TypeError:
1325
+ instr_id = instr.id
1326
+ if instr_id not in mapped_instructions.setdefault(channel_name, {}):
1327
+ mapped = _map_instruction(instr)
1328
+ idx = pl.channel_descriptions[channel_name].add_instruction(mapped)
1329
+ sc_schedule.instructions.setdefault(channel_name, []).append(idx)
1330
+ mapped_instructions[channel_name][instr_id] = idx
1331
+ else:
1332
+ sc_schedule.instructions.setdefault(channel_name, []).append(
1333
+ mapped_instructions[channel_name][instr_id]
1334
+ )
1335
+
1336
+ # add the schedules in the playlist
1337
+
1349
1338
  # NOTE that there is no implicit right-alignment or equal duration for schedules, unlike in old-style playlists!
1350
1339
  for schedule in schedules:
1351
- if finish_schedules:
1352
- schedule = self._finish_schedule(schedule) # noqa: PLW2901
1340
+ finished_schedule = self._finish_schedule(schedule) if finish_schedules else schedule
1353
1341
  sc_schedule = SC_Schedule()
1354
- for channel_name, segment in schedule.items():
1342
+ for channel_name, segment in finished_schedule.items():
1355
1343
  if (channel := channel_descriptions.get(channel_name)) is None:
1356
- # ignore virtual channels in the schedule
1357
1344
  continue
1358
1345
  pl.add_channel(channel)
1346
+ prev_wait = None
1359
1347
  for instruction in segment:
1360
- try:
1361
- # Check if it can be used as a dictionary key, and use it if possible.
1362
- # 2 dataclasses can have the same hash if their fields are identical. We must distinguish
1363
- # between different Waveform classes which may have identical fields,
1364
- # so we use the instruction itself as a key, so that the class is checked too.
1365
- instr_id = instruction
1366
- is_mapped = instr_id in mapped_instructions
1367
- except TypeError:
1368
- instr_id = instruction.id
1369
- is_mapped = instr_id in mapped_instructions
1370
- if is_mapped:
1371
- sc_schedule.add_to_segment(channel, mapped_instructions[instr_id])
1348
+ # convert all NONSOLID instructions into Waits
1349
+ if finish_schedules and (isinstance(instruction, NONSOLID) or isinstance(instruction, Wait)):
1350
+ if instruction.duration > 0:
1351
+ if prev_wait: # if the previous instruction was a Wait, combine durations
1352
+ prev_wait = Wait(prev_wait.duration + instruction.duration)
1353
+ else:
1354
+ prev_wait = Wait(instruction.duration)
1372
1355
  else:
1373
- mapped = _map_instruction(instruction)
1374
- mapped_instructions[instr_id] = mapped
1375
- sc_schedule.add_to_segment(channel, mapped)
1376
-
1356
+ if prev_wait: # if there's a prev_wait not yet added to schedule, place it before instruction
1357
+ instructions_to_add = [prev_wait, instruction]
1358
+ prev_wait = None
1359
+ else:
1360
+ instructions_to_add = [instruction]
1361
+ for instr in instructions_to_add:
1362
+ _append_to_schedule(sc_schedule, channel_name, instr)
1363
+ if prev_wait:
1364
+ _append_to_schedule(sc_schedule, channel_name, prev_wait)
1377
1365
  pl.segments.append(sc_schedule)
1378
-
1379
1366
  return pl
1380
1367
 
1381
1368
  def _set_gate_implementation_shortcut(self, op_name: str) -> None:
@@ -114,7 +114,7 @@ class GateImplementation(abc.ABC):
114
114
 
115
115
  def __init__(
116
116
  self, parent: QuantumOp, name: str, locus: Locus, calibration_data: OILCalibrationData, builder: ScheduleBuilder
117
- ):
117
+ ) -> None:
118
118
  self.parent = parent
119
119
  self.name = name
120
120
  self.locus = locus
@@ -247,12 +247,12 @@ class GateImplementation(abc.ABC):
247
247
  return value
248
248
  if unit == "s":
249
249
 
250
- def conversion(val):
250
+ def conversion(val: Any) -> Any:
251
251
  return val / dur if dur > 0 else 0.0
252
252
 
253
253
  elif unit == "Hz":
254
254
 
255
- def conversion(val):
255
+ def conversion(val: Any) -> Any:
256
256
  return val * dur
257
257
 
258
258
  if isinstance(value, Iterable):
@@ -303,10 +303,7 @@ class GateImplementation(abc.ABC):
303
303
  def build_node(path: Iterable[str], dictionary: dict[str, Any]) -> SettingNode:
304
304
  node = SettingNode(".".join(path), path=".".join(path))
305
305
  for key, value in dictionary.items():
306
- if "*" in key:
307
- wildcard_keys = [key.replace("*", q) for q in locus]
308
- else:
309
- wildcard_keys = [key]
306
+ wildcard_keys = [key.replace("*", q) for q in locus] if "*" in key else [key]
310
307
 
311
308
  for wkey in wildcard_keys:
312
309
  new_path = (*tuple(path), wkey)
@@ -321,7 +318,7 @@ class GateImplementation(abc.ABC):
321
318
  value.parameter.model_copy(update={"name": name}), value.value, path=name
322
319
  )
323
320
  else:
324
- raise ValueError(f"{wkey}: value {value} is neither a Parameter, Setting nor a dict.")
321
+ raise TypeError(f"{wkey}: value {value} is neither a Parameter, Setting nor a dict.")
325
322
  return node
326
323
 
327
324
  return build_node(path, cls.parameters)
@@ -394,7 +391,7 @@ class CustomIQWaveforms(GateImplementation):
394
391
 
395
392
  def __init__(
396
393
  self, parent: QuantumOp, name: str, locus: Locus, calibration_data: OILCalibrationData, builder: ScheduleBuilder
397
- ):
394
+ ) -> None:
398
395
  super().__init__(parent, name, locus, calibration_data, builder)
399
396
  if getattr(self, "wave_i", None) is None or getattr(self, "wave_q", None) is None:
400
397
  raise ValueError(
@@ -408,7 +405,7 @@ class CustomIQWaveforms(GateImplementation):
408
405
  wave_i: type[Waveform] | None = None,
409
406
  wave_q: type[Waveform] | None = None,
410
407
  dependent_waves: bool | None = None,
411
- ):
408
+ ) -> None:
412
409
  """Store the Waveform types used by this subclass, and their parameters.
413
410
 
414
411
  NOTE: if ``MyGate`` is a subclass of ``CustomIQWaveforms``, with some defined i and q waves, further
@@ -579,7 +576,7 @@ class CompositeGate(GateImplementation):
579
576
  locus: Locus,
580
577
  calibration_data: OILCalibrationData,
581
578
  builder: ScheduleBuilder,
582
- ):
579
+ ) -> None:
583
580
  super().__init__(parent, name, locus, calibration_data, builder)
584
581
  custom_defaults = {
585
582
  op: data.get("default_implementation") for op, data in calibration_data.items() if isinstance(data, dict)
@@ -596,9 +593,8 @@ class CompositeGate(GateImplementation):
596
593
  key_is_hashable = True
597
594
  except TypeError:
598
595
  key_is_hashable = False
599
- if key_is_hashable:
600
- if box := self.builder.composite_cache.get(self, default_cache_key):
601
- return box
596
+ if key_is_hashable and (box := self.builder.composite_cache.get(self, default_cache_key)):
597
+ return box
602
598
  box = self._call(*args, **kwargs)
603
599
  if key_is_hashable:
604
600
  self.builder.composite_cache.set(self, default_cache_key, box)
@@ -660,7 +656,7 @@ class CompositeCache:
660
656
  data.
661
657
  """
662
658
 
663
- def __init__(self):
659
+ def __init__(self) -> None:
664
660
  self._cache: dict[tuple[Any, ...], TimeBox] = {}
665
661
 
666
662
  def set(
@@ -383,8 +383,9 @@ class CouplerFluxPulseQubitACStarkPulseGate(GateImplementation):
383
383
  """
384
384
  _, phase_increment = phase_transformation(0.0, phase_increment)
385
385
 
386
- wave_i = cls.qubit_drive_wave(phase=phase, **kwargs) # type: ignore
387
- wave_q = cls.qubit_drive_wave(phase=phase - np.pi / 2, **kwargs) # type: ignore
386
+ if cls.qubit_drive_wave is not None:
387
+ wave_i = cls.qubit_drive_wave(phase=phase, **kwargs)
388
+ wave_q = cls.qubit_drive_wave(phase=phase - np.pi / 2, **kwargs)
388
389
  return IQPulse(
389
390
  kwargs["n_samples"],
390
391
  wave_i=wave_i,
@@ -431,7 +431,7 @@ class Measure_Constant(Measure_CustomWaveforms, wave_i=Constant, wave_q=Constant
431
431
  """
432
432
 
433
433
 
434
- class Measure_Constant_Qnd(Measure_CustomWaveforms, wave_i=Constant, wave_q=Constant):
434
+ class Measure_Constant_Qnd(Measure_CustomWaveforms, wave_i=Constant, wave_q=Constant): # type:ignore[call-arg]
435
435
  """Implementation of a single-qubit projective, non quantum demolition, dispersive
436
436
  measurements in the Z basis.
437
437
 
@@ -824,10 +824,9 @@ class Shelved_Measure_CustomWaveforms(Measure_CustomWaveforms):
824
824
  # implementation multiplexing. This is because the method has to return time boxes due to the `prx_12` pulses,
825
825
  # instead of `MultiplexedProbeTimeBox`
826
826
  # TODO: Enable mixed implementation multiplexing for shelved readout
827
- def probe_timebox(
827
+ def probe_timebox( # type: ignore[override]
828
828
  self, key: str = "", feedback_key: str = "", do_acquisition: bool = True, _skip_override: bool = False
829
829
  ) -> TimeBox:
830
- # type: ignore[override]
831
830
  if _skip_override:
832
831
  return super().probe_timebox(key, feedback_key, do_acquisition)
833
832
  multiplexed_timeboxes = super().probe_timebox(key, feedback_key)
@@ -847,7 +846,7 @@ class Shelved_Measure_CustomWaveforms(Measure_CustomWaveforms):
847
846
  return shelved_measure_box
848
847
 
849
848
 
850
- class Shelved_Measure_Constant(Shelved_Measure_CustomWaveforms, wave_i=Constant, wave_q=Constant):
849
+ class Shelved_Measure_Constant(Shelved_Measure_CustomWaveforms, wave_i=Constant, wave_q=Constant): # type:ignore[call-arg]
851
850
  """Implementation of a shelved readout.
852
851
 
853
852
  A measure gate implemented as a constant waveform is surrounded by two `prx_12` gates.
@@ -26,6 +26,7 @@ It rotates the qubit state around an axis that lies in the XY plane of the Bloch
26
26
 
27
27
  from __future__ import annotations
28
28
 
29
+ from abc import abstractmethod
29
30
  import copy
30
31
  from functools import lru_cache
31
32
  from typing import TYPE_CHECKING
@@ -85,24 +86,25 @@ def get_unitary_prx(angle: float, phase: float) -> np.ndarray:
85
86
  )
86
87
 
87
88
 
88
- class PRX_GateImplementation(GateImplementation):
89
+ class PrxGateImplementation(GateImplementation):
89
90
  """ABC for different implementations of the PRX gate."""
90
91
 
91
92
  def __init__(
92
93
  self, parent: QuantumOp, name: str, locus: Locus, calibration_data: OILCalibrationData, builder: ScheduleBuilder
93
- ):
94
+ ) -> None:
94
95
  super().__init__(parent, name, locus, calibration_data, builder)
95
96
  self._cliffords: dict[XYGate, TimeBox] = {}
96
97
 
97
- def _call(self, angle: float, phase: float = 0.0) -> TimeBox: # type: ignore[override]
98
- """Phased x rotation gate.
98
+ @abstractmethod
99
+ def _call(self, angle: float, phase: float) -> TimeBox:
100
+ """Phased X rotation gate.
99
101
 
100
102
  Args:
101
- angle: rotation angle (in radians)
102
- phase: phase angle (in radians)
103
+ angle: Rotation angle in radians.
104
+ phase: Phase angle in radians.
103
105
 
104
106
  Returns:
105
- boxed instruction schedule implementing the phased x rotation gate
107
+ Boxed instruction schedule implementing the phased X rotation gate.
106
108
 
107
109
  """
108
110
  raise NotImplementedError
@@ -176,8 +178,8 @@ class PRX_GateImplementation(GateImplementation):
176
178
  return SINGLE_COMPONENTS_WITH_DRIVE_LOCUS_MAPPING
177
179
 
178
180
 
179
- class PRX_SinglePulse_GateImplementation(SinglePulseGate, PRX_GateImplementation):
180
- r"""ABC for PRX gates implemented using a single IQ pulse.
181
+ class PRX_SinglePulse_GateImplementation(SinglePulseGate, PrxGateImplementation):
182
+ r"""Base class for PRX gates implemented using a single IQ pulse.
181
183
 
182
184
  This class implements phased x rotation gates on a specific qubit using an :class:`.IQPulse`
183
185
  instance, derived from the pulse calibration data provided at construction by
@@ -195,7 +197,7 @@ class PRX_SinglePulse_GateImplementation(SinglePulseGate, PRX_GateImplementation
195
197
  duration being zero, the gate implementation will apply a ``Block(0)`` instruction to the qubit's drive channel.
196
198
  """
197
199
 
198
- def _call(self, angle: float, phase: float = 0.0) -> TimeBox: # type: ignore[override]
200
+ def _call(self, angle: float, phase: float = 0.0) -> TimeBox:
199
201
  scale, new_phase = _normalize_params(angle, phase)
200
202
  pulse = self.pulse.copy(
201
203
  scale_i=scale * self.pulse.scale_i,
@@ -216,7 +218,7 @@ class PRX_SinglePulse_GateImplementation(SinglePulseGate, PRX_GateImplementation
216
218
 
217
219
 
218
220
  class PRX_CustomWaveforms(PRX_SinglePulse_GateImplementation, CustomIQWaveforms):
219
- """ABC for PRX gates implemented using a single IQ pulse and hot-swappable waveforms."""
221
+ """Base class for PRX gates implemented using a single IQ pulse and hot-swappable waveforms."""
220
222
 
221
223
  root_parameters: dict[str, Parameter | Setting] = {
222
224
  "duration": Parameter("", "pi pulse duration", "s"),
@@ -251,14 +253,14 @@ class PRX_CustomWaveforms(PRX_SinglePulse_GateImplementation, CustomIQWaveforms)
251
253
  )
252
254
 
253
255
 
254
- class PRX_DRAGGaussian(PRX_CustomWaveforms, wave_i=TruncatedGaussian, wave_q=TruncatedGaussianD): # type: ignore
256
+ class PRX_DRAGGaussian(PRX_CustomWaveforms, wave_i=TruncatedGaussian, wave_q=TruncatedGaussianD): # type:ignore[call-arg]
255
257
  """PRX gate, DRAG / TruncatedGaussian IQ pulse implementation.
256
258
 
257
259
  See :class:`.PRX_CustomWaveforms`.
258
260
  """
259
261
 
260
262
 
261
- class PRX_DRAGCosineRiseFall(PRX_CustomWaveforms, wave_i=CosineRiseFall, wave_q=CosineRiseFallD): # type: ignore
263
+ class PRX_DRAGCosineRiseFall(PRX_CustomWaveforms, wave_i=CosineRiseFall, wave_q=CosineRiseFallD): # type:ignore[call-arg]
262
264
  """PRX gate, DRAG / CosineRiseFall IQ pulse implementation.
263
265
 
264
266
  See :class:`.PRX_CustomWaveforms`.
@@ -273,7 +275,7 @@ class PRX_DRAGCosineRiseFall(PRX_CustomWaveforms, wave_i=CosineRiseFall, wave_q=
273
275
 
274
276
 
275
277
  class PRX_CustomWaveformsSX(PRX_SinglePulse_GateImplementation, CustomIQWaveforms):
276
- r"""ABC for PRX gates implemented using SX gate, hot-swappable waveforms and phase manipulation.
278
+ r"""Base class for PRX gates implemented using SX gate, hot-swappable waveforms and phase manipulation.
277
279
 
278
280
  The schedule used to implement the PRX gate depends on the arguments:
279
281
  1. If the rotation angle :math:`\theta = \pi/2`, the timebox will consist of just the SX IQ pulse, with phase.
@@ -373,15 +375,14 @@ class PRX_CustomWaveformsSX(PRX_SinglePulse_GateImplementation, CustomIQWaveform
373
375
  )
374
376
 
375
377
 
376
- class PRX_DRAGGaussianSX(PRX_CustomWaveformsSX, wave_i=TruncatedGaussian, wave_q=TruncatedGaussianD):
377
- # type: ignore
378
+ class PRX_DRAGGaussianSX(PRX_CustomWaveformsSX, wave_i=TruncatedGaussian, wave_q=TruncatedGaussianD): # type:ignore[call-arg]
378
379
  """PRX gate, DRAG / Gaussian IQ pulse with VZ implementation.
379
380
 
380
381
  See :class:`.PRX_CustomWaveformsVZ`.
381
382
  """
382
383
 
383
384
 
384
- class PRX_DRAGCosineRiseFallSX(PRX_CustomWaveformsSX, wave_i=CosineRiseFall, wave_q=CosineRiseFallD): # type: ignore
385
+ class PRX_DRAGCosineRiseFallSX(PRX_CustomWaveformsSX, wave_i=CosineRiseFall, wave_q=CosineRiseFallD): # type:ignore[call-arg]
385
386
  """PRX gate, DRAG / CosineRiseFall IQ pulse with VZ implementation.
386
387
 
387
388
  See :class:`.PRX_CustomWaveformsVZ`.
@@ -395,32 +396,28 @@ class PRX_DRAGCosineRiseFallSX(PRX_CustomWaveformsSX, wave_i=CosineRiseFall, wav
395
396
  return super()._get_pulse(**kwargs)
396
397
 
397
398
 
398
- class PRX_FastDragSX(PRX_CustomWaveformsSX, wave_i=FastDragI, wave_q=FastDragQ):
399
- # type: ignore
399
+ class PRX_FastDragSX(PRX_CustomWaveformsSX, wave_i=FastDragI, wave_q=FastDragQ): # type:ignore[call-arg]
400
400
  """PRX gate, FAST DRAG IQ pulse with VZ-based SX-implementation.
401
401
 
402
402
  See :class:`.PRX_CustomWaveformsSX`.
403
403
  """
404
404
 
405
405
 
406
- class PRX_FastDrag(PRX_CustomWaveforms, wave_i=FastDragI, wave_q=FastDragQ):
407
- # type: ignore
406
+ class PRX_FastDrag(PRX_CustomWaveforms, wave_i=FastDragI, wave_q=FastDragQ): # type:ignore[call-arg]
408
407
  """PRX gate, FAST DRAG IQ pulse based on amplitude scaling.
409
408
 
410
409
  See :class:`.PRX_CustomWaveforms`.
411
410
  """
412
411
 
413
412
 
414
- class PRX_HdDragSX(PRX_CustomWaveformsSX, wave_i=HdDragI, wave_q=HdDragQ):
415
- # type: ignore
413
+ class PRX_HdDragSX(PRX_CustomWaveformsSX, wave_i=HdDragI, wave_q=HdDragQ): # type:ignore[call-arg]
416
414
  """PRX gate, HD DRAG IQ pulse with VZ-based SX-implementation.
417
415
 
418
416
  See :class:`.PRX_CustomWaveformsSX`.
419
417
  """
420
418
 
421
419
 
422
- class PRX_HdDrag(PRX_CustomWaveforms, wave_i=HdDragI, wave_q=HdDragQ):
423
- # type: ignore
420
+ class PRX_HdDrag(PRX_CustomWaveforms, wave_i=HdDragI, wave_q=HdDragQ): # type:ignore[call-arg]
424
421
  """PRX gate, HD DRAG IQ pulse based on amplitude scaling
425
422
 
426
423
  See :class:`.PRX_CustomWaveforms`.
@@ -447,8 +444,8 @@ def _normalize_params(angle: float, phase: float) -> tuple[float, float]:
447
444
  return angle / half_turn, normalize_angle(phase)
448
445
 
449
446
 
450
- class ABC_Constant_smooth(PRX_GateImplementation):
451
- r"""ABC class for creating gates with an arbitrarily long Constant pulses with smooth rise and fall.
447
+ class ABC_Constant_smooth(PrxGateImplementation):
448
+ r"""Base class for creating gates with an arbitrarily long Constant pulses with smooth rise and fall.
452
449
  This pulse creates a :'Segment' consisting of three instructions : [rise_waveform, main_waveform, fall_waveform].
453
450
  This class is created so that one can use middle waveform as a constant, thus enabling to use arbitrarily
454
451
  long pulses, not limited by the awg memory.
@@ -617,8 +614,8 @@ class Constant_PRX_with_smooth_rise_fall(
617
614
  """
618
615
 
619
616
 
620
- class PRX_ModulatedCustomWaveForms(PRX_CustomWaveforms): # type: ignore
621
- r"""ABC for PRX gates with modulated frequency, hot-swappable waveforms.
617
+ class PRX_ModulatedCustomWaveForms(PRX_CustomWaveforms):
618
+ r"""Base class for PRX gates with modulated frequency, hot-swappable waveforms.
622
619
 
623
620
  The class takes baseband I and Q waveform as input, and modulates them with frequency in the root_parameters.
624
621
  The final pulse shape after modulation is:
@@ -660,7 +657,7 @@ class PRX_ModulatedCustomWaveForms(PRX_CustomWaveforms): # type: ignore
660
657
  )
661
658
 
662
659
 
663
- class PRX_ModulatedDRAGCosineRiseFall(PRX_ModulatedCustomWaveForms, wave_i=CosineRiseFall, wave_q=CosineRiseFallD):
660
+ class PRX_ModulatedDRAGCosineRiseFall(PRX_ModulatedCustomWaveForms, wave_i=CosineRiseFall, wave_q=CosineRiseFallD): # type:ignore[call-arg]
664
661
  """Modulated PRX pulse with cosine rise fall waveform"""
665
662
 
666
663
  excluded_parameters = ["rise_time"]
@@ -218,8 +218,8 @@ class RZ_ACStarkShift(GateImplementation):
218
218
  """
219
219
  _, phase_increment = phase_transformation(0, phase_increment)
220
220
 
221
- wave_i = cls.ac_stark_waveform(n_samples=n_samples, phase=phase, **kwargs) # type: ignore
222
- wave_q = cls.ac_stark_waveform(n_samples=n_samples, phase=phase - np.pi / 2, **kwargs) # type: ignore
221
+ wave_i = cls.ac_stark_waveform(n_samples=n_samples, phase=phase, **kwargs)
222
+ wave_q = cls.ac_stark_waveform(n_samples=n_samples, phase=phase - np.pi / 2, **kwargs)
223
223
  return IQPulse(
224
224
  n_samples,
225
225
  wave_i=wave_i,
@@ -236,9 +236,9 @@ class RZ_ACStarkShift_CosineRiseFall(RZ_ACStarkShift, ac_stark_waveform=Modulate
236
236
 
237
237
  class RZ_ACStarkShift_smoothConstant(
238
238
  Constant_PRX_with_smooth_rise_fall,
239
- rise_waveform=CosineRise,
240
- main_waveform=Constant,
241
- fall_waveform=CosineFall,
239
+ rise_waveform=CosineRise, # type:ignore[call-arg]
240
+ main_waveform=Constant, # type:ignore[call-arg]
241
+ fall_waveform=CosineFall, # type:ignore[call-arg]
242
242
  ):
243
243
  """Constant AC stark pulse with cosine rise and fall padding.
244
244
  Implemented as a 3-instruction Schedule.
@@ -114,11 +114,11 @@ def solve_hd_drag_coefficients_from_suppressed_frequencies(
114
114
 
115
115
  """
116
116
  # The beta coefficients can be solved from a matrix equation A*beta = b according to Eq. (B5). Let's build A
117
- suppressed_freq_arr = np.asarray(suppressed_freq_arr)
118
- n_coefs = len(suppressed_freq_arr) + 1
117
+ suppressed_freqs = np.asarray(suppressed_freq_arr)
118
+ n_coefs = len(suppressed_freqs) + 1
119
119
  a_mat = np.zeros((n_coefs, n_coefs))
120
120
  a_mat[0, 0] = 1
121
- a_mat[1:, :] = (-1) ** np.arange(n_coefs)[None] * (pulse_duration * suppressed_freq_arr[:, None]) ** (
121
+ a_mat[1:, :] = (-1) ** np.arange(n_coefs)[None] * (pulse_duration * suppressed_freqs[:, None]) ** (
122
122
  2 * np.arange(n_coefs)[None]
123
123
  )
124
124
  b_arr = np.zeros((n_coefs,))
@@ -266,8 +266,8 @@ def extend_schedule_new( # noqa: PLR0915
266
266
  _ = channels[ch].duration_to_int_samples(duration, message=f"{ch}: {iA} + {iB}: overlap duration")
267
267
 
268
268
  if isinstance(iA, Nothing) and isinstance(iB, Nothing):
269
- return Nothing(duration=duration) # type: ignore
270
- return Block(duration=duration) # type: ignore
269
+ return Nothing(duration=duration)
270
+ return Block(duration=duration)
271
271
 
272
272
  def find_start_time() -> float:
273
273
  """Find the earliest possible start time for schedule B."""
@@ -297,7 +297,7 @@ def extend_schedule_new( # noqa: PLR0915
297
297
  if distance >= -TOL:
298
298
  if distance > TOL:
299
299
  # gap, add a Nothing between A and B
300
- segment_A._instructions.append(Nothing(duration=distance)) # type: ignore
300
+ segment_A._instructions.append(Nothing(duration=distance))
301
301
  # add B
302
302
  segment_A._instructions.extend(pointer_B.tail())
303
303
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-pulse
3
- Version: 9.15.0
3
+ Version: 9.17.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
+ 9.17.0
@@ -1 +0,0 @@
1
- 9.15.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