boulder-opal-scale-up-sdk 1.0.4__tar.gz → 1.0.5__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 (74) hide show
  1. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/PKG-INFO +1 -1
  2. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/agent/worker.py +23 -4
  3. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/common/dtypes.py +20 -1
  4. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/controller/qblox.py +94 -26
  5. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/device.py +5 -1
  6. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/processor/superconducting_processor.py +23 -3
  7. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/__init__.py +8 -0
  8. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/chi01_scan.py +9 -7
  9. boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/drag_leakage_calibration.py +66 -0
  10. boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/fine_amplitude_calibration.py +54 -0
  11. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/power_rabi.py +10 -7
  12. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/power_rabi_ef.py +10 -9
  13. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/ramsey.py +9 -7
  14. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/readout_classifier_calibration.py +9 -3
  15. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/resonator_spectroscopy.py +9 -7
  16. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_bias.py +11 -8
  17. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_power.py +10 -9
  18. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/t1.py +8 -6
  19. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/t2.py +12 -10
  20. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/t2_echo.py +12 -10
  21. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/transmon_anharmonicity.py +13 -10
  22. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/transmon_spectroscopy.py +10 -8
  23. boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/voltage_bias_fine_tune.py +58 -0
  24. boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/zz_ramsey.py +59 -0
  25. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/plotting/dtypes.py +5 -3
  26. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/agent_pb2.py +9 -3
  27. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/agent_pb2.pyi +14 -0
  28. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/agent_pb2_grpc.py +34 -0
  29. boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/protobuf/v1/device_pb2.py +89 -0
  30. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/device_pb2.pyi +4 -2
  31. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/routines/__init__.py +10 -1
  32. boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/routines/transmon_retuning.py → boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/routines/one_qubit_calibration.py +10 -5
  33. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/routines/resonator_mapping.py +1 -1
  34. boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/routines/transmon_coherence.py +34 -0
  35. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/routines/transmon_discovery.py +5 -9
  36. boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/routines/transmon_retuning.py +41 -0
  37. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/pyproject.toml +2 -4
  38. boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/protobuf/v1/device_pb2.py +0 -89
  39. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/LICENSE +0 -0
  40. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/README.md +0 -0
  41. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/__init__.py +0 -0
  42. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/agent/__init__.py +0 -0
  43. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/common/__init__.py +0 -0
  44. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/common/typeclasses.py +0 -0
  45. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/__init__.py +0 -0
  46. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/common.py +0 -0
  47. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/config_loader.py +0 -0
  48. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/controller/__init__.py +0 -0
  49. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/controller/base.py +0 -0
  50. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/controller/quantum_machines.py +0 -0
  51. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/controller/resolver.py +0 -0
  52. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/defcal.py +0 -0
  53. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/processor/__init__.py +0 -0
  54. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/processor/common.py +0 -0
  55. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/common.py +0 -0
  56. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/waveforms.py +0 -0
  57. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/grpc_interceptors/__init__.py +0 -0
  58. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/grpc_interceptors/auth.py +0 -0
  59. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/plotting/__init__.py +0 -0
  60. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/device_pb2_grpc.py +0 -0
  61. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/task_pb2.py +0 -0
  62. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/task_pb2.pyi +0 -0
  63. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/task_pb2_grpc.py +0 -0
  64. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/py.typed +0 -0
  65. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/routines/common.py +0 -0
  66. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/stubs/__init__.py +0 -0
  67. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/stubs/dtypes.py +0 -0
  68. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/stubs/maps.py +0 -0
  69. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/third_party/__init__.py +0 -0
  70. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/third_party/quantum_machines/__init__.py +0 -0
  71. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/third_party/quantum_machines/config.py +0 -0
  72. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/third_party/quantum_machines/constants.py +0 -0
  73. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/utils/__init__.py +0 -0
  74. {boulder_opal_scale_up_sdk-1.0.4 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/utils/serial_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: boulder-opal-scale-up-sdk
3
- Version: 1.0.4
3
+ Version: 1.0.5
4
4
  Summary: Q-CTRL Boulder Opal Scale Up Python SDK
5
5
  License: https://q-ctrl.com/terms
6
6
  Keywords: black opal,boulder opal,fire opal,nisq,open controls,q control,q ctrl,q-control,q-ctrl,qcontrol,qctrl,quantum,quantum algorithms,quantum circuits,quantum coding,quantum coding software,quantum computing,quantum control,quantum control software,quantum control theory,quantum engineering,quantum error correction,quantum firmware,quantum fundamentals,quantum sensing,qubit,qudit
@@ -54,11 +54,13 @@ class TaskHandler(Protocol):
54
54
  self,
55
55
  request: agent_pb2.RunProgramRequest
56
56
  | agent_pb2.RunQuantumMachinesMixerCalibrationRequest
57
- | agent_pb2.DisplayResultsRequest,
57
+ | agent_pb2.DisplayResultsRequest
58
+ | agent_pb2.AskRequest,
58
59
  ) -> (
59
60
  agent_pb2.RunProgramResponse
60
61
  | agent_pb2.RunQuantumMachinesMixerCalibrationResponse
61
62
  | agent_pb2.DisplayResultsResponse
63
+ | agent_pb2.AskResponse
62
64
  | task_pb2.TaskErrorDetail
63
65
  ): ...
