boulder-opal-scale-up-sdk 1.0.3__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.
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/PKG-INFO +1 -1
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/agent/worker.py +23 -4
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/common/dtypes.py +31 -1
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/config_loader.py +1 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/controller/qblox.py +222 -25
- boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/device/device.py → boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/device/defcal.py +7 -9
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/device/device.py +67 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/processor/__init__.py +0 -2
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/processor/common.py +1 -6
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/processor/superconducting_processor.py +34 -7
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/__init__.py +24 -2
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/chi01_scan.py +18 -15
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/common.py +0 -16
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/drag_leakage_calibration.py +66 -0
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/fine_amplitude_calibration.py +54 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/power_rabi.py +22 -16
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/power_rabi_ef.py +67 -0
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/ramsey.py +62 -0
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/readout_classifier_calibration.py +50 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/resonator_spectroscopy.py +20 -18
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_bias.py +23 -20
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_power.py +22 -21
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/t1.py +16 -22
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/t2.py +51 -0
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/t2_echo.py +51 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/transmon_anharmonicity.py +26 -24
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/experiments/transmon_spectroscopy.py +19 -17
- boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/experiments/t2.py → boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/voltage_bias_fine_tune.py +22 -12
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/waveforms.py +63 -0
- boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/experiments/ramsey.py → boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/experiments/zz_ramsey.py +19 -19
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/plotting/dtypes.py +6 -3
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/agent_pb2.py +9 -3
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/agent_pb2.pyi +14 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/agent_pb2_grpc.py +34 -0
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/protobuf/v1/device_pb2.py +89 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/device_pb2.pyi +8 -4
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/routines/__init__.py +30 -0
- boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/device/__init__.py → boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/routines/common.py +9 -5
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/routines/one_qubit_calibration.py +36 -0
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/routines/resonator_mapping.py +35 -0
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/routines/transmon_coherence.py +34 -0
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/routines/transmon_discovery.py +41 -0
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/routines/transmon_retuning.py +41 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/stubs/maps.py +11 -2
- boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/utils/__init__.py +12 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/pyproject.toml +2 -4
- boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/device/defcal.py +0 -62
- boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/experiments/readout_classifier_calibration.py +0 -28
- boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/protobuf/v1/device_pb2.py +0 -89
- boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/routines/__init__.py +0 -6
- boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/routines/common.py +0 -10
- boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/routines/resonator_mapping.py +0 -19
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/LICENSE +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/README.md +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/__init__.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/agent/__init__.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/common/__init__.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/common/typeclasses.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/stubs → boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/device}/__init__.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/common.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/controller/__init__.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/controller/base.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/controller/quantum_machines.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/device/controller/resolver.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/grpc_interceptors/__init__.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/grpc_interceptors/auth.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/plotting/__init__.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/device_pb2_grpc.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/task_pb2.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/task_pb2.pyi +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/protobuf/v1/task_pb2_grpc.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/py.typed +0 -0
- {boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/utils → boulder_opal_scale_up_sdk-1.0.5/boulderopalscaleupsdk/stubs}/__init__.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/stubs/dtypes.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/third_party/__init__.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/third_party/quantum_machines/__init__.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/third_party/quantum_machines/config.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.5}/boulderopalscaleupsdk/third_party/quantum_machines/constants.py +0 -0
- {boulder_opal_scale_up_sdk-1.0.3 → 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.
|
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
|
-
|
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
|
|
@@ -28,7 +28,7 @@ from typing import Annotated, Any, Literal, Self
|
|
28
28
|
|
29
29
|
import numpy as np
|
30
30
|
from dateutil.parser import isoparse
|
31
|
-
from pydantic import BeforeValidator, ConfigDict, Field, PlainSerializer, TypeAdapter
|
31
|
+
from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, PlainSerializer, TypeAdapter
|
32
32
|
from pydantic.dataclasses import dataclass
|
33
33
|
|
34
34
|
GrpcMetadata = list[tuple[str, str | bytes]]
|
@@ -317,6 +317,36 @@ class JobHistorySortOrder(enum.Enum):
|
|
317
317
|
CREATED_AT_ASC = 2
|
318
318
|
|
319
319
|
|
320
|
+
class JobSummary(BaseModel):
|
321
|
+
id: str
|
322
|
+
name: str
|
323
|
+
device_name: str
|
324
|
+
session_id: str
|
325
|
+
created_at: ISO8601DatetimeUTCLike
|
326
|
+
|
327
|
+
def __str__(self):
|
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]
|
348
|
+
|
349
|
+
|
320
350
|
DEFAULT_JOB_HISTORY_PAGE = 1
|
321
351
|
DEFAULT_JOB_HISTORY_PAGE_SIZE = 10
|
322
352
|
DEFAULT_JOB_HISTORY_SORT_ORDER = JobHistorySortOrder.CREATED_AT_DESC
|
@@ -34,6 +34,7 @@ class ProcessorArchitecture(str, Enum):
|
|
34
34
|
Superconducting = "superconducting"
|
35
35
|
|
36
36
|
|
37
|
+
# TODO: Remove this in the next release of SDK
|
37
38
|
class DeviceInfo(BaseModel):
|
38
39
|
controller_info: QBLOXControllerInfo | QuantumMachinesControllerInfo
|
39
40
|
processor: SuperconductingProcessor # | OtherSDKProcessorType
|
@@ -18,6 +18,7 @@ QBLOX Quantum Control Stack
|
|
18
18
|
__all__ = (
|
19
19
|
"DEFAULT_MODULE_CONSTRAINTS",
|
20
20
|
"AcquisitionConfig",
|
21
|
+
"BitStrideArrayEncoding",
|
21
22
|
"ChannelType",
|
22
23
|
"ComplexChannel",
|
23
24
|
"IndexedData",
|
@@ -40,15 +41,19 @@ __all__ = (
|
|
40
41
|
"SequenceProgram",
|
41
42
|
"SequencerAddr",
|
42
43
|
"SequencerAddrType",
|
44
|
+
"SequencerResults",
|
45
|
+
"process_sequencer_output",
|
43
46
|
"validate_channel",
|
44
47
|
)
|
45
48
|
|
46
49
|
import dataclasses
|
47
50
|
import enum
|
51
|
+
import math
|
48
52
|
import re
|
49
53
|
from dataclasses import dataclass
|
50
54
|
from typing import Annotated, Any, ClassVar, Literal, Self, TypeVar
|
51
55
|
|
56
|
+
import numpy as np
|
52
57
|
from pydantic import BaseModel, BeforeValidator, Field, PlainSerializer, model_validator
|
53
58
|
|
54
59
|
from boulderopalscaleupsdk.device.controller.base import Backend, ControllerType
|
@@ -252,18 +257,18 @@ ChannelType = RealChannel | ComplexChannel
|
|
252
257
|
# ==================================================================================================
|
253
258
|
# Controller information
|
254
259
|
# ==================================================================================================
|
255
|
-
class
|
260
|
+
class PortConnection(BaseModel): # pragma: no cover
|
256
261
|
"""
|
257
|
-
The connections involved for a
|
262
|
+
The connections involved for a QPU port.
|
258
263
|
|
259
264
|
Attributes
|
260
265
|
----------
|
261
266
|
ch_out: ChannelType
|
262
|
-
The output channel that will signal towards the
|
263
|
-
ch_in: ChannelType, optional
|
264
|
-
The input channel from which signals will be acquired from the
|
265
|
-
not all modules support acquisitions. If an input channel is specified, it must be
|
266
|
-
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.
|
267
272
|
|
268
273
|
Notes
|
269
274
|
-----
|
@@ -271,20 +276,21 @@ class ElementConnection(BaseModel): # pragma: no cover
|
|
271
276
|
direction is outwards from the control stack. The following diagram depicts a simple setup with
|
272
277
|
the arrows indicating a control channel.
|
273
278
|
|
274
|
-
┌────────┐
|
275
|
-
│ │─── out ──►│
|
276
|
-
│ QBLOX │
|
277
|
-
│ Stack │
|
278
|
-
│ │─── out ──►│
|
279
|
-
│ │◄── in ────│
|
280
|
-
└────────┘
|
279
|
+
┌────────┐ ┌───────────────┐
|
280
|
+
│ │─── out ──►│ Port: p_xy1 │
|
281
|
+
│ QBLOX │ └───────────────┘
|
282
|
+
│ Stack │ ┌───────────────┐
|
283
|
+
│ │─── out ──►│ Port: p_flrr0 │
|
284
|
+
│ │◄── in ────│ │
|
285
|
+
└────────┘ └───────────────┘
|
286
|
+
QPU fridge
|
281
287
|
"""
|
282
288
|
|
283
289
|
ch_out: ChannelType
|
284
290
|
ch_in: ChannelType | None = None
|
285
291
|
|
286
292
|
@model_validator(mode="after")
|
287
|
-
def validate_channels(self) -> "
|
293
|
+
def validate_channels(self) -> "PortConnection":
|
288
294
|
if self.ch_in is not None and self.ch_in.module != self.ch_out.module:
|
289
295
|
raise ValueError("I/O channels for an element must be on the same module.")
|
290
296
|
return self
|
@@ -304,38 +310,102 @@ class QBLOXControllerInfo(BaseModel): # pragma: no cover
|
|
304
310
|
The type of controller, which is always `ControllerType.QBLOX` for this class.
|
305
311
|
modules: dict[ModuleAddrType, ModuleType]
|
306
312
|
The modules connected to the QBLOX stack.
|
307
|
-
|
308
|
-
The
|
313
|
+
port_config: dict[str, PortConnection]
|
314
|
+
The dictionary of ports with their types and addresses.
|
309
315
|
"""
|
310
316
|
|
311
317
|
controller_type: Literal[ControllerType.QBLOX] = ControllerType.QBLOX
|
312
318
|
modules: dict[ModuleAddrType, ModuleType]
|
313
|
-
|
319
|
+
port_config: dict[str, PortConnection]
|
314
320
|
|
315
321
|
|
316
322
|
# ==================================================================================================
|
317
323
|
# Instrument management
|
318
324
|
# ==================================================================================================
|
319
325
|
class SequencerParams(BaseModel):
|
320
|
-
nco_freq: float | None = Field(default=None
|
326
|
+
nco_freq: float | None = Field(default=None)
|
321
327
|
gain_awg_path0: float | None = Field(default=None, ge=-1.0, le=1.0)
|
322
328
|
offset_awg_path0: float | None = Field(default=None, ge=-1.0, le=1.0)
|
323
329
|
gain_awg_path1: float | None = Field(default=None, ge=-1.0, le=1.0)
|
324
330
|
offset_awg_path1: float | None = Field(default=None, ge=-1.0, le=1.0)
|
325
331
|
marker_ovr_en: bool | None = Field(default=None)
|
326
|
-
marker_ovr_value: int | None = Field(default=None)
|
332
|
+
marker_ovr_value: int | None = Field(default=None, ge=0, le=15)
|
327
333
|
mod_en_awg: bool | None = Field(default=None)
|
328
334
|
demod_en_acq: bool | None = Field(default=None)
|
329
335
|
sync_en: bool | None = Field(default=None)
|
330
336
|
nco_prop_delay_comp_en: bool | None = Field(default=True)
|
331
|
-
integration_length_acq: int | None = Field(default=None)
|
337
|
+
integration_length_acq: int | None = Field(default=None, ge=4, le=16777212, multiple_of=4)
|
332
338
|
|
333
339
|
|
334
|
-
class
|
335
|
-
|
336
|
-
|
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
|
+
|
337
360
|
out0_lo_freq: float | None = Field(default=None, gt=0)
|
338
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}!")
|
339
409
|
|
340
410
|
|
341
411
|
# ==================================================================================================
|
@@ -373,6 +443,7 @@ class SequenceProgram(BaseModel):
|
|
373
443
|
weights: dict[str, IndexedData] = {}
|
374
444
|
acquisitions: dict[str, AcquisitionConfig] = {}
|
375
445
|
acquisition_scopes: list[str] = []
|
446
|
+
acquisition_shapes: dict[str, tuple[int, ...]] = {}
|
376
447
|
params: SequencerParams = SequencerParams()
|
377
448
|
params_only: bool = False
|
378
449
|
|
@@ -408,7 +479,7 @@ class PreparedSequenceProgram(BaseModel): # pragma: no cover
|
|
408
479
|
|
409
480
|
|
410
481
|
class PreparedModule(BaseModel):
|
411
|
-
params: ModuleParams
|
482
|
+
params: ModuleParams
|
412
483
|
|
413
484
|
|
414
485
|
class PreparedProgram(BaseModel):
|
@@ -417,6 +488,14 @@ class PreparedProgram(BaseModel):
|
|
417
488
|
modules: dict[ModuleAddrType, PreparedModule] # The set of modules this program will target.
|
418
489
|
sequence_programs: dict[str, PreparedSequenceProgram] # The individual element programs.
|
419
490
|
|
491
|
+
@property
|
492
|
+
def sequencers(self) -> dict[SequencerAddr, str]:
|
493
|
+
return {psp.sequencer_addr: name for name, psp in self.sequence_programs.items()}
|
494
|
+
|
495
|
+
def get_sequencer_program(self, seq_addr: SequencerAddr) -> SequenceProgram:
|
496
|
+
prog_name = self.sequencers[seq_addr]
|
497
|
+
return self.sequence_programs[prog_name].sequence_program
|
498
|
+
|
420
499
|
def dumps(self) -> str:
|
421
500
|
return self.model_dump_json()
|
422
501
|
|
@@ -428,6 +507,9 @@ class PreparedProgram(BaseModel):
|
|
428
507
|
# ==================================================================================================
|
429
508
|
# Results
|
430
509
|
# ==================================================================================================
|
510
|
+
MAX_ACQUISITION_BINS = 131072
|
511
|
+
|
512
|
+
|
431
513
|
class OutputScopedAcquisitionData(BaseModel): # pragma: no cover
|
432
514
|
"""
|
433
515
|
Scoped acquisition data for a single path in `OutputScopedAcquisition`.
|
@@ -544,6 +626,121 @@ class OutputIndexedAcquisition(BaseModel): # pragma: no cover
|
|
544
626
|
OutputSequencerAcquisitions = dict[str, OutputIndexedAcquisition] # pragma: no cover
|
545
627
|
|
546
628
|
|
629
|
+
@dataclass
|
630
|
+
class SequencerResults:
|
631
|
+
"""
|
632
|
+
Sequencer results formatted as a complex signal.
|
633
|
+
|
634
|
+
The real component corresponds to results on path0, whilst the imaginary component corresponds
|
635
|
+
to the results on path1.
|
636
|
+
"""
|
637
|
+
|
638
|
+
scopes: dict[str, np.ndarray]
|
639
|
+
bins: dict[str, np.ndarray]
|
640
|
+
|
641
|
+
|
642
|
+
def process_sequencer_output(
|
643
|
+
program: SequenceProgram,
|
644
|
+
output: OutputSequencerAcquisitions,
|
645
|
+
) -> SequencerResults:
|
646
|
+
"""
|
647
|
+
Process the output from executing a sequencer into a simplified SequencerResults data structure.
|
648
|
+
|
649
|
+
Parameters
|
650
|
+
----------
|
651
|
+
program: SequenceProgram
|
652
|
+
The corresponding program that was executed
|
653
|
+
output: OutputSequencerAcquisitions
|
654
|
+
The results of one sequencer's execution
|
655
|
+
|
656
|
+
Returns
|
657
|
+
-------
|
658
|
+
SequencerResults
|
659
|
+
"""
|
660
|
+
bins = {}
|
661
|
+
scopes = {}
|
662
|
+
for acq_ref, acq_result in output.items():
|
663
|
+
acquisition = acq_result.acquisition
|
664
|
+
|
665
|
+
raw_bin = acquisition.bins.integration
|
666
|
+
shape = program.acquisition_shapes.get(acq_ref)
|
667
|
+
if shape is None or len(shape) == 1:
|
668
|
+
bins[acq_ref] = np.array(raw_bin.path0) + 1j * np.array(raw_bin.path1)
|
669
|
+
else:
|
670
|
+
bse = BitStrideArrayEncoding.from_desired(shape)
|
671
|
+
bins[acq_ref] = bse.decode(raw_bin.path0) + 1j * bse.decode(raw_bin.path1)
|
672
|
+
|
673
|
+
raw_scope = acquisition.scope
|
674
|
+
if acq_ref in program.acquisition_scopes:
|
675
|
+
scopes[acq_ref] = np.array(raw_scope.path0.data) + 1j * np.array(raw_scope.path1.data)
|
676
|
+
|
677
|
+
return SequencerResults(scopes=scopes, bins=bins)
|
678
|
+
|
679
|
+
|
680
|
+
@dataclass
|
681
|
+
class BitStrideArrayEncoding:
|
682
|
+
"""
|
683
|
+
Encode a multi-dimensional array such that each dimensional index occupies an integer number of
|
684
|
+
bits (the bit-stride).
|
685
|
+
|
686
|
+
In this encoding, a linear index is calculated by left-shifting each element in the index by its
|
687
|
+
corresponding bit-stride:
|
688
|
+
|
689
|
+
linear_index = Σ (ii << bb) ∀ (ii, bb) ∈ zip(index, bit_strides)
|
690
|
+
|
691
|
+
Examples
|
692
|
+
--------
|
693
|
+
Given a desired_shape of (3, 5), the first index will have a bit-stride of 2 and the second
|
694
|
+
index will have a bit-stride of 3. To determine the linear index for a sample, we will use:
|
695
|
+
|
696
|
+
def linear_index(idx0: int, idx1: int) -> int:
|
697
|
+
return (idx0 << 2) + (idx1 << 3)
|
698
|
+
|
699
|
+
Note, the encoded data will occupy 2^2 * 2^3 samples and have (2^2 * 2^3 - 3 * 5) unused data
|
700
|
+
points.
|
701
|
+
|
702
|
+
>>> bse = BitStrideArrayEncoding.from_desired((3, 5))
|
703
|
+
>>> bse.encoded_shape
|
704
|
+
(4, 8)
|
705
|
+
>>> bse.bit_stride
|
706
|
+
(2, 3)
|
707
|
+
"""
|
708
|
+
|
709
|
+
desired_shape: tuple[int, ...]
|
710
|
+
encoded_shape: tuple[int, ...]
|
711
|
+
bit_stride: tuple[int, ...]
|
712
|
+
|
713
|
+
@staticmethod
|
714
|
+
def _round_power2_32bit(val: int) -> int:
|
715
|
+
val -= 1
|
716
|
+
val |= val >> 1
|
717
|
+
val |= val >> 2
|
718
|
+
val |= val >> 4
|
719
|
+
val |= val >> 8
|
720
|
+
val |= val >> 16
|
721
|
+
val += 1
|
722
|
+
return val
|
723
|
+
|
724
|
+
@classmethod
|
725
|
+
def from_desired(cls, desired_shape: tuple[int, ...]) -> Self:
|
726
|
+
encoded_shape = tuple(
|
727
|
+
BitStrideArrayEncoding._round_power2_32bit(dim) for dim in desired_shape
|
728
|
+
)
|
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)))
|
733
|
+
return cls(
|
734
|
+
desired_shape=desired_shape,
|
735
|
+
encoded_shape=encoded_shape,
|
736
|
+
bit_stride=bit_stride,
|
737
|
+
)
|
738
|
+
|
739
|
+
def decode(self, values: list[float]) -> np.ndarray:
|
740
|
+
decoded = np.reshape(values, self.encoded_shape)
|
741
|
+
return decoded[tuple(slice(0, dim) for dim in self.desired_shape)]
|
742
|
+
|
743
|
+
|
547
744
|
# ==================================================================================================
|
548
745
|
# Utilities
|
549
746
|
# ==================================================================================================
|
@@ -11,15 +11,13 @@
|
|
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
|
14
15
|
|
15
|
-
from pydantic
|
16
|
+
from pydantic import BaseModel
|
16
17
|
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@dataclass
|
24
|
-
class InvalidDeviceComponent:
|
25
|
-
message: str
|
19
|
+
class DefCalData(BaseModel):
|
20
|
+
gate: str
|
21
|
+
addr: list[str]
|
22
|
+
body: str
|
23
|
+
status: Literal["calibrated", "uncalibrated"]
|
@@ -0,0 +1,67 @@
|
|
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
|
+
|
15
|
+
from pydantic import BaseModel
|
16
|
+
from pydantic.dataclasses import dataclass
|
17
|
+
|
18
|
+
from boulderopalscaleupsdk.common.dtypes import ISO8601DatetimeUTCLike
|
19
|
+
from boulderopalscaleupsdk.device.controller import (
|
20
|
+
QBLOXControllerInfo,
|
21
|
+
QuantumMachinesControllerInfo,
|
22
|
+
)
|
23
|
+
from boulderopalscaleupsdk.device.defcal import DefCalData
|
24
|
+
from boulderopalscaleupsdk.device.processor import SuperconductingProcessor
|
25
|
+
|
26
|
+
|
27
|
+
@dataclass
|
28
|
+
class EmptyDefCalData:
|
29
|
+
message: str
|
30
|
+
|
31
|
+
|
32
|
+
DeviceName = str
|
33
|
+
|
34
|
+
|
35
|
+
@dataclass
|
36
|
+
class DeviceData:
|
37
|
+
# TODO: retire DeviceInfo the next SDK release
|
38
|
+
qpu: SuperconductingProcessor # | OtherSDKProcessorType
|
39
|
+
controller_info: QBLOXControllerInfo | QuantumMachinesControllerInfo
|
40
|
+
_defcals: dict[tuple[str, tuple[str, ...]], DefCalData]
|
41
|
+
|
42
|
+
def get_defcal(self, gate: str, addr: tuple[str, ...]) -> DefCalData | EmptyDefCalData:
|
43
|
+
"""
|
44
|
+
Get the defcal data for a specific gate and address alias.
|
45
|
+
"""
|
46
|
+
if self._defcals == {}:
|
47
|
+
return EmptyDefCalData(message="No defcal data available in a fresh device.")
|
48
|
+
_addr = tuple(i.lower() for i in sorted(addr))
|
49
|
+
defcal = self._defcals.get((gate, _addr))
|
50
|
+
if defcal is None:
|
51
|
+
return EmptyDefCalData(
|
52
|
+
message=f"No defcal data found for gate '{gate}' and address '{_addr}'.",
|
53
|
+
)
|
54
|
+
return defcal
|
55
|
+
|
56
|
+
|
57
|
+
class DeviceSummary(BaseModel):
|
58
|
+
id: str
|
59
|
+
organization_id: str
|
60
|
+
name: str
|
61
|
+
provider: str
|
62
|
+
updated_at: ISO8601DatetimeUTCLike
|
63
|
+
created_at: ISO8601DatetimeUTCLike
|
64
|
+
copied_from: DeviceName | None = None
|
65
|
+
|
66
|
+
def __str__(self):
|
67
|
+
return f'DeviceSummary(name="{self.name}", id="{self.id}")'
|
@@ -14,7 +14,6 @@
|
|
14
14
|
__all__ = [
|
15
15
|
"CalibrationThresholds",
|
16
16
|
"ComponentParameter",
|
17
|
-
"DurationComponentParameter",
|
18
17
|
"FloatComponentParameter",
|
19
18
|
"SuperconductingProcessor",
|
20
19
|
"SuperconductingProcessorTemplate",
|
@@ -24,7 +23,6 @@ __all__ = [
|
|
24
23
|
from .common import (
|
25
24
|
CalibrationThresholds,
|
26
25
|
ComponentParameter,
|
27
|
-
DurationComponentParameter,
|
28
26
|
FloatComponentParameter,
|
29
27
|
update_parameter,
|
30
28
|
)
|
@@ -200,7 +200,7 @@ class ComponentParameter(Generic[T]):
|
|
200
200
|
):
|
201
201
|
self.calibration_status = _get_calibration_status_from_thresholds(
|
202
202
|
value=to_float(self.value),
|
203
|
-
confidence_interval=to_float(self.err_plus)
|
203
|
+
confidence_interval=to_float(self.err_plus) + to_float(self.err_minus),
|
204
204
|
calibration_thresholds=calibration_thresholds,
|
205
205
|
)
|
206
206
|
self.updated_at = datetime.now(UTC)
|
@@ -272,8 +272,3 @@ FloatComponentParameter = Annotated[
|
|
272
272
|
ComponentParameter[float],
|
273
273
|
BeforeValidator(lambda value: ComponentParameter.from_value(value, float)),
|
274
274
|
]
|
275
|
-
|
276
|
-
DurationComponentParameter = Annotated[
|
277
|
-
ComponentParameter[Duration],
|
278
|
-
BeforeValidator(lambda value: ComponentParameter.from_value(value, Duration)),
|
279
|
-
]
|