boulder-opal-scale-up-sdk 1.0.7__tar.gz → 1.0.8__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 (88) hide show
  1. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/PKG-INFO +4 -6
  2. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/common/dtypes.py +7 -5
  3. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/controller/qblox.py +149 -52
  4. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/device.py +0 -1
  5. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/processor/superconducting_processor.py +21 -1
  6. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/chi01_scan.py +1 -1
  7. boulder_opal_scale_up_sdk-1.0.8/boulderopalscaleupsdk/experiments/cz_spectroscopy_by_bias.py +65 -0
  8. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/drag_leakage_calibration.py +1 -4
  9. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/fine_amplitude_calibration.py +4 -3
  10. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/power_rabi.py +2 -2
  11. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/power_rabi_ef.py +2 -2
  12. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/ramsey.py +1 -1
  13. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/ramsey_ef.py +1 -1
  14. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/readout_classifier.py +2 -2
  15. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/readout_optimization.py +3 -3
  16. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/resonator_spectroscopy.py +1 -1
  17. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_bias.py +2 -2
  18. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_power.py +1 -1
  19. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/t1.py +1 -1
  20. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/t2.py +1 -1
  21. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/t2_echo.py +1 -1
  22. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/transmon_anharmonicity.py +1 -1
  23. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/transmon_spectroscopy.py +2 -2
  24. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/voltage_bias_fine_tune.py +7 -5
  25. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/zz_ramsey.py +8 -14
  26. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/plotting/dtypes.py +1 -1
  27. boulder_opal_scale_up_sdk-1.0.8/boulderopalscaleupsdk/protobuf/v1/device_pb2.py +91 -0
  28. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/protobuf/v1/device_pb2.pyi +0 -14
  29. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/protobuf/v1/device_pb2_grpc.py +0 -34
  30. boulder_opal_scale_up_sdk-1.0.8/boulderopalscaleupsdk/protobuf/v1/resource_pb2.py +40 -0
  31. boulder_opal_scale_up_sdk-1.0.8/boulderopalscaleupsdk/protobuf/v1/resource_pb2.pyi +52 -0
  32. boulder_opal_scale_up_sdk-1.0.8/boulderopalscaleupsdk/protobuf/v1/resource_pb2_grpc.py +104 -0
  33. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/routines/coupler_discovery.py +4 -4
  34. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/routines/one_qubit_calibration.py +4 -0
  35. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/routines/resonator_mapping.py +1 -1
  36. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/pyproject.toml +23 -15
  37. boulder_opal_scale_up_sdk-1.0.7/boulderopalscaleupsdk/experiments/cz_spectroscopy_by_bias.py +0 -84
  38. boulder_opal_scale_up_sdk-1.0.7/boulderopalscaleupsdk/protobuf/v1/device_pb2.py +0 -97
  39. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/LICENSE +0 -0
  40. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/README.md +0 -0
  41. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/__init__.py +0 -0
  42. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/agent/__init__.py +0 -0
  43. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/agent/worker.py +0 -0
  44. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/common/__init__.py +0 -0
  45. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/common/typeclasses.py +0 -0
  46. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/constants.py +0 -0
  47. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/__init__.py +0 -0
  48. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/common.py +0 -0
  49. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/config_loader.py +0 -0
  50. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/controller/__init__.py +0 -0
  51. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/controller/base.py +0 -0
  52. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/controller/quantum_machines.py +0 -0
  53. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/controller/resolver.py +0 -0
  54. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/defcal.py +0 -0
  55. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/processor/__init__.py +0 -0
  56. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/device/processor/common.py +0 -0
  57. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/errors.py +0 -0
  58. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/__init__.py +0 -0
  59. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/common.py +0 -0
  60. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/experiments/waveforms.py +0 -0
  61. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/grpc_interceptors/__init__.py +0 -0
  62. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/grpc_interceptors/auth.py +0 -0
  63. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/grpc_interceptors/error.py +0 -0
  64. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/plotting/__init__.py +0 -0
  65. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/protobuf/v1/agent_pb2.py +0 -0
  66. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/protobuf/v1/agent_pb2.pyi +0 -0
  67. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/protobuf/v1/agent_pb2_grpc.py +0 -0
  68. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/protobuf/v1/job_pb2.py +0 -0
  69. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/protobuf/v1/job_pb2.pyi +0 -0
  70. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/protobuf/v1/job_pb2_grpc.py +0 -0
  71. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/protobuf/v1/task_pb2.py +0 -0
  72. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/protobuf/v1/task_pb2.pyi +0 -0
  73. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/protobuf/v1/task_pb2_grpc.py +0 -0
  74. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/py.typed +0 -0
  75. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/routines/__init__.py +0 -0
  76. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/routines/common.py +0 -0
  77. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/routines/transmon_coherence.py +0 -0
  78. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/routines/transmon_discovery.py +0 -0
  79. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/routines/transmon_retuning.py +0 -0
  80. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/solutions/__init__.py +0 -0
  81. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/solutions/common.py +0 -0
  82. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/solutions/placeholder_solution.py +0 -0
  83. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/third_party/__init__.py +0 -0
  84. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/third_party/quantum_machines/__init__.py +0 -0
  85. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/third_party/quantum_machines/config.py +0 -0
  86. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/third_party/quantum_machines/constants.py +0 -0
  87. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/boulderopalscaleupsdk/utils/__init__.py +0 -0
  88. {boulder_opal_scale_up_sdk-1.0.7 → boulder_opal_scale_up_sdk-1.0.8}/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.7