64
66
 
@@ -67,9 +69,8 @@ class TaskHandler(Protocol):
67
69
  task: task_pb2.Task,
68
70
  ) -> any_pb2.Any | task_pb2.TaskErrorDetail:
69
71
  request = (
70
- _as_run_program_request(
71
- task.data,
72
- )
72
+ _as_run_program_request(task.data)
73
+ or _as_ask_request(task.data)
73
74
  or _as_run_qua_calibration_request(task.data)
74
75
  or _as_display_results_request(task.data)
75
76
  )
@@ -78,6 +79,7 @@ class TaskHandler(Protocol):
78
79
  agent_pb2.RunProgramRequest()
79
80
  | agent_pb2.RunQuantumMachinesMixerCalibrationRequest()
80
81
  | agent_pb2.DisplayResultsRequest()
82
+ | agent_pb2.AskRequest()
81
83
  ):
82
84
  return _as_any_message(await self.handle(request))
83
85
  case None:
@@ -104,6 +106,17 @@ def _as_run_program_request(
104
106
  return request
105
107
 
106
108
 
109
+ def _as_ask_request(
110
+ task_result: any_pb2.Any,
111
+ ) -> agent_pb2.AskRequest | None:
112
+ request = agent_pb2.AskRequest()
113
+ unpacked: bool = task_result.Unpack(request) # type: ignore[reportUnknownMemberType]
114
+ if not unpacked:
115
+ return None
116
+
117
+ return request
118
+
119
+
107
120
  def _as_run_qua_calibration_request(
108
121
  task_result: any_pb2.Any,
109
122
  ) -> agent_pb2.RunQuantumMachinesMixerCalibrationRequest | None:
@@ -158,16 +171,22 @@ class Agent:
158
171
  Create a gRPC channel.
159
172
  """
160
173
  host = url.split(":")[0]
174
+ options = [
175
+ ("grpc.max_send_message_length", 1024 * 1024 * 50), # 50MB
176
+ ("grpc.max_receive_message_length", 1024 * 1024 * 50), # 50MB
177
+ ]
161
178
  if host in ["localhost", "127.0.0.1", "0.0.0.0", "::"]:
162
179
  channel = grpc.insecure_channel(
163
180
  url,
164
181
  interceptors=interceptors,
182
+ options=options,
165
183
  )
166
184
  else:
167
185
  channel = grpc.secure_channel(
168
186
  url,
169
187
  credentials=ssl_channel_credentials(),
170
188
  interceptors=interceptors,
189
+ options=options,
171
190
  )
172
191
  return channel
173
192
 
@@ -325,7 +325,26 @@ class JobSummary(BaseModel):
325
325
  created_at: ISO8601DatetimeUTCLike
326
326
 
327
327
  def __str__(self):
328
- return f"JobSummary(name = {self.name}, id = {self.id})"
328
+ return f'JobSummary(name="{self.name}", id="{self.id}")'
329
+
330
+
331
+ class JobDataEntry(BaseModel):
332
+ message: str
333
+ created_at: ISO8601DatetimeUTCLike
334
+ dt: str
335
+ elapsed_time: str
336
+
337
+ class Config:
338
+ extra = "allow"
339
+
340
+
341
+ class JobData(BaseModel):
342
+ id: str
343
+ name: str
344
+ session_id: str
345
+ created_at: ISO8601DatetimeUTCLike
346
+ device_name: str
347
+ data: list[JobDataEntry]
329
348
 
330
349
 
331
350
  DEFAULT_JOB_HISTORY_PAGE = 1
@@ -257,18 +257,18 @@ ChannelType = RealChannel | ComplexChannel
257
257
  # ==================================================================================================
258
258
  # Controller information
259
259
  # ==================================================================================================
260
- class ElementConnection(BaseModel): # pragma: no cover
260
+ class PortConnection(BaseModel): # pragma: no cover
261
261
  """
262
- The connections involved for a control element.
262
+ The connections involved for a QPU port.
263
263
 
264
264
  Attributes
265
265
  ----------
266
266
  ch_out: ChannelType
267
- The output channel that will signal towards the control element.
268
- ch_in: ChannelType, optional
269
- The input channel from which signals will be acquired from the element. This is optional, as
270
- not all modules support acquisitions. If an input channel is specified, it must be located
271
- on the same module as the output channel.
267
+ The output channel that will signal towards the QPU port
268
+ ch_in: ChannelType or None, optional
269
+ The input channel from which signals will be acquired from the QPU port. This is optional,
270
+ as not all modules support acquisitions. If an input channel is specified, it must be
271
+ located on the same module as the output channel.
272
272
 
273
273
  Notes
274
274
  -----
@@ -276,20 +276,21 @@ class ElementConnection(BaseModel): # pragma: no cover
276
276
  direction is outwards from the control stack. The following diagram depicts a simple setup with
277
277
  the arrows indicating a control channel.
278
278
 
279
- ┌────────┐ ┌────────────┐
280
- │ │─── out ──►│Element: xy1
281
- │ QBLOX │ └────────────┘
282
- │ Stack │ ┌────────────┐
283
- │ │─── out ──►│Element: ro1
284
- │ │◄── in ────│
285
- └────────┘ └────────────┘
279
+ ┌────────┐ ┌───────────────┐
280
+ │ │─── out ──►│ Port: p_xy1
281
+ │ QBLOX │ └───────────────┘
282
+ │ Stack │ ┌───────────────┐
283
+ │ │─── out ──►│ Port: p_flrr0
284
+ │ │◄── in ────│
285
+ └────────┘ └───────────────┘
286
+ QPU fridge
286
287
  """
287
288
 
288
289
  ch_out: ChannelType
289
290
  ch_in: ChannelType | None = None
290
291
 
291
292
  @model_validator(mode="after")
292
- def validate_channels(self) -> "ElementConnection":
293
+ def validate_channels(self) -> "PortConnection":
293
294
  if self.ch_in is not None and self.ch_in.module != self.ch_out.module:
294
295
  raise ValueError("I/O channels for an element must be on the same module.")
295
296
  return self
@@ -309,38 +310,102 @@ class QBLOXControllerInfo(BaseModel): # pragma: no cover
309
310
  The type of controller, which is always `ControllerType.QBLOX` for this class.
310
311
  modules: dict[ModuleAddrType, ModuleType]
311
312
  The modules connected to the QBLOX stack.
312
- elements: dict[str, ElementConnection]
313
- The addressable control elements for the stack.
313
+ port_config: dict[str, PortConnection]
314
+ The dictionary of ports with their types and addresses.
314
315
  """
315
316
 
316
317
  controller_type: Literal[ControllerType.QBLOX] = ControllerType.QBLOX
317
318
  modules: dict[ModuleAddrType, ModuleType]
318
- elements: dict[str, ElementConnection]
319
+ port_config: dict[str, PortConnection]
319
320
 
320
321
 
321
322
  # ==================================================================================================
322
323
  # Instrument management
323
324
  # ==================================================================================================
324
325
  class SequencerParams(BaseModel):
325
- nco_freq: float | None = Field(default=None, gt=0)
326
+ nco_freq: float | None = Field(default=None)
326
327
  gain_awg_path0: float | None = Field(default=None, ge=-1.0, le=1.0)
327
328
  offset_awg_path0: float | None = Field(default=None, ge=-1.0, le=1.0)
328
329
  gain_awg_path1: float | None = Field(default=None, ge=-1.0, le=1.0)
329
330
  offset_awg_path1: float | None = Field(default=None, ge=-1.0, le=1.0)
330
331
  marker_ovr_en: bool | None = Field(default=None)
331
- marker_ovr_value: int | None = Field(default=None)
332
+ marker_ovr_value: int | None = Field(default=None, ge=0, le=15)
332
333
  mod_en_awg: bool | None = Field(default=None)
333
334
  demod_en_acq: bool | None = Field(default=None)
334
335
  sync_en: bool | None = Field(default=None)
335
336
  nco_prop_delay_comp_en: bool | None = Field(default=True)
336
- integration_length_acq: int | None = Field(default=None)
337
+ integration_length_acq: int | None = Field(default=None, ge=4, le=16777212, multiple_of=4)
337
338
 
338
339
 
339
- class ModuleParams(BaseModel):
340
- out0_in0_lo_freq: float | None = Field(default=None, gt=0)
341
- out0_in0_lo_en: bool | None = Field(default=None)
340
+ class QcmParams(BaseModel):
341
+ out0_offset: float | None = Field(default=None, ge=-2.5, le=2.5)
342
+ out1_offset: float | None = Field(default=None, ge=-2.5, le=2.5)
343
+ out2_offset: float | None = Field(default=None, ge=-2.5, le=2.5)
344
+ out3_offset: float | None = Field(default=None, ge=-2.5, le=2.5)
345
+
346
+ def update(self, other: Self) -> None:
347
+ if self == other:
348
+ return # Nothing to do
349
+
350
+ self.out0_offset = pick_only_one_or_raise(self.out0_offset, other.out0_offset)
351
+ self.out1_offset = pick_only_one_or_raise(self.out1_offset, other.out1_offset)
352
+ self.out2_offset = pick_only_one_or_raise(self.out2_offset, other.out2_offset)
353
+ self.out3_offset = pick_only_one_or_raise(self.out3_offset, other.out3_offset)
354
+
355
+
356
+ class QcmRfParams(BaseModel):
357
+ out0_att: int | None = Field(default=None, ge=0, le=60, multiple_of=2)
358
+ out1_att: int | None = Field(default=None, ge=0, le=60, multiple_of=2)
359
+
342
360
  out0_lo_freq: float | None = Field(default=None, gt=0)
343
361
  out0_lo_en: bool | None = Field(default=None)
362
+ out1_lo_freq: float | None = Field(default=None, gt=0)
363
+ out1_lo_en: bool | None = Field(default=None)
364
+
365
+ def update(self, other: Self) -> None:
366
+ if self == other:
367
+ return # Nothing to do
368
+
369
+ self.out0_att = pick_only_one_or_raise(self.out0_att, other.out0_att)
370
+ self.out1_att = pick_only_one_or_raise(self.out1_att, other.out1_att)
371
+ self.out0_lo_freq = pick_only_one_or_raise(self.out0_lo_freq, other.out0_lo_freq)
372
+ self.out0_lo_en = pick_only_one_or_raise(self.out0_lo_en, other.out0_lo_en)
373
+ self.out1_lo_freq = pick_only_one_or_raise(self.out1_lo_freq, other.out1_lo_freq)
374
+ self.out1_lo_en = pick_only_one_or_raise(self.out1_lo_en, other.out1_lo_en)
375
+
376
+
377
+ class QrmRfParams(BaseModel):
378
+ out0_att: int | None = Field(default=None, ge=0, le=60, multiple_of=2)
379
+
380
+ out0_in0_lo_freq: float | None = Field(default=None, gt=0)
381
+ out0_in0_lo_en: bool | None = Field(default=None)
382
+
383
+ def update(self, other: Self) -> None:
384
+ if self == other:
385
+ return # Nothing to do
386
+
387
+ self.out0_att = pick_only_one_or_raise(self.out0_att, other.out0_att)
388
+ self.out0_in0_lo_freq = pick_only_one_or_raise(
389
+ self.out0_in0_lo_freq,
390
+ other.out0_in0_lo_freq,
391
+ )
392
+ self.out0_in0_lo_en = pick_only_one_or_raise(self.out0_in0_lo_en, other.out0_in0_lo_en)
393
+
394
+
395
+ ModuleParams = QcmParams | QcmRfParams | QrmRfParams
396
+
397
+
398
+ T0 = TypeVar("T0")
399
+
400
+
401
+ def pick_only_one_or_raise(a: T0 | None, b: T0 | None) -> T0 | None:
402
+ if a == b:
403
+ return a
404
+ if a is None:
405
+ return b
406
+ if b is None:
407
+ return a
408
+ raise ValueError(f"Cannot resolve conflict between given parameters {a} and {b}!")
344
409
 
345
410
 
346
411
  # ==================================================================================================
@@ -414,7 +479,7 @@ class PreparedSequenceProgram(BaseModel): # pragma: no cover
414
479
 
415
480
 
416
481
  class PreparedModule(BaseModel):
417
- params: ModuleParams = ModuleParams()
482
+ params: ModuleParams
418
483
 
419
484
 
420
485
  class PreparedProgram(BaseModel):
@@ -661,7 +726,10 @@ class BitStrideArrayEncoding:
661
726
  encoded_shape = tuple(
662
727
  BitStrideArrayEncoding._round_power2_32bit(dim) for dim in desired_shape
663
728
  )
664
- bit_stride = tuple(int(math.log2(dim)) for dim in encoded_shape)
729
+ # Right-most (most nested) dimension takes least significant bits!
730
+ exponents = tuple(int(math.log2(dim)) for dim in encoded_shape)
731
+ n_bits = sum(exponents)
732
+ bit_stride = tuple(n_bits - sum(exponents[: idx + 1]) for idx in range(len(exponents)))
665
733
  return cls(
666
734
  desired_shape=desired_shape,
667
735
  encoded_shape=encoded_shape,
@@ -29,6 +29,9 @@ class EmptyDefCalData:
29
29
  message: str
30
30
 
31
31
 
32
+ DeviceName = str
33
+
34
+
32
35
  @dataclass
33
36
  class DeviceData:
34
37
  # TODO: retire DeviceInfo the next SDK release
@@ -58,6 +61,7 @@ class DeviceSummary(BaseModel):
58
61
  provider: str
59
62
  updated_at: ISO8601DatetimeUTCLike
60
63
  created_at: ISO8601DatetimeUTCLike
64
+ copied_from: DeviceName | None = None
61
65
 
62
66
  def __str__(self):
63
- return f"DeviceSummary(name = {self.name}, id = {self.id})"
67
+ return f'DeviceSummary(name="{self.name}", id="{self.id}")'
@@ -161,7 +161,27 @@ class Feedline(Component[Literal["feedline"]]):
161
161
  traits: list = Field(default=[])
162
162
 
163
163
 
164
- SuperconductingComponentType = Transmon | Resonator | Port | Feedline | Filter | Coupler
164
+ class TWPA(Component[Literal["twpa"]]):
165
+ dtype: Literal["twpa"] = "twpa"
166
+ traits: list = Field(default=[])
167
+
168
+ impedance: FloatComponentParameter = Field(
169
+ default=ComponentParameter(value=0.0),
170
+ json_schema_extra={"display": {"label": "impedance", "unit": "Ohm", "scale": 1}},
171
+ )
172
+
173
+ # Tunable TWPA parameters
174
+ pump_power: FloatComponentParameter = Field(
175
+ default=ComponentParameter(value=0.0),
176
+ json_schema_extra={"display": {"label": "pump_power", "unit": "dBm", "scale": 1}},
177
+ )
178
+ pump_freq: FloatComponentParameter = Field(
179
+ default=ComponentParameter(value=(0.0)),
180
+ json_schema_extra={"display": {"label": "pump_freq", "unit": "MHz", "scale": 1e-6}},
181
+ )
182
+
183
+
184
+ SuperconductingComponentType = Transmon | Resonator | Port | Feedline | Filter | Coupler | TWPA
165
185
 
166
186
 
167
187
  class Edge(BaseModel):
@@ -176,14 +196,14 @@ class TemplateParam(BaseModel):
176
196
 
177
197
 
178
198
  class ProcessorTemplate(BaseModel):
179
- elements: dict[str, Transmon | Resonator | Port | Feedline | Filter | Coupler]
199
+ elements: dict[str, SuperconductingComponentType]
180
200
  edges: list[Edge]
181
201
 
182
202
 
183
203
  class SuperconductingProcessorTemplate(BaseModel):
184
204
  build: list[TemplateParam]
185
205
  templates: dict[str, ProcessorTemplate]
186
- device_parameters: dict[str, Transmon | Resonator | Port | Feedline | Filter | Coupler] = Field(
206
+ device_parameters: dict[str, SuperconductingComponentType] = Field(
187
207
  default={},
188
208
  )
189
209
 
@@ -20,7 +20,9 @@ __all__ = [
20
20
  "Chi01Scan",
21
21
  "ConstantWaveform",
22
22
  "DragCosineWaveform",
23
+ "DragLeakageCalibration",
23
24
  "Experiment",
25
+ "FineAmplitudeCalibration",
24
26
  "GaussianWaveform",
25
27
  "HypIterable",
26
28
  "LinspaceIterable",
@@ -36,7 +38,9 @@ __all__ = [
36
38
  "T2Echo",
37
39
  "TransmonAnharmonicity",
38
40
  "TransmonSpectroscopy",
41
+ "VoltageBiasFineTune",
39
42
  "Waveform",
43
+ "ZZRamsey",
40
44
  "update_amplitude",
41
45
  ]
42
46
 
@@ -49,6 +53,8 @@ from .common import (
49
53
  LogspaceIterable,
50
54
  RangeIterable,
51
55
  )
56
+ from .drag_leakage_calibration import DragLeakageCalibration
57
+ from .fine_amplitude_calibration import FineAmplitudeCalibration
52
58
  from .power_rabi import PowerRabi
53
59
  from .power_rabi_ef import PowerRabiEF
54
60
  from .ramsey import Ramsey
@@ -61,6 +67,7 @@ from .t2 import T2
61
67
  from .t2_echo import T2Echo
62
68
  from .transmon_anharmonicity import TransmonAnharmonicity
63
69
  from .transmon_spectroscopy import TransmonSpectroscopy
70
+ from .voltage_bias_fine_tune import VoltageBiasFineTune
64
71
  from .waveforms import (
65
72
  ConstantWaveform,
66
73
  DragCosineWaveform,
@@ -68,3 +75,4 @@ from .waveforms import (
68
75
  Waveform,
69
76
  update_amplitude,
70
77
  )
78
+ from .zz_ramsey import ZZRamsey
@@ -11,6 +11,8 @@
11
11
  # distributed under the License is distributed on an "AS IS" BASIS. See the
12
12
  # License for the specific language.
13
13
 
14
+ from typing import Literal
15
+
14
16
  from pydantic import PrivateAttr
15
17
 
16
18
  from .common import (
@@ -22,9 +24,6 @@ from .common import (
22
24
  )
23
25
  from .waveforms import ConstantWaveform
24
26
 
25
- DEFAULT_RECYCLE_DELAY_NS = 10_000
26
- DEFAULT_SHOT_COUNT = 100
27
-
28
27
 
29
28
  class Chi01Scan(Experiment):
30
29
  """
@@ -35,12 +34,12 @@ class Chi01Scan(Experiment):
35
34
  ----------
36
35
  transmon : str
37
36
  The reference for the transmon to target.
38
- frequencies : list[int] or LinspaceIterable or RangeIterable or CWSIterable
37
+ frequencies : list[int] or LinspaceIterable or RangeIterable or CWSIterable \
39
38
  or HypIterable or None, optional
40
39
  The frequencies at which to scan, in Hz.
41
40
  Defaults to a scan around the readout frequency.
42
41
  recycle_delay_ns : int, optional
43
- The delay between consecutive shots, in nanoseconds. Defaults to 10,000 ns.
42
+ The delay between consecutive shots, in nanoseconds. Defaults to 200,000 ns.
44
43
  shot_count : int, optional
45
44
  The number of shots to take. Defaults to 100.
46
45
  measure_waveform : ConstantWaveform or None, optional
@@ -48,6 +47,8 @@ class Chi01Scan(Experiment):
48
47
  Defaults to the measurement defcal.
49
48
  run_mixer_calibration: bool
50
49
  Whether to run mixer calibrations before running a program. Defaults to False.
50
+ update : "auto" or "off" or "prompt", optional
51
+ How the device should be updated after an experiment run. Defaults to auto.
51
52
  """
52
53
 
53
54
  _experiment_name: str = PrivateAttr("chi01_scan")
@@ -56,7 +57,8 @@ class Chi01Scan(Experiment):
56
57
  frequencies: list[int] | LinspaceIterable | RangeIterable | CWSIterable | HypIterable | None = (
57
58
  None
58
59
  )
59
- recycle_delay_ns: int = DEFAULT_RECYCLE_DELAY_NS
60
- shot_count: int = DEFAULT_SHOT_COUNT
60
+ recycle_delay_ns: int = 200_000
61
+ shot_count: int = 100
61
62
  measure_waveform: ConstantWaveform | None = None
62
63
  run_mixer_calibration: bool = False
64
+ update: Literal["auto", "off", "prompt"] = "auto"
@@ -0,0 +1,66 @@
1
+ # Copyright 2025 Q-CTRL. All rights reserved.
2
+ #
3
+ # Licensed under the Q-CTRL Terms of service (the "License"). Unauthorized
4
+ # copying or use of this file, via any medium, is strictly prohibited.
5
+ # Proprietary and confidential. You may not use this file except in compliance
6
+ # with the License. You may obtain a copy of the License at
7
+ #
8
+ # https://q-ctrl.com/terms
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS. See the
12
+ # License for the specific language.
13
+
14
+ from typing import Literal
15
+
16
+ from pydantic import PrivateAttr, field_validator
17
+
18
+ from .common import CWSIterable, Experiment
19
+
20
+
21
+ class DragLeakageCalibration(Experiment):
22
+ """
23
+ Parameters for running a DRAG leakage calibration experiment for
24
+ a specified gate on a transmon.
25
+
26
+ Attributes
27
+ ----------
28
+ transmon : str
29
+ The reference for the transmon to target.
30
+ recycle_delay_ns : int
31
+ The delay between consecutive shots, in nanoseconds. Defaults to 10,000 ns.
32
+ shot_count : int, optional
33
+ The number of shots to take. Defaults to 1,000.
34
+ alphas: list[float] or CWSIterable
35
+ List of values to sweep for DRAG parameter alpha.
36
+ repetitions : list[int]
37
+ List of repetition counts for the calibration experiment.
38
+ gate : "sx" or "x"
39
+ The gate to calibrate.
40
+ anharmonicity : float or None, optional
41
+ The anharmonicity of the transmon, in Hz.
42
+ Defaults to None, in which case the anharmonicity of the transmon will be used.
43
+ drag_guess : float
44
+ Initial guess for the DRAG parameter.
45
+ update : "auto" or "off" or "prompt", optional
46
+ How the device should be updated after an experiment run. Defaults to auto.
47
+ """
48
+
49
+ _experiment_name: str = PrivateAttr("drag_leakage_calibration")
50
+
51
+ transmon: str
52
+ recycle_delay_ns: int = 10_000
53
+ shot_count: int = 1_000
54
+ alphas: list[float] | CWSIterable
55
+ repetitions: list[int]
56
+ gate: Literal["x", "sx"]
57
+ anharmonicity: float | None = None
58
+ drag_guess: float
59
+ update: Literal["auto", "off", "prompt"] = "auto"
60
+
61
+ @field_validator("alphas")
62
+ @classmethod
63
+ def validate_alphas(cls, value: list[float] | CWSIterable):
64
+ if isinstance(value, CWSIterable) and value.center is None:
65
+ raise TypeError("The center of alphas must not be None.")
66
+ return value
@@ -0,0 +1,54 @@
1
+ # Copyright 2025 Q-CTRL. All rights reserved.
2
+ #
3
+ # Licensed under the Q-CTRL Terms of service (the "License"). Unauthorized
4
+ # copying or use of this file, via any medium, is strictly prohibited.
5
+ # Proprietary and confidential. You may not use this file except in compliance
6
+ # with the License. You may obtain a copy of the License at
7
+ #
8
+ # https://q-ctrl.com/terms
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS. See the
12
+ # License for the specific language.
13
+
14
+ from typing import Literal
15
+
16
+ from pydantic import Field, PrivateAttr
17
+
18
+ from .common import Experiment
19
+ from .waveforms import ConstantWaveform
20
+
21
+
22
+ class FineAmplitudeCalibration(Experiment):
23
+ """
24
+ Parameters for running a fine amplitude calibration experiment for
25
+ a specified gate on a transmon.
26
+
27
+ Attributes
28
+ ----------
29
+ transmon : str
30
+ The reference for the transmon to target.
31
+ gate : Literal["sx", "x"]
32
+ The gate to be calibrated.
33
+ repetitions : list[int]
34
+ List of repetition counts for the calibration experiment.
35
+ recycle_delay_ns : int
36
+ The delay between consecutive shots, in nanoseconds. Defaults to 10,000 ns.
37
+ shot_count : int, optional
38
+ The number of shots to take. Defaults to 1000.
39
+ measure_waveform : ConstantWaveform or None, optional
40
+ The waveform to use for the measurement pulse.
41
+ Defaults to the measurement defcal.
42
+ update : "auto" or "off" or "prompt", optional
43
+ How the device should be updated after an experiment run. Defaults to auto.
44
+ """
45
+
46
+ _experiment_name: str = PrivateAttr("fine_amplitude_calibration")
47
+
48
+ transmon: str
49
+ gate: Literal["sx", "x"]
50
+ repetitions: list[int] = Field(default=list(range(0, 100, 4)))
51
+ recycle_delay_ns: int = 10_000
52
+ shot_count: int = 1000
53
+ measure_waveform: ConstantWaveform | None = None
54
+ update: Literal["auto", "off", "prompt"] = "auto"
@@ -11,6 +11,8 @@
11
11
  # distributed under the License is distributed on an "AS IS" BASIS. See the
12
12
  # License for the specific language.
13
13
 
14
+ from typing import Literal
15
+
14
16
  from pydantic import PrivateAttr
15
17
 
16
18
  from .common import (
@@ -22,25 +24,23 @@ from .common import (
22
24
  )
23
25
  from .waveforms import ConstantWaveform, Waveform
24
26
 
25
- DEFAULT_SHOT_COUNT = 400
26
-
27
27
 
28
28
  class PowerRabi(Experiment):
29
29
  """
30
30
  Parameters for running a Power Rabi experiment.
31
31
 
32
- Parameters
32
+ Attributes
33
33
  ----------
34
34
  transmon : str
35
35
  The reference for the transmon to target.
36
- scales : list[float] or LinspaceIterable or RangeIterable
36
+ scales : list[float] or LinspaceIterable or RangeIterable \
37
37
  or CWSIterable or HypIterable or None, optional
38
38
  The scaling factors for the drive pulse amplitude.
39
39
  If None, a default scan will be used.
40
40
  drive_waveform : Waveform
41
41
  The waveform to use for the drive pulse.
42
42
  recycle_delay_ns : int
43
- The delay between consecutive shots, in nanoseconds.
43
+ The delay between consecutive shots, in nanoseconds. Defaults to 200,000 ns.
44
44
  shot_count : int, optional
45
45
  The number of shots to take. Defaults to 400.
46
46
  pulse_vp : float, optional
@@ -50,6 +50,8 @@ class PowerRabi(Experiment):
50
50
  Defaults to the measurement defcal.
51
51
  run_mixer_calibration: bool
52
52
  Whether to run mixer calibrations before running a program. Defaults to False.
53
+ update : "auto" or "off" or "prompt", optional
54
+ How the device should be updated after an experiment run. Defaults to auto.
53
55
  """
54
56
 
55
57
  _experiment_name: str = PrivateAttr("power_rabi")
@@ -57,8 +59,9 @@ class PowerRabi(Experiment):
57
59
  transmon: str
58
60
  scales: list[float] | LinspaceIterable | RangeIterable | CWSIterable | HypIterable | None = None
59
61
  drive_waveform: Waveform
60
- recycle_delay_ns: int
61
- shot_count: int = DEFAULT_SHOT_COUNT
62
+ recycle_delay_ns: int = 200_000
63
+ shot_count: int = 400
62
64
  pulse_vp: float | None = None
63
65
  measure_waveform: ConstantWaveform | None = None
64
66
  run_mixer_calibration: bool = False
67
+ update: Literal["auto", "off", "prompt"] = "auto"