boulder-opal-scale-up-sdk 1.0.0__py3-none-any.whl
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.
- boulder_opal_scale_up_sdk-1.0.0.dist-info/METADATA +38 -0
- boulder_opal_scale_up_sdk-1.0.0.dist-info/RECORD +50 -0
- boulder_opal_scale_up_sdk-1.0.0.dist-info/WHEEL +4 -0
- boulderopalscaleupsdk/__init__.py +14 -0
- boulderopalscaleupsdk/agent/__init__.py +29 -0
- boulderopalscaleupsdk/agent/worker.py +244 -0
- boulderopalscaleupsdk/common/__init__.py +12 -0
- boulderopalscaleupsdk/common/dtypes.py +353 -0
- boulderopalscaleupsdk/common/typeclasses.py +85 -0
- boulderopalscaleupsdk/device/__init__.py +16 -0
- boulderopalscaleupsdk/device/common.py +58 -0
- boulderopalscaleupsdk/device/config_loader.py +88 -0
- boulderopalscaleupsdk/device/controller/__init__.py +32 -0
- boulderopalscaleupsdk/device/controller/base.py +18 -0
- boulderopalscaleupsdk/device/controller/qblox.py +664 -0
- boulderopalscaleupsdk/device/controller/quantum_machines.py +139 -0
- boulderopalscaleupsdk/device/device.py +35 -0
- boulderopalscaleupsdk/device/processor/__init__.py +23 -0
- boulderopalscaleupsdk/device/processor/common.py +148 -0
- boulderopalscaleupsdk/device/processor/superconducting_processor.py +291 -0
- boulderopalscaleupsdk/experiments/__init__.py +44 -0
- boulderopalscaleupsdk/experiments/common.py +96 -0
- boulderopalscaleupsdk/experiments/power_rabi.py +60 -0
- boulderopalscaleupsdk/experiments/ramsey.py +55 -0
- boulderopalscaleupsdk/experiments/resonator_spectroscopy.py +64 -0
- boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_bias.py +76 -0
- boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_power.py +64 -0
- boulderopalscaleupsdk/grpc_interceptors/__init__.py +16 -0
- boulderopalscaleupsdk/grpc_interceptors/auth.py +101 -0
- boulderopalscaleupsdk/plotting/__init__.py +24 -0
- boulderopalscaleupsdk/plotting/dtypes.py +221 -0
- boulderopalscaleupsdk/protobuf/v1/agent_pb2.py +48 -0
- boulderopalscaleupsdk/protobuf/v1/agent_pb2.pyi +53 -0
- boulderopalscaleupsdk/protobuf/v1/agent_pb2_grpc.py +138 -0
- boulderopalscaleupsdk/protobuf/v1/device_pb2.py +71 -0
- boulderopalscaleupsdk/protobuf/v1/device_pb2.pyi +110 -0
- boulderopalscaleupsdk/protobuf/v1/device_pb2_grpc.py +274 -0
- boulderopalscaleupsdk/protobuf/v1/task_pb2.py +53 -0
- boulderopalscaleupsdk/protobuf/v1/task_pb2.pyi +118 -0
- boulderopalscaleupsdk/protobuf/v1/task_pb2_grpc.py +119 -0
- boulderopalscaleupsdk/py.typed +0 -0
- boulderopalscaleupsdk/routines/__init__.py +9 -0
- boulderopalscaleupsdk/routines/common.py +10 -0
- boulderopalscaleupsdk/routines/resonator_mapping.py +13 -0
- boulderopalscaleupsdk/third_party/__init__.py +14 -0
- boulderopalscaleupsdk/third_party/quantum_machines/__init__.py +51 -0
- boulderopalscaleupsdk/third_party/quantum_machines/config.py +597 -0
- boulderopalscaleupsdk/third_party/quantum_machines/constants.py +20 -0
- boulderopalscaleupsdk/utils/__init__.py +12 -0
- boulderopalscaleupsdk/utils/serial_utils.py +62 -0
@@ -0,0 +1,51 @@
|
|
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
|
+
# pyright: reportPrivateImportUsage=false
|
15
|
+
"""qm-qua imports.
|
16
|
+
|
17
|
+
This module standardizes all the qm-qua imports across the various versions we will
|
18
|
+
support.
|
19
|
+
"""
|
20
|
+
|
21
|
+
__all__ = [
|
22
|
+
"Constants",
|
23
|
+
"QuaExpression",
|
24
|
+
"QuaProgram",
|
25
|
+
"version",
|
26
|
+
]
|
27
|
+
|
28
|
+
|
29
|
+
import importlib.metadata
|
30
|
+
|
31
|
+
from packaging.version import Version
|
32
|
+
|
33
|
+
version = Version(importlib.metadata.version("qm-qua"))
|
34
|
+
if version >= Version("1.2.0"):
|
35
|
+
from qm.api.models.capabilities import OPX_FEM_IDX
|
36
|
+
from qm.program import Program as QuaProgram
|
37
|
+
from qm.qua._expressions import QuaExpression
|
38
|
+
else:
|
39
|
+
from qm.qua import Program as QuaProgram # type: ignore[attr-defined,no-redef]
|
40
|
+
from qm.qua._dsl import ( # type: ignore[attr-defined,no-redef]
|
41
|
+
_Expression as QuaExpression, # pyright: ignore[reportAttributeAccessIssue]
|
42
|
+
)
|
43
|
+
|
44
|
+
OPX_FEM_IDX = None # type: ignore[assignment]
|
45
|
+
|
46
|
+
|
47
|
+
class Constants:
|
48
|
+
"""QM-Qua constants."""
|
49
|
+
|
50
|
+
opx_fem_idx: int | None = OPX_FEM_IDX
|
51
|
+
"""The default FEM port for OPX. Only available for >=1.2.0"""
|
@@ -0,0 +1,597 @@
|
|
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
|
+
"""Qua Configuration.
|
15
|
+
|
16
|
+
Root model & managing qua versions
|
17
|
+
----------------------------------
|
18
|
+
A `QuaConfig` Pydantic RootModel definition should be the singular entrypoint for
|
19
|
+
parsing and validating Qua configuration dictionaries. This will use Pydantic's
|
20
|
+
discriminated union feature to automatically resolve the correct configuration version
|
21
|
+
as a function of the `version` string field.
|
22
|
+
|
23
|
+
See: https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions
|
24
|
+
|
25
|
+
Adding a new version
|
26
|
+
--------------------
|
27
|
+
To add a new configuration version:
|
28
|
+
|
29
|
+
1. If `_BaseQuaConfig` has fields NOT present in your new version, then update
|
30
|
+
`_BaseQuaConfig` to ensure it only contains common fields across all versions.
|
31
|
+
Correspondingly, update all existing subclasses of `_BaseQuaConfig` to explicitly
|
32
|
+
declare the removed field.
|
33
|
+
|
34
|
+
2. Subclass `_BaseQuaConfig` to create your new version.
|
35
|
+
|
36
|
+
* The class name should be `_QuaConfig<VersionTagSlug>`
|
37
|
+
* The class must have a PrivateAttr field `_qm_version_spec: str` that follows PyPA
|
38
|
+
version specifiers (see references).
|
39
|
+
|
40
|
+
Note: the overridden fields will need to use `# type: ignore[assignment]` as MyPy will
|
41
|
+
complain about mismatched types from the base class.
|
42
|
+
|
43
|
+
Type conventions
|
44
|
+
----------------
|
45
|
+
On using Generics for standard collections defined in `typing` module...
|
46
|
+
|
47
|
+
* As of Python 3.9 / PEP 585, the standard collection now implement generics,
|
48
|
+
removing the necessity of a parallel type hierarchy in the `typing` modules.
|
49
|
+
See: https://peps.python.org/pep-0585
|
50
|
+
* qm-qua still uses the alternative type hierarchy for its typing; however since we
|
51
|
+
do not intend to support Python <=3.9, this is not necessary for us.
|
52
|
+
* Hence, we will override their typing with the standard collection
|
53
|
+
(e.g. `Dict` -> `dict`).
|
54
|
+
|
55
|
+
On `typing.Mapping` vs `dict`...
|
56
|
+
|
57
|
+
* qm-qua 1.2.1 changes `typing.Dict` typing to `typing.Mapping` typing, the latter
|
58
|
+
being a more abstract/generic type that (only) defines the `__getitem__`,
|
59
|
+
`__len__`, and `__iter__` magic methods. Further, `typing.Mapping` is covariant
|
60
|
+
whilst `dict` is invariant.
|
61
|
+
* We should preference `typing.MutableMapping` wherever possible, since we want to
|
62
|
+
the broad support of `typing.Mapping`, but need to mutate the config as we build
|
63
|
+
programs.
|
64
|
+
* A type alias is added here `Mapping = typing.MutableMapping`
|
65
|
+
|
66
|
+
References
|
67
|
+
----------
|
68
|
+
For field enumeration:
|
69
|
+
|
70
|
+
* In `qm-qua` module: `qm.type_hinting.config_types`
|
71
|
+
* Configuration API: https://docs.quantum-machines.co/1.2.1/assets/qua_config.html
|
72
|
+
|
73
|
+
For validation details:
|
74
|
+
|
75
|
+
* In `qm-qua` module: `qm.program._qua_config_schema`
|
76
|
+
* In `qm-qua` module: `qm.program._qua_config_to_pb`
|
77
|
+
|
78
|
+
For information about PyPA's version specifiers:
|
79
|
+
|
80
|
+
* https://packaging.python.org/en/latest/specifications/version-specifiers/#id5
|
81
|
+
"""
|
82
|
+
|
83
|
+
# TODO: Migrate more validations from qm-qua.
|
84
|
+
|
85
|
+
# ruff: noqa: D101, UP007, N815, E741
|
86
|
+
from collections.abc import MutableMapping
|
87
|
+
from typing import Annotated, Any, Literal, TypeVar, Union
|
88
|
+
|
89
|
+
from packaging.specifiers import SpecifierSet
|
90
|
+
from packaging.version import Version
|
91
|
+
from pydantic import (
|
92
|
+
BaseModel,
|
93
|
+
BeforeValidator,
|
94
|
+
Discriminator,
|
95
|
+
Field,
|
96
|
+
PrivateAttr,
|
97
|
+
RootModel,
|
98
|
+
Tag,
|
99
|
+
field_validator,
|
100
|
+
model_validator,
|
101
|
+
)
|
102
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
103
|
+
|
104
|
+
from boulderopalscaleupsdk.common.dtypes import Self
|
105
|
+
|
106
|
+
Mapping = MutableMapping
|
107
|
+
Number = Union[int, float]
|
108
|
+
|
109
|
+
T = TypeVar("T", bound=BaseModel)
|
110
|
+
|
111
|
+
|
112
|
+
class Constants(BaseSettings):
|
113
|
+
# TODO: Revise how we propagate defaults
|
114
|
+
# We don't want to manage environments and environment overrides, esp since
|
115
|
+
# this will limit our deployment strategy. Consider a approach where default
|
116
|
+
# values are added to the requests schema, and enriched for all inbound
|
117
|
+
# requests as a function of customer. Roughly, something like this:
|
118
|
+
# Controller(customer) --> connect_to_processor(**customer_defaults) --> ...
|
119
|
+
|
120
|
+
model_config = SettingsConfigDict(
|
121
|
+
env_prefix="QCSU_QM_QUA_",
|
122
|
+
)
|
123
|
+
octave_n_rf_out: int = 5
|
124
|
+
octave_default_lo_freq: float = 4e9
|
125
|
+
|
126
|
+
|
127
|
+
CONST = Constants()
|
128
|
+
|
129
|
+
|
130
|
+
StandardPort = tuple[str, int, int]
|
131
|
+
PortReferenceType = Union[tuple[str, int], StandardPort]
|
132
|
+
|
133
|
+
|
134
|
+
class AnalogOutputFilterConfigType(BaseModel):
|
135
|
+
feedforward: list[float] = []
|
136
|
+
feedback: list[float] = []
|
137
|
+
|
138
|
+
|
139
|
+
def ensure_int(value: Any) -> Any:
|
140
|
+
match value:
|
141
|
+
case int():
|
142
|
+
return value
|
143
|
+
case str():
|
144
|
+
return int(value)
|
145
|
+
case _:
|
146
|
+
raise ValueError("Field must be convertible to int.")
|
147
|
+
|
148
|
+
|
149
|
+
IntLike = Annotated[int, BeforeValidator(ensure_int)]
|
150
|
+
|
151
|
+
|
152
|
+
class AnalogOutputPortConfigType(BaseModel):
|
153
|
+
offset: Number | None = None
|
154
|
+
filter: AnalogOutputFilterConfigType | None = None
|
155
|
+
delay: int | None = None
|
156
|
+
crosstalk: Mapping[IntLike, Number] = {}
|
157
|
+
shareable: bool | None = None
|
158
|
+
|
159
|
+
|
160
|
+
class AnalogInputPortConfigType(BaseModel):
|
161
|
+
offset: Number | None = None
|
162
|
+
gain_db: int | None = None
|
163
|
+
shareable: bool | None = None
|
164
|
+
sampling_rate: float | None = None
|
165
|
+
|
166
|
+
|
167
|
+
class DigitalOutputPortConfigType(BaseModel):
|
168
|
+
shareable: bool | None = None
|
169
|
+
inverted: bool | None = None
|
170
|
+
level: Literal["TTL", "LVTTL"] | None = None
|
171
|
+
|
172
|
+
|
173
|
+
class DigitalInputPortConfigType(BaseModel):
|
174
|
+
shareable: bool | None = None
|
175
|
+
deadtime: int | None = None
|
176
|
+
polarity: Literal["RISING", "FALLING"] | None = None
|
177
|
+
threshold: Number | None = None
|
178
|
+
|
179
|
+
|
180
|
+
class AnalogOutputPortConfigTypeOctoDac(BaseModel):
|
181
|
+
offset: Number | None = None
|
182
|
+
filter: AnalogOutputFilterConfigType | None = None
|
183
|
+
delay: int | None = None
|
184
|
+
crosstalk: Mapping[int, Number] = {}
|
185
|
+
shareable: bool | None = None
|
186
|
+
connectivity: tuple[str, str] | None = None
|
187
|
+
sampling_rate: float | None = None
|
188
|
+
upsampling_mode: Literal["mw", "pulse"] | None = None
|
189
|
+
output_mode: Literal["direct", "amplified"] | None = None
|
190
|
+
|
191
|
+
|
192
|
+
class LfFemConfigType(BaseModel):
|
193
|
+
type: Literal["LF"] | None = None
|
194
|
+
analog_outputs: Mapping[int | str, AnalogOutputPortConfigTypeOctoDac] = {}
|
195
|
+
analog_inputs: Mapping[int | str, AnalogInputPortConfigType] = {}
|
196
|
+
digital_outputs: Mapping[int | str, DigitalOutputPortConfigType] = {}
|
197
|
+
digital_inputs: Mapping[int | str, DigitalInputPortConfigType] = {}
|
198
|
+
|
199
|
+
|
200
|
+
Band = Literal[1, 2, 3]
|
201
|
+
|
202
|
+
|
203
|
+
class MwFemAnalogInputPortConfigType(BaseModel):
|
204
|
+
sampling_rate: float | None = None
|
205
|
+
gain_db: int | None = None
|
206
|
+
shareable: bool | None = None
|
207
|
+
band: Band | None = None
|
208
|
+
downconverter_frequency: float | None = None
|
209
|
+
|
210
|
+
|
211
|
+
class MwUpconverterConfigType(BaseModel):
|
212
|
+
frequency: float | None = None
|
213
|
+
|
214
|
+
|
215
|
+
class MwFemAnalogOutputPortConfigType(BaseModel):
|
216
|
+
sampling_rate: float | None = None
|
217
|
+
full_scale_power_dbm: int | None = None
|
218
|
+
band: Band | None = None
|
219
|
+
delay: int | None = None
|
220
|
+
shareable: bool | None = None
|
221
|
+
upconverters: Mapping[IntLike, MwUpconverterConfigType] = {}
|
222
|
+
upconverter_frequency: float | None = None
|
223
|
+
|
224
|
+
|
225
|
+
class MwFemConfigType(BaseModel):
|
226
|
+
type: Literal["MW"] | None = None
|
227
|
+
analog_outputs: Mapping[int | str, MwFemAnalogOutputPortConfigType] = {}
|
228
|
+
analog_inputs: Mapping[int | str, MwFemAnalogInputPortConfigType] = {}
|
229
|
+
digital_outputs: Mapping[int | str, DigitalOutputPortConfigType] = {}
|
230
|
+
digital_inputs: Mapping[int | str, DigitalInputPortConfigType] = {}
|
231
|
+
|
232
|
+
|
233
|
+
class ControllerConfigType(BaseModel):
|
234
|
+
type: Literal["opx", "opx1"] | None = None
|
235
|
+
analog_outputs: Mapping[int | str, AnalogOutputPortConfigType] = {}
|
236
|
+
analog_inputs: Mapping[int | str, AnalogInputPortConfigType] = {}
|
237
|
+
digital_outputs: Mapping[int | str, DigitalOutputPortConfigType] = {}
|
238
|
+
digital_inputs: Mapping[int | str, DigitalInputPortConfigType] = {}
|
239
|
+
|
240
|
+
|
241
|
+
class OctaveRFOutputConfigType(BaseModel):
|
242
|
+
LO_frequency: float = Field(default=CONST.octave_default_lo_freq, ge=2e9, le=18e9)
|
243
|
+
LO_source: Literal["internal", "external"] = "internal"
|
244
|
+
output_mode: Literal[
|
245
|
+
"always_on",
|
246
|
+
"always_off",
|
247
|
+
"triggered",
|
248
|
+
"triggered_reversed",
|
249
|
+
] = "always_off"
|
250
|
+
gain: int | float = Field(default=0, ge=-20, le=20, multiple_of=0.5)
|
251
|
+
input_attenuators: Literal["ON", "OFF"] = "OFF"
|
252
|
+
I_connection: PortReferenceType | None = None
|
253
|
+
Q_connection: PortReferenceType | None = None
|
254
|
+
|
255
|
+
|
256
|
+
_RF_SOURCES = Literal[
|
257
|
+
"RF_in",
|
258
|
+
"loopback_1",
|
259
|
+
"loopback_2",
|
260
|
+
"loopback_3",
|
261
|
+
"loopback_4",
|
262
|
+
"loopback_5",
|
263
|
+
]
|
264
|
+
|
265
|
+
|
266
|
+
class OctaveRFInputConfigType(BaseModel):
|
267
|
+
RF_source: _RF_SOURCES | None = None
|
268
|
+
LO_frequency: float | None = None
|
269
|
+
LO_source: Literal["internal", "external", "analyzer"] | None = None
|
270
|
+
IF_mode_I: Literal["direct", "mixer", "envelope", "off"] | None = None
|
271
|
+
IF_mode_Q: Literal["direct", "mixer", "envelope", "off"] | None = None
|
272
|
+
|
273
|
+
|
274
|
+
class OctaveSingleIfOutputConfigType(BaseModel):
|
275
|
+
port: PortReferenceType | None = None
|
276
|
+
name: str | None = None
|
277
|
+
|
278
|
+
|
279
|
+
class OctaveIfOutputsConfigType(BaseModel):
|
280
|
+
IF_out1: OctaveSingleIfOutputConfigType | None = None
|
281
|
+
IF_out2: OctaveSingleIfOutputConfigType | None = None
|
282
|
+
|
283
|
+
|
284
|
+
FEM_IDX = Literal[1, 2, 3, 4, 5, 6, 7, 8, "1", "2", "3", "4", "5", "6", "7", "8"]
|
285
|
+
|
286
|
+
|
287
|
+
class OPX1000ControllerConfigType(BaseModel):
|
288
|
+
type: Literal["opx1000"] | None = None
|
289
|
+
fems: Mapping[FEM_IDX, LfFemConfigType | MwFemConfigType] = {}
|
290
|
+
|
291
|
+
|
292
|
+
LoopbackType = tuple[
|
293
|
+
tuple[str, Literal["Synth1", "Synth2", "Synth3", "Synth4", "Synth5"]],
|
294
|
+
Literal["Dmd1LO", "Dmd2LO", "LO1", "LO2", "LO3", "LO4", "LO5"],
|
295
|
+
]
|
296
|
+
|
297
|
+
|
298
|
+
class OctaveConfig(BaseModel):
|
299
|
+
"""Octave configuration for qm-qua 1.1.7."""
|
300
|
+
|
301
|
+
RF_outputs: Mapping[IntLike, OctaveRFOutputConfigType] = {}
|
302
|
+
"""
|
303
|
+
RF Outputs in Octave's up-converter chain.
|
304
|
+
OPX/AnalogOutput -> Octave/IFInput -> Octave/RFOutput -> Fridge.
|
305
|
+
"""
|
306
|
+
|
307
|
+
RF_inputs: Mapping[IntLike, OctaveRFInputConfigType] = {}
|
308
|
+
"""RF Inputs in Octave's down-converter chain. See IF_Outputs."""
|
309
|
+
|
310
|
+
IF_outputs: OctaveIfOutputsConfigType | None = None
|
311
|
+
"""
|
312
|
+
IF Outputs in Octave's down-converter chain.
|
313
|
+
Fridge -> Octave/RFInput -> Octave/IFOutput -> OPX/AnalogInput
|
314
|
+
"""
|
315
|
+
|
316
|
+
loopbacks: list[LoopbackType] = []
|
317
|
+
"""
|
318
|
+
Loopbacks connected to Octave.
|
319
|
+
Each loopback is ((octave_name, octave_port), target_port).
|
320
|
+
"""
|
321
|
+
|
322
|
+
connectivity: str | None = None
|
323
|
+
"""
|
324
|
+
Default connectivity to OPX (either in host, or host,FEM_IDX format).
|
325
|
+
This cannot be set when RF_outputs I/Q connections are set.
|
326
|
+
"""
|
327
|
+
|
328
|
+
@model_validator(mode="after")
|
329
|
+
def validate_connectivity(self) -> Self:
|
330
|
+
if self.connectivity is not None:
|
331
|
+
for output_num, output in self.RF_outputs.items():
|
332
|
+
if output.I_connection or output.Q_connection:
|
333
|
+
raise ValueError(
|
334
|
+
"Octave has ambiguous connectivity; "
|
335
|
+
f"both connectivity set and RF outputs set for {output_num}",
|
336
|
+
)
|
337
|
+
return self
|
338
|
+
|
339
|
+
|
340
|
+
class OctaveConfig121(OctaveConfig):
|
341
|
+
"""Octave configuration for qm-qua 1.2.1."""
|
342
|
+
|
343
|
+
connectivity: Union[str, tuple[str, int]] | None = None # type: ignore[assignment]
|
344
|
+
|
345
|
+
|
346
|
+
class DigitalInputConfigType(BaseModel):
|
347
|
+
delay: int | None = None
|
348
|
+
buffer: int | None = None
|
349
|
+
port: PortReferenceType | None = None
|
350
|
+
|
351
|
+
|
352
|
+
class IntegrationWeightConfigType(BaseModel):
|
353
|
+
cosine: list[tuple[float, int]] | list[float] = []
|
354
|
+
sine: list[tuple[float, int]] | list[float] = []
|
355
|
+
|
356
|
+
|
357
|
+
class ConstantWaveFormConfigType(BaseModel):
|
358
|
+
type: Literal["constant"] | None = None
|
359
|
+
sample: float | None = None
|
360
|
+
|
361
|
+
|
362
|
+
class CompressedWaveFormConfigType(BaseModel):
|
363
|
+
type: str | None = None
|
364
|
+
samples: list[float] = []
|
365
|
+
sample_rate: float | None = None
|
366
|
+
|
367
|
+
|
368
|
+
class ArbitraryWaveFormConfigType(BaseModel):
|
369
|
+
type: Literal["arbitrary"] | None = None
|
370
|
+
samples: list[float] = []
|
371
|
+
max_allowed_error: float | None = None
|
372
|
+
sampling_rate: Number | None = None
|
373
|
+
is_overridable: bool | None = None
|
374
|
+
|
375
|
+
|
376
|
+
class DigitalWaveformConfigType(BaseModel):
|
377
|
+
samples: list[tuple[int, int]] = []
|
378
|
+
|
379
|
+
|
380
|
+
class MixerConfigType(BaseModel):
|
381
|
+
intermediate_frequency: float | None = None
|
382
|
+
lo_frequency: float | None = None
|
383
|
+
correction: tuple[Number, Number, Number, Number] | None = None
|
384
|
+
|
385
|
+
|
386
|
+
class PulseConfigType(BaseModel):
|
387
|
+
operation: Literal["measurement", "control"] | None = None
|
388
|
+
length: int | None = None
|
389
|
+
waveforms: Mapping[str, str] = {}
|
390
|
+
digital_marker: str | None = None
|
391
|
+
integration_weights: Mapping[str, str] = {}
|
392
|
+
|
393
|
+
|
394
|
+
class SingleInputConfigType(BaseModel):
|
395
|
+
port: PortReferenceType | None = None
|
396
|
+
|
397
|
+
|
398
|
+
class MwInputConfigType(BaseModel):
|
399
|
+
port: PortReferenceType | None = None
|
400
|
+
upconverter: int | None = None
|
401
|
+
|
402
|
+
|
403
|
+
class MwOutputConfigType(BaseModel):
|
404
|
+
port: PortReferenceType | None = None
|
405
|
+
|
406
|
+
|
407
|
+
class HoldOffsetConfigType(BaseModel):
|
408
|
+
duration: int | None = None
|
409
|
+
|
410
|
+
|
411
|
+
class StickyConfigType(BaseModel):
|
412
|
+
analog: bool | None = None
|
413
|
+
digital: bool | None = None
|
414
|
+
duration: int | None = None
|
415
|
+
|
416
|
+
|
417
|
+
class MixInputConfigType(BaseModel):
|
418
|
+
I: PortReferenceType | None = None
|
419
|
+
Q: PortReferenceType | None = None
|
420
|
+
mixer: str | None = None
|
421
|
+
lo_frequency: float | None = None
|
422
|
+
|
423
|
+
|
424
|
+
class InputCollectionConfigType(BaseModel):
|
425
|
+
inputs: Mapping[str, PortReferenceType] = {}
|
426
|
+
|
427
|
+
|
428
|
+
class OscillatorConfigType(BaseModel):
|
429
|
+
intermediate_frequency: float | None = None
|
430
|
+
mixer: str | None = None
|
431
|
+
lo_frequency: float | None = None
|
432
|
+
|
433
|
+
|
434
|
+
class OutputPulseParameterConfigType(BaseModel):
|
435
|
+
signalThreshold: int | None = None
|
436
|
+
signalPolarity: Literal["ABOVE", "ASCENDING", "BELOW", "DESCENDING"] | None = None
|
437
|
+
derivativeThreshold: int | None = None
|
438
|
+
derivativePolarity: Literal["ABOVE", "ASCENDING", "BELOW", "DESCENDING"] | None = None
|
439
|
+
|
440
|
+
|
441
|
+
class ElementConfig(BaseModel):
|
442
|
+
"""ElementConfigType for qm-qua 1.1.7."""
|
443
|
+
|
444
|
+
intermediate_frequency: float | None = None
|
445
|
+
oscillator: str | None = None
|
446
|
+
measurement_qe: str | None = None
|
447
|
+
operations: Mapping[str, str] = {}
|
448
|
+
singleInput: SingleInputConfigType | None = None
|
449
|
+
mixInputs: MixInputConfigType | None = None
|
450
|
+
singleInputCollection: InputCollectionConfigType | None = None
|
451
|
+
multipleInputs: InputCollectionConfigType | None = None
|
452
|
+
time_of_flight: int | None = None
|
453
|
+
smearing: int | None = None
|
454
|
+
outputs: Mapping[str, PortReferenceType] = {}
|
455
|
+
digitalInputs: Mapping[str, DigitalInputConfigType] | None = None
|
456
|
+
digitalOutputs: Mapping[str, PortReferenceType] | None = None
|
457
|
+
outputPulseParameters: OutputPulseParameterConfigType | None = None
|
458
|
+
hold_offset: HoldOffsetConfigType | None = None
|
459
|
+
sticky: StickyConfigType | None = None
|
460
|
+
thread: str | None = None
|
461
|
+
RF_inputs: Mapping[str, tuple[str, int]] | None = None
|
462
|
+
RF_outputs: Mapping[str, tuple[str, int]] | None = None
|
463
|
+
|
464
|
+
@model_validator(mode="after")
|
465
|
+
def validator_oscillators(self) -> Self:
|
466
|
+
if self.intermediate_frequency and self.oscillator:
|
467
|
+
raise ValueError(
|
468
|
+
"Intermediate frequency and oscillator cannot be defined together.",
|
469
|
+
)
|
470
|
+
return self
|
471
|
+
|
472
|
+
|
473
|
+
class ElementConfig121(ElementConfig):
|
474
|
+
"""ElementConfig for qm-qua 1.2.1."""
|
475
|
+
|
476
|
+
MWInput: MwInputConfigType | None = None
|
477
|
+
MWOutput: MwOutputConfigType | None = None
|
478
|
+
|
479
|
+
@model_validator(mode="after")
|
480
|
+
def validate_outputs(self) -> Self:
|
481
|
+
if self.RF_outputs:
|
482
|
+
if self.smearing is None:
|
483
|
+
raise ValueError("Element with output must have smearing defined.")
|
484
|
+
if self.time_of_flight is None:
|
485
|
+
raise ValueError(
|
486
|
+
"Element with output must have time_of_flight defined.",
|
487
|
+
)
|
488
|
+
else:
|
489
|
+
if self.smearing:
|
490
|
+
raise ValueError("smearing only for elements with outputs.")
|
491
|
+
if self.time_of_flight:
|
492
|
+
raise ValueError("time_of_flight only for elements with outputs.")
|
493
|
+
|
494
|
+
return self
|
495
|
+
|
496
|
+
|
497
|
+
class _BaseQuaConfig(BaseModel):
|
498
|
+
"""
|
499
|
+
Base Qua configuration.
|
500
|
+
|
501
|
+
Based off 1.1.7; newer versions should shadow fields that need updating.
|
502
|
+
"""
|
503
|
+
|
504
|
+
_qm_version_spec: str
|
505
|
+
"""The version specification that this model support.
|
506
|
+
Uses PyPA version specifiers."""
|
507
|
+
|
508
|
+
qm_version: str = Field(exclude=True)
|
509
|
+
"""The qm-qua package version used."""
|
510
|
+
|
511
|
+
version: str = "1"
|
512
|
+
"""The configuration version. This is a field used in Qua's configuration API."""
|
513
|
+
|
514
|
+
oscillators: Mapping[str, OscillatorConfigType] = {}
|
515
|
+
"""The oscillators used to drive the elements."""
|
516
|
+
|
517
|
+
elements: Mapping[str, ElementConfig] = {}
|
518
|
+
"""Elements represents a controllable entity wired to a port on the controller."""
|
519
|
+
|
520
|
+
controllers: Mapping[str, ControllerConfigType | OPX1000ControllerConfigType] = {}
|
521
|
+
"""The controllers."""
|
522
|
+
|
523
|
+
octaves: Mapping[str, OctaveConfig] = {}
|
524
|
+
"""Any octaves in the stack."""
|
525
|
+
|
526
|
+
integration_weights: Mapping[str, IntegrationWeightConfigType] = {}
|
527
|
+
"""The integration weight vectors used in the integration and demodulation of data
|
528
|
+
returning from a element."""
|
529
|
+
|
530
|
+
waveforms: Mapping[
|
531
|
+
str,
|
532
|
+
ArbitraryWaveFormConfigType | ConstantWaveFormConfigType | CompressedWaveFormConfigType,
|
533
|
+
] = {}
|
534
|
+
"""The analog waveforms sent to an element when a pulse is played."""
|
535
|
+
|
536
|
+
digital_waveforms: Mapping[str, DigitalWaveformConfigType] = {}
|
537
|
+
"""The digital waveforms sent to an element when a pulse is played."""
|
538
|
+
|
539
|
+
pulses: Mapping[str, PulseConfigType] = {}
|
540
|
+
"""The pulses to be played to the elements."""
|
541
|
+
|
542
|
+
mixers: Mapping[str, list[MixerConfigType]] = {}
|
543
|
+
"""The IQ mixer calibration properties, used to post-shape the pulse to compensate
|
544
|
+
for imperfections in the mixers used for up-converting the analog waveforms."""
|
545
|
+
|
546
|
+
@field_validator("qm_version")
|
547
|
+
@classmethod
|
548
|
+
def validate_qm_version(cls, vs: str):
|
549
|
+
if Version(vs) not in SpecifierSet(cls._qm_version_spec.default): # type: ignore[attr-defined]
|
550
|
+
raise ValueError(f"qm-qua version {vs} not supported.")
|
551
|
+
return vs
|
552
|
+
|
553
|
+
|
554
|
+
class _QuaConfig117(_BaseQuaConfig):
|
555
|
+
_qm_version_spec: str = PrivateAttr("~=1.1.7")
|
556
|
+
|
557
|
+
|
558
|
+
class _QuaConfig121(_BaseQuaConfig):
|
559
|
+
_qm_version_spec: str = PrivateAttr("~=1.2.1")
|
560
|
+
elements: Mapping[str, ElementConfig121] = {} # type: ignore[assignment]
|
561
|
+
octaves: Mapping[str, OctaveConfig121] = {} # type: ignore[assignment]
|
562
|
+
|
563
|
+
|
564
|
+
SUPPORTED_VERSION_SPECS: dict[str, SpecifierSet] = {
|
565
|
+
# Nb. the `attr-defined` mypy flag is because Pydantic converts private attributes
|
566
|
+
# into pydantic.fields.ModelPrivateAttr, and the value is set in the default.
|
567
|
+
cls._qm_version_spec.default: SpecifierSet(cls._qm_version_spec.default) # type: ignore[attr-defined]
|
568
|
+
for cls in _BaseQuaConfig.__subclasses__()
|
569
|
+
}
|
570
|
+
|
571
|
+
|
572
|
+
def _get_version(data: dict | BaseModel):
|
573
|
+
"""
|
574
|
+
Resolve the correct version Tag from the configuration data.
|
575
|
+
|
576
|
+
Configuration data version will be
|
577
|
+
"""
|
578
|
+
version_str = (
|
579
|
+
data.qm_version if isinstance(data, BaseModel) else data.get("qm_version") # type: ignore[attr-defined]
|
580
|
+
)
|
581
|
+
if not version_str:
|
582
|
+
raise AttributeError("No version specified")
|
583
|
+
|
584
|
+
version = Version(version_str)
|
585
|
+
|
586
|
+
for spec_name, spec in SUPPORTED_VERSION_SPECS.items():
|
587
|
+
if version in spec:
|
588
|
+
return spec_name
|
589
|
+
|
590
|
+
raise ValueError(f"Version {version_str} not supported")
|
591
|
+
|
592
|
+
|
593
|
+
class QuaConfig(RootModel):
|
594
|
+
root: Annotated[
|
595
|
+
Annotated[_QuaConfig117, Tag("~=1.1.7")] | Annotated[_QuaConfig121, Tag("~=1.2.1")],
|
596
|
+
Discriminator(_get_version),
|
597
|
+
]
|
@@ -0,0 +1,20 @@
|
|
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 boulderopalscaleupsdk.common.dtypes import Duration, TimeUnit
|
15
|
+
|
16
|
+
# See https://docs.quantum-machines.co/latest/docs/API_references/qua/dsl_main/?h=clock+cycle#qm.qua._dsl.wait
|
17
|
+
QUA_CLOCK_CYCLE = Duration(4, TimeUnit.NS)
|
18
|
+
MIN_TIME_OF_FLIGHT = Duration(24, TimeUnit.NS)
|
19
|
+
|
20
|
+
QUA_MAX_DELAY = Duration(2**31 - 1, TimeUnit.NS)
|
@@ -0,0 +1,12 @@
|
|
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.
|