3
+ Version: 1.0.8
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
@@ -14,10 +14,8 @@ Classifier: Environment :: Console
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: Intended Audience :: Education
16
16
  Classifier: Intended Audience :: Science/Research
17
- Classifier: License :: Other/Proprietary License
18
17
  Classifier: Natural Language :: English
19
18
  Classifier: Operating System :: OS Independent
20
- Classifier: Programming Language :: Python :: 3
21
19
  Classifier: Programming Language :: Python :: 3.11
22
20
  Classifier: Programming Language :: Python :: 3.12
23
21
  Classifier: Topic :: Internet :: WWW/HTTP
@@ -27,13 +25,13 @@ Classifier: Topic :: Software Development :: Embedded Systems
27
25
  Classifier: Topic :: System :: Distributed Computing
28
26
  Provides-Extra: quantum-machines
29
27
  Requires-Dist: attrs (>=25.1.0,<26.0.0)
30
- Requires-Dist: googleapis-common-protos (>=1.69.2,<2.0.0)
31
- Requires-Dist: numpy (>=1.26.4,<2.0.0)
28
+ Requires-Dist: googleapis-common-protos (>=1.69.2,<1.70.0)
29
+ Requires-Dist: numpy (>=2.3.3,<3.0.0)
32
30
  Requires-Dist: pydantic (>=2.10.4,<3.0.0)
33
31
  Requires-Dist: pydantic-settings (>=2.7.0,<3.0.0)
34
32
  Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
35
33
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
36
- Requires-Dist: qm-qua (==1.2.3) ; extra == "quantum-machines"
34
+ Requires-Dist: qm-qua ; extra == "quantum-machines"
37
35
  Project-URL: Facebook, https://www.facebook.com/qctrl
38
36
  Project-URL: GitHub, https://github.com/qctrl
39
37
  Project-URL: Homepage, https://q-ctrl.com
@@ -158,7 +158,7 @@ class Duration:
158
158
 
159
159
  self.value = int(self.value)
160
160
  try:
161
- self._np_rep = np.timedelta64(self.value, self.unit)
161
+ self._np_rep = np.timedelta64(self.value, self.unit.value)
162
162
  except ValueError as e:
163
163
  raise err from e
164
164
 
@@ -182,12 +182,14 @@ class Duration:
182
182
  def __str__(self):
183
183
  return f"{self.value} {self.unit.value}"
184
184
 
185
- def convert(self, unit: TimeUnit) -> Duration | InvalidDurationConversion:
186
- val: np.float64 = self._np_rep / np.timedelta64(1, unit)
185
+ def convert(self, unit: TimeUnit | str) -> Duration | InvalidDurationConversion:
186
+ unit_enum = TimeUnit(unit) if isinstance(unit, str) else unit
187
+
188
+ val = np.float64(self._np_rep / np.timedelta64(1, unit_enum.value))
187
189
  if val.is_integer():
188
- return Duration(int(val), unit)
190
+ return Duration(int(val), unit_enum)
189
191
  return InvalidDurationConversion(
190
- f"fail to convert to {unit} with {self.value} {self.unit}.",
192
+ f"fail to convert to {unit_enum} with {self.value} {self.unit}.",
191
193
  )
192
194
 
193
195
  def to_ns(self) -> Duration:
@@ -20,7 +20,8 @@ __all__ = (
20
20
  "AcquisitionConfig",
21
21
  "BitStrideArrayEncoding",
22
22
  "ChannelType",
23
- "ComplexChannel",
23
+ "IQChannel",
24
+ "IQMixedChannel",
24
25
  "IndexedData",
25
26
  "ModuleAddr",
26
27
  "ModuleAddrType",
@@ -37,11 +38,11 @@ __all__ = (
37
38
  "PortAddrType",
38
39
  "PreparedProgram",
39
40
  "PreparedSequenceProgram",
40
- "RealChannel",
41
41
  "SequenceProgram",
42
42
  "SequencerAddr",
43
43
  "SequencerAddrType",
44
44
  "SequencerResults",
45
+ "SingleChannel",
45
46
  "process_sequencer_output",
46
47
  "validate_channel",
47
48
  )
@@ -204,10 +205,20 @@ PortAddrType = Annotated[PortAddr, _addr_validator(PortAddr), PlainSerializer(st
204
205
  # ==================================================================================================
205
206
  # Signalling Channels
206
207
  # ==================================================================================================
207
- class RealChannel(BaseModel):
208
- """A real channel targeting a single hardware port on a QBLOX module."""
208
+ class IQMixedChannel(BaseModel):
209
+ """
210
+ An IQ-mixed channel.
211
+
212
+ Both sequencer AWG paths are mixed internally and routed to a single module port.
209
213
 
210
- mode: Literal["real"] = "real"
214
+ ┌─────────┐Path 0 Port
215
+ │ ├───────►│IQMixed ┌─┐
216
+ │Sequencer│ │───────►│─│
217
+ │ ├───────►│Ch └─┘
218
+ └─────────┘Path 1
219
+ """
220
+
221
+ mode: Literal["iq-mixed"] = "iq-mixed"
211
222
  port: PortAddrType
212
223
 
213
224
  @property
@@ -219,13 +230,26 @@ class RealChannel(BaseModel):
219
230
  return self.port.direction
220
231
 
221
232
  def __str__(self) -> str:
222
- return f"{self.port!s}[R]"
233
+ return f"{self.port!s}[1]"
234
+
223
235
 
236
+ class IQChannel(BaseModel):
237
+ """
238
+ An IQ channel.
239
+
240
+ The sequencer AWG paths target separate ports for external IQ mixing.
224
241
 
225
- class ComplexChannel(BaseModel):
226
- """A Complex channel targeting a single hardware port on a QBLOX module."""
242
+ IPort
243
+ ┌─────────┐Path 0 ┌─┐
244
+ │ ├───────►├───────►│─│
245
+ │Sequencer│ │IQ Ch ├─┤
246
+ │ ├───────►├───────►│─│
247
+ └─────────┘Path 1 └─┘
248
+ QPort
227
249
 
228
- mode: Literal["complex"] = "complex"
250
+ """
251
+
252
+ mode: Literal["iq"] = "iq"
229
253
  i_port: PortAddrType
230
254
  q_port: PortAddrType
231
255
 
@@ -238,10 +262,10 @@ class ComplexChannel(BaseModel):
238
262
  return self.i_port.direction
239
263
 
240
264
  def __str__(self) -> str:
241
- return f"{self.i_port!s}_{self.q_port.number}[Z]"
265
+ return f"{self.i_port!s}_{self.q_port.number}[iq]"
242
266
 
243
267
  @model_validator(mode="after")
244
- def validate_i_q_ports(self) -> "ComplexChannel":
268
+ def validate_i_q_ports(self) -> "IQChannel":
245
269
  ii = self.i_port
246
270
  qq = self.q_port
247
271
  if ii.cluster != qq.cluster or ii.slot != qq.slot:
@@ -251,7 +275,36 @@ class ComplexChannel(BaseModel):
251
275
  return self
252
276
 
253
277
 
254
- ChannelType = RealChannel | ComplexChannel
278
+ class SingleChannel(BaseModel):
279
+ """
280
+ Single channel.
281
+
282
+ A single sequencer path is routed to a single port.
283
+
284
+ ┌─────────┐Path 0 Port
285
+ │ ├───────►│Single ┌─┐
286
+ │Sequencer│ ├───────►│─│
287
+ │ │ │Ch └─┘
288
+ └─────────┘
289
+ """
290
+
291
+ mode: Literal["single"] = "single"
292
+ port: PortAddrType
293
+ path: Literal[0, 1] = 0
294
+
295
+ @property
296
+ def module(self) -> ModuleAddr:
297
+ return ModuleAddr(self.port.cluster, self.port.slot)
298
+
299
+ @property
300
+ def direction(self) -> Literal["out", "in"]:
301
+ return self.port.direction
302
+
303
+ def __str__(self) -> str:
304
+ return f"{self.port!s}_{self.port.number}[single]"
305
+
306
+
307
+ ChannelType = IQMixedChannel | IQChannel | SingleChannel
255
308
 
256
309
 
257
310
  # ==================================================================================================
@@ -318,6 +371,20 @@ class QBLOXControllerInfo(BaseModel): # pragma: no cover
318
371
  modules: dict[ModuleAddrType, ModuleType]
319
372
  port_config: dict[str, PortConnection]
320
373
 
374
+ @model_validator(mode="after")
375
+ def validate_channels(self) -> Self:
376
+ for port, port_conn in self.port_config.items():
377
+ mod_addr = port_conn.ch_out.module
378
+ mod_type = self.modules[mod_addr]
379
+
380
+ mod_constraints = DEFAULT_MODULE_CONSTRAINTS[mod_type]
381
+ if ch_iss := validate_channel(port_conn.ch_out, mod_constraints):
382
+ raise ValueError(f"Invalid channel for port {port}: {ch_iss}")
383
+ if port_conn.ch_in and (ch_iss := validate_channel(port_conn.ch_in, mod_constraints)):
384
+ raise ValueError(f"Invalid channel for port {port}: {ch_iss}")
385
+
386
+ return self
387
+
321
388
 
322
389
  # ==================================================================================================
323
390
  # Instrument management
@@ -752,6 +819,9 @@ class BitStrideArrayEncoding:
752
819
  # ==================================================================================================
753
820
  # Utilities
754
821
  # ==================================================================================================
822
+ Q1_MAX_WAVEFORM_MEMORY = 16384 # samples
823
+
824
+
755
825
  @dataclasses.dataclass
756
826
  class ModuleConstraints:
757
827
  """Physical constraints of a module."""
@@ -761,6 +831,7 @@ class ModuleConstraints:
761
831
  n_ch_out: int = 0
762
832
  n_ch_in: int = 0
763
833
  n_digital_io: int = 0
834
+ is_rf: bool = False
764
835
 
765
836
  # TODO: Confirm if ordering is important.
766
837
  ch_out_iq_pairs: list[set[int]] = dataclasses.field(default_factory=list)
@@ -781,6 +852,7 @@ DEFAULT_MODULE_CONSTRAINTS: dict[ModuleType, ModuleConstraints] = {
781
852
  n_markers=2,
782
853
  n_ch_out=2,
783
854
  n_ch_in=0,
855
+ is_rf=True,
784
856
  ),
785
857
  ModuleType.QRM: ModuleConstraints(
786
858
  n_sequencers=6,
@@ -795,6 +867,7 @@ DEFAULT_MODULE_CONSTRAINTS: dict[ModuleType, ModuleConstraints] = {
795
867
  n_markers=2,
796
868
  n_ch_out=1,
797
869
  n_ch_in=1,
870
+ is_rf=True,
798
871
  ),
799
872
  ModuleType.QRC: ModuleConstraints(
800
873
  n_sequencers=12,
@@ -843,53 +916,77 @@ def validate_channel(ch: ChannelType, constraint: ModuleConstraints) -> list[str
843
916
  return _validate_input_channel(ch, constraint)
844
917
 
845
918
 
846
- def _validate_output_channel(ch_out: ChannelType, constraint: ModuleConstraints) -> list[str]:
919
+ def _validate_output_channel(ch_out: ChannelType, constraint: ModuleConstraints) -> list[str]: # noqa: C901
847
920
  issues = []
848
- if isinstance(ch_out, RealChannel):
849
- po_out = ch_out.port
850
- if constraint.n_ch_out == 0:
851
- issues.append("module has no output ports.")
852
- elif po_out.number < 0 or po_out.number >= constraint.n_ch_out:
853
- issues.append(
854
- f"output port number {po_out.number} out-of-bounds for module, "
855
- f"must be between [0, {constraint.n_ch_out}).",
856
- )
857
- else:
858
- valid_pairs = constraint.ch_out_iq_pairs
859
- if not valid_pairs:
860
- issues.append("module does not support complex output channels.")
861
- else:
862
- po_out_i = ch_out.i_port
863
- po_out_q = ch_out.q_port
864
- if {po_out_i.number, po_out_q.number} not in valid_pairs:
921
+ match ch_out:
922
+ case IQMixedChannel():
923
+ po_out = ch_out.port
924
+ if constraint.n_ch_out == 0:
925
+ issues.append("module has no output ports.")
926
+ elif po_out.number < 0 or po_out.number >= constraint.n_ch_out:
865
927
  issues.append(
866
- f"invalid output IQ pair {{{po_out_i.number}, {po_out_q.number}}}, module only "
867
- f"supports pairs {valid_pairs}.",
928
+ f"output port number {po_out.number} out-of-bounds for module, "
929
+ f"must be between [0, {constraint.n_ch_out}).",
930
+ )
931
+ case IQChannel():
932
+ valid_pairs = constraint.ch_out_iq_pairs
933
+ if not valid_pairs:
934
+ issues.append("module does not support IQ output channels.")
935
+ else:
936
+ po_out_i = ch_out.i_port
937
+ po_out_q = ch_out.q_port
938
+ if {po_out_i.number, po_out_q.number} not in valid_pairs:
939
+ issues.append(
940
+ f"invalid output IQ pair {{{po_out_i.number}, {po_out_q.number}}}, "
941
+ f"module only supports pairs {valid_pairs}.",
942
+ )
943
+ case SingleChannel():
944
+ po_out = ch_out.port
945
+ if constraint.is_rf:
946
+ issues.append("RF modules cannot use single channels.")
947
+ if constraint.n_ch_out == 0:
948
+ issues.append("module has no output ports.")
949
+ elif po_out.number < 0 or po_out.number >= constraint.n_ch_out:
950
+ issues.append(
951
+ f"output port number {po_out.number} out-of-bounds for module, "
952
+ f"must be between [0, {constraint.n_ch_out}).",
868
953
  )
869
954
  return issues
870
955
 
871
956
 
872
- def _validate_input_channel(ch_in: ChannelType, constraint: ModuleConstraints) -> list[str]:
957
+ def _validate_input_channel(ch_in: ChannelType, constraint: ModuleConstraints) -> list[str]: # noqa: C901
873
958
  issues = []
874
- if isinstance(ch_in, RealChannel):
875
- po_in = ch_in.port
876
- if constraint.n_ch_in == 0:
877
- issues.append("module has no input ports.")
878
- elif po_in.number < 0 or po_in.number >= constraint.n_ch_in:
879
- issues.append(
880
- f"input port number {po_in.number} out-of-bounds for module, "
881
- f"must be between [0, {constraint.n_ch_in}).",
882
- )
883
- else:
884
- valid_pairs = constraint.ch_in_iq_pairs
885
- if not valid_pairs:
886
- issues.append("module does not support complex input channels.")
887
- else:
888
- po_in_i = ch_in.i_port
889
- po_in_q = ch_in.q_port
890
- if {po_in_i.number, po_in_q.number} not in valid_pairs:
959
+ match ch_in:
960
+ case IQMixedChannel():
961
+ po_in = ch_in.port
962
+ if constraint.n_ch_in == 0:
963
+ issues.append("module has no input ports.")
964
+ elif po_in.number < 0 or po_in.number >= constraint.n_ch_in:
965
+ issues.append(
966
+ f"input port number {po_in.number} out-of-bounds for module, "
967
+ f"must be between [0, {constraint.n_ch_in}).",
968
+ )
969
+ case IQChannel():
970
+ valid_pairs = constraint.ch_in_iq_pairs
971
+ if not valid_pairs:
972
+ issues.append("module does not support IQ input channels.")
973
+ else:
974
+ po_in_i = ch_in.i_port
975
+ po_in_q = ch_in.q_port
976
+ if {po_in_i.number, po_in_q.number} not in valid_pairs:
977
+ issues.append(
978
+ f"invalid input IQ pair {{{po_in_i.number}, {po_in_q.number}}}, "
979
+ f"module only supports pairs {valid_pairs}.",
980
+ )
981
+ case SingleChannel():
982
+ po_in = ch_in.port
983
+ if constraint.is_rf:
984
+ issues.append("RF modules cannot use single channels.")
985
+ if constraint.n_ch_in == 0:
986
+ issues.append("module has no input ports.")
987
+ elif po_in.number < 0 or po_in.number >= constraint.n_ch_in:
891
988
  issues.append(
892
- f"invalid input IQ pair {{{po_in_i.number}, {po_in_q.number}}}, module only "
893
- f"supports pairs {valid_pairs}.",
989
+ f"input port number {po_in.number} out-of-bounds for module, "
990
+ f"must be between [0, {constraint.n_ch_in}).",
894
991
  )
895
992
  return issues
@@ -61,7 +61,6 @@ class DeviceSummary(BaseModel):
61
61
  provider: str
62
62
  updated_at: ISO8601DatetimeUTCLike
63
63
  created_at: ISO8601DatetimeUTCLike
64
- copied_from: DeviceName | None = None
65
64
 
66
65
  def __str__(self):
67
66
  return f'DeviceSummary(name="{self.name}", id="{self.id}")'
@@ -142,6 +142,14 @@ class Coupler(Component[Literal["tunable"]]):
142
142
  default=ComponentParameter(value=0.0),
143
143
  json_schema_extra={"display": {"label": "bias_period", "unit": "V", "scale": 1}},
144
144
  )
145
+ freq_01: FloatComponentParameter = Field(
146
+ default=ComponentParameter(value=(0.0)),
147
+ json_schema_extra={"display": {"label": "freq_01", "unit": "MHz", "scale": 1e-6}},
148
+ )
149
+ anharm: FloatComponentParameter = Field(
150
+ default=ComponentParameter(value=0.0),
151
+ json_schema_extra={"display": {"label": "anharm", "unit": "MHz", "scale": 1e-6}},
152
+ )
145
153
 
146
154
 
147
155
  class Port(Component[Literal["drive", "readout", "flux"]]):
@@ -151,10 +159,19 @@ class Port(Component[Literal["drive", "readout", "flux"]]):
151
159
  )
152
160
 
153
161
 
162
+ # TODO: [SCUP-2169] Rename Filter to PurcellFilter
154
163
  class Filter(Component[Literal["filter"]]):
155
164
  dtype: Literal["filter"] = "filter"
156
165
  traits: list = Field(default=[])
157
166
 
167
+ # TODO: [SCUP-2167] Move resonator filter parameters to Filter/PurcellFilter class
168
+ # TODO: Use better name for this field. "Center Frequency"?
169
+ # TODO: Handle the same rename in the SDK
170
+ frequency: FloatComponentParameter = Field(
171
+ default=ComponentParameter(value=0.0),
172
+ json_schema_extra={"display": {"label": "frequency", "unit": "MHz", "scale": 1}},
173
+ )
174
+
158
175
 
159
176
  class Feedline(Component[Literal["feedline"]]):
160
177
  dtype: Literal["feedline"] = "feedline"
@@ -201,6 +218,7 @@ class ProcessorTemplate(BaseModel):
201
218
 
202
219
 
203
220
  class SuperconductingProcessorTemplate(BaseModel):
221
+ qpu_model: str
204
222
  build: list[TemplateParam]
205
223
  templates: dict[str, ProcessorTemplate]
206
224
  device_parameters: dict[str, SuperconductingComponentType] = Field(
@@ -209,12 +227,14 @@ class SuperconductingProcessorTemplate(BaseModel):
209
227
 
210
228
 
211
229
  class SuperconductingProcessor(BaseModel):
230
+ # TODO: Fast5, remove default after SCUP-2205
231
+ qpu_model: str = Field(default="quantware-soprano-a")
212
232
  nodes: dict[ComponentRef, SuperconductingComponentType]
213
233
  edges: list[Edge]
214
234
 
215
235
  @staticmethod
216
236
  def from_template(template: SuperconductingProcessorTemplate) -> "SuperconductingProcessor":
217
- _qpu = SuperconductingProcessor(nodes={}, edges=[])
237
+ _qpu = SuperconductingProcessor(qpu_model=template.qpu_model, nodes={}, edges=[])
218
238
  for build in template.build:
219
239
  qpu_template = template.templates[build.template]
220
240
  for idx, subs in build.vars.items():
@@ -45,7 +45,7 @@ class Chi01Scan(Experiment):
45
45
  measure_waveform : ConstantWaveform or None, optional
46
46
  The waveform to use for the measurement pulse.
47
47
  Defaults to the measurement defcal.
48
- run_mixer_calibration: bool
48
+ run_mixer_calibration: bool, optional
49
49
  Whether to run mixer calibrations before running a program. Defaults to False.
50
50
  update : "auto" or "off" or "prompt", optional
51
51
  How the device should be updated after an experiment run. Defaults to auto.
@@ -0,0 +1,65 @@
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
17
+
18
+ from .common import Experiment, LinspaceIterable, RangeIterable
19
+
20
+
21
+ class CZSpectroscopyByBias(Experiment):
22
+ """
23
+ Parameters for running a CZ spectroscopy by bias experiment.
24
+
25
+ Attributes
26
+ ----------
27
+ control_transmon : str
28
+ The reference for the transmon to use as control.
29
+ target_transmon : str
30
+ The reference for the transmon to use as target.
31
+ vps : LinspaceIterable
32
+ The voltage points to sample, in volts.
33
+ coupler_flux_vp : float
34
+ The flux voltage point for the coupler, in volts.
35
+ durations_ns : RangeIterable, optional
36
+ The pulse durations to sample, in nanoseconds.
37
+ Defaults to RangeIterable(start=16, stop=200, step=8).
38
+ prep_padding_ns : int, optional
39
+ The padding to apply before the CZ pulse, in nanoseconds.
40
+ Defaults to 16 ns.
41
+ measurement_padding_ns : int, optional
42
+ The padding to apply after the CZ pulse, in nanoseconds.
43
+ Defaults to 16 ns.
44
+ recycle_delay_ns : float, optional
45
+ The delay between consecutive shots, in nanoseconds.
46
+ Defaults to 500,000 ns.
47
+ shot_count : int, optional
48
+ The number of shots to take.
49
+ Defaults to 200.
50
+ update : "auto" or "off" or "prompt", optional
51
+ How the device should be updated after an experiment run. Defaults to auto.
52
+ """
53
+
54
+ _experiment_name: str = PrivateAttr("cz_spectroscopy_by_bias")
55
+
56
+ control_transmon: str
57
+ target_transmon: str
58
+ vps: LinspaceIterable
59
+ coupler_flux_vp: float
60
+ durations_ns: RangeIterable = RangeIterable(start=16, stop=200, step=8)
61
+ prep_padding_ns: int = 16
62
+ measurement_padding_ns: int = 16
63
+ recycle_delay_ns: int = 500_000
64
+ shot_count: int = 200
65
+ update: Literal["auto", "off", "prompt"] = "auto"
@@ -27,7 +27,7 @@ class DragLeakageCalibration(Experiment):
27
27
  ----------
28
28
  transmon : str
29
29
  The reference for the transmon to target.
30
- recycle_delay_ns : int
30
+ recycle_delay_ns : int, optional
31
31
  The delay between consecutive shots, in nanoseconds. Defaults to 10,000 ns.
32
32
  shot_count : int, optional
33
33
  The number of shots to take. Defaults to 1,000.
@@ -40,8 +40,6 @@ class DragLeakageCalibration(Experiment):
40
40
  anharmonicity : float or None, optional
41
41
  The anharmonicity of the transmon, in Hz.
42
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
43
  update : "auto" or "off" or "prompt", optional
46
44
  How the device should be updated after an experiment run. Defaults to auto.
47
45
  """
@@ -55,7 +53,6 @@ class DragLeakageCalibration(Experiment):
55
53
  repetitions: list[int]
56
54
  gate: Literal["x", "sx"]
57
55
  anharmonicity: float | None = None
58
- drag_guess: float
59
56
  update: Literal["auto", "off", "prompt"] = "auto"
60
57
 
61
58
  @field_validator("alphas")
@@ -30,12 +30,13 @@ class FineAmplitudeCalibration(Experiment):
30
30
  The reference for the transmon to target.
31
31
  gate : Literal["sx", "x"]
32
32
  The gate to be calibrated.
33
- repetitions : list[int]
33
+ repetitions : list[int], optional
34
34
  List of repetition counts for the calibration experiment.
35
- recycle_delay_ns : int
35
+ Defaults to every fourth number from 0 to 100.
36
+ recycle_delay_ns : int, optional
36
37
  The delay between consecutive shots, in nanoseconds. Defaults to 10,000 ns.
37
38
  shot_count : int, optional
38
- The number of shots to take. Defaults to 1000.
39
+ The number of shots to take. Defaults to 1,000.
39
40
  measure_waveform : ConstantWaveform or None, optional
40
41
  The waveform to use for the measurement pulse.
41
42
  Defaults to the measurement defcal.
@@ -39,7 +39,7 @@ class PowerRabi(Experiment):
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
- recycle_delay_ns : int
42
+ recycle_delay_ns : int, optional
43
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.
@@ -48,7 +48,7 @@ class PowerRabi(Experiment):
48
48
  measure_waveform : ConstantWaveform or None, optional
49
49
  The waveform to use for the measurement pulse.
50
50
  Defaults to the measurement defcal.
51
- run_mixer_calibration: bool
51
+ run_mixer_calibration: bool, optional
52
52
  Whether to run mixer calibrations before running a program. Defaults to False.
53
53
  update : "auto" or "off" or "prompt", optional
54
54
  How the device should be updated after an experiment run. Defaults to auto.
@@ -39,7 +39,7 @@ class PowerRabiEF(Experiment):
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
- recycle_delay_ns : int
42
+ recycle_delay_ns : int, optional
43
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.
@@ -48,7 +48,7 @@ class PowerRabiEF(Experiment):
48
48
  measure_waveform : ConstantWaveform or None, optional
49
49
  The waveform to use for the measurement pulse.
50
50
  Defaults to the measurement defcal.
51
- run_mixer_calibration: bool
51
+ run_mixer_calibration: bool, optional
52
52
  Whether to run mixer calibrations before running a program. Defaults to False.
53
53
  update : "auto" or "off" or "prompt", optional
54
54
  How the device should be updated after an experiment run. Defaults to auto.
@@ -42,7 +42,7 @@ class Ramsey(Experiment):
42
42
  measure_waveform : ConstantWaveform or None, optional
43
43
  The waveform to use for the measurement pulse.
44
44
  Defaults to the measurement defcal.
45
- run_mixer_calibration: bool
45
+ run_mixer_calibration: bool, optional
46
46
  Whether to run mixer calibrations before running a program. Defaults to False.
47
47
  update : "auto" or "off" or "prompt", optional
48
48
  How the device should be updated after an experiment run. Defaults to auto.
@@ -21,7 +21,7 @@ from .waveforms import ConstantWaveform
21
21
 
22
22
  class RamseyEF(Experiment):
23
23
  """
24
- Parameters for running a EF Ramsey experiment.
24
+ Parameters for running an EF Ramsey experiment.
25
25
 
26
26
  Attributes
27
27
  ----------
@@ -31,14 +31,14 @@ class ReadoutClassifier(Experiment):
31
31
  ----------
32
32
  transmon : str
33
33
  The reference for the transmon to target.
34
- recycle_delay_ns : int
34
+ recycle_delay_ns : int, optional
35
35
  The delay between consecutive shots, in nanoseconds. Defaults to 200,000 ns.
36
36
  shot_count : int, optional
37
37
  The number of shots to take. Defaults to 5,000.
38
38
  measure_waveform : ConstantWaveform or None, optional
39
39
  The waveform to use for the measurement pulse.
40
40
  Defaults to the measurement defcal.
41
- run_mixer_calibration: bool
41
+ run_mixer_calibration: bool, optional
42
42
  Whether to run mixer calibrations before running a program. Defaults to False.
43
43
  update : "auto" or "off" or "prompt", optional
44
44
  How the device should be updated after an experiment run. Defaults to auto.
@@ -28,7 +28,7 @@ class ReadoutOptimization(Experiment):
28
28
  """
29
29
  Parameters for optimizing the readout classifier.
30
30
 
31
- Parameters
31
+ Attributes
32
32
  ----------
33
33
  transmon : str
34
34
  The reference for the transmon to target.
@@ -36,11 +36,11 @@ class ReadoutOptimization(Experiment):
36
36
  The readout frequencies to sweep, in Hz.
37
37
  amplitudes : list[float]
38
38
  The readout amplitudes to sweep.
39
- recycle_delay_ns : int
39
+ recycle_delay_ns : int, optional
40
40
  The delay between consecutive shots, in nanoseconds. Defaults to 200,000 ns.
41
41
  shot_count : int, optional
42
42
  The number of shots to take. Defaults to 5,000.
43
- run_mixer_calibration: bool
43
+ run_mixer_calibration: bool, optional
44
44
  Whether to run mixer calibrations before running a program. Defaults to False.
45
45
  update : "auto" or "off", optional
46
46
  How the device should be updated after an experiment run. Defaults to auto.
@@ -44,7 +44,7 @@ class ResonatorSpectroscopy(Experiment):
44
44
  measure_waveform : ConstantWaveform or None, optional
45
45
  The waveform to use for the measurement pulse.
46
46
  Defaults to the measurement defcal.
47
- run_mixer_calibration: bool
47
+ run_mixer_calibration: bool, optional
48
48
  Whether to run mixer calibrations before running a program. Defaults to False.
49
49
  update : "auto" or "off" or "prompt", optional
50
50
  How the device should be updated after an experiment run. Defaults to auto.