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,139 @@
|
|
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
|
+
import logging
|
15
|
+
import os
|
16
|
+
from typing import Literal
|
17
|
+
from unittest.mock import patch
|
18
|
+
|
19
|
+
from pydantic import BaseModel, Field, model_validator
|
20
|
+
|
21
|
+
from boulderopalscaleupsdk.common.dtypes import Duration, DurationNsLike, Self, TimeUnit
|
22
|
+
from boulderopalscaleupsdk.third_party.quantum_machines.config import (
|
23
|
+
ControllerConfigType,
|
24
|
+
OctaveConfig121,
|
25
|
+
OPX1000ControllerConfigType,
|
26
|
+
)
|
27
|
+
from boulderopalscaleupsdk.third_party.quantum_machines.constants import (
|
28
|
+
MIN_TIME_OF_FLIGHT,
|
29
|
+
QUA_CLOCK_CYCLE,
|
30
|
+
)
|
31
|
+
|
32
|
+
from .base import BaseControllerInfo
|
33
|
+
|
34
|
+
# Disable QM logging and telemetry
|
35
|
+
os.environ["QM_DISABLE_STREAMOUTPUT"] = "True" # Used in 1.1.0
|
36
|
+
_qm_logger = logging.getLogger("qm")
|
37
|
+
_qm_logger.disabled = True
|
38
|
+
|
39
|
+
# Disable unwanted telemetry/logging modules in QM
|
40
|
+
_qm_patch_targets = [
|
41
|
+
"qm._loc._get_loc",
|
42
|
+
"qm.program.expressions._get_loc",
|
43
|
+
"qm.program.StatementsCollection._get_loc",
|
44
|
+
"qm.qua._get_loc",
|
45
|
+
"qm.qua._dsl._get_loc",
|
46
|
+
"qm.qua._expressions._get_loc",
|
47
|
+
"qm.qua.AnalogMeasureProcess._get_loc",
|
48
|
+
"qm.qua.DigitalMeasureProcess._get_loc",
|
49
|
+
"qm.datadog_api.DatadogHandler",
|
50
|
+
]
|
51
|
+
for target in _qm_patch_targets:
|
52
|
+
try:
|
53
|
+
_m = patch(target).__enter__()
|
54
|
+
_m.return_value = ""
|
55
|
+
except (AttributeError, ModuleNotFoundError): # noqa: PERF203
|
56
|
+
pass
|
57
|
+
|
58
|
+
PortRef = str
|
59
|
+
OctaveRef = str
|
60
|
+
ControllerRef = str
|
61
|
+
PortNum = int
|
62
|
+
PortMapping = tuple[OctaveRef, PortNum]
|
63
|
+
|
64
|
+
|
65
|
+
class OctaveConfig(OctaveConfig121):
|
66
|
+
host: str | None = Field(default=None)
|
67
|
+
port: int | None = Field(default=None)
|
68
|
+
|
69
|
+
def to_qm_octave_config_121(self) -> OctaveConfig121:
|
70
|
+
return OctaveConfig121.model_validate(self.model_dump())
|
71
|
+
|
72
|
+
|
73
|
+
class DrivePortConfig(BaseModel):
|
74
|
+
port_type: Literal["drive"] = "drive"
|
75
|
+
port_mapping: PortMapping
|
76
|
+
|
77
|
+
|
78
|
+
class FluxPortConfig(BaseModel):
|
79
|
+
port_type: Literal["flux"] = "flux"
|
80
|
+
port_mapping: PortMapping
|
81
|
+
|
82
|
+
|
83
|
+
class ReadoutPortConfig(BaseModel):
|
84
|
+
port_type: Literal["readout"] = "readout"
|
85
|
+
port_mapping: PortMapping
|
86
|
+
time_of_flight: DurationNsLike
|
87
|
+
smearing: DurationNsLike = Field(default=Duration(0, TimeUnit.NS))
|
88
|
+
|
89
|
+
@model_validator(mode="after")
|
90
|
+
def _validate_readout_port_config(self) -> Self:
|
91
|
+
min_time_of_flight_ns = MIN_TIME_OF_FLIGHT.convert(TimeUnit.NS).value
|
92
|
+
time_of_flight_ns = self.time_of_flight.convert(TimeUnit.NS).value
|
93
|
+
smearing_ns = self.smearing.convert(TimeUnit.NS).value
|
94
|
+
qua_clock_cycle_ns = QUA_CLOCK_CYCLE.convert(TimeUnit.NS).value
|
95
|
+
|
96
|
+
if time_of_flight_ns < min_time_of_flight_ns:
|
97
|
+
raise ValueError(f"time_of_flight must be >= {MIN_TIME_OF_FLIGHT}")
|
98
|
+
|
99
|
+
if time_of_flight_ns % qua_clock_cycle_ns != 0:
|
100
|
+
raise ValueError(f"time_of_flight must be a multiple of {QUA_CLOCK_CYCLE}")
|
101
|
+
|
102
|
+
if smearing_ns > time_of_flight_ns - 8:
|
103
|
+
raise ValueError(f"smearing must be at most {time_of_flight_ns - 8} ns")
|
104
|
+
|
105
|
+
return self
|
106
|
+
|
107
|
+
|
108
|
+
OPXControllerConfig = ControllerConfigType
|
109
|
+
OPX1000ControllerConfig = OPX1000ControllerConfigType
|
110
|
+
|
111
|
+
|
112
|
+
class QuantumMachinesControllerInfo(BaseControllerInfo):
|
113
|
+
"""
|
114
|
+
QuantumMachinesControllerInfo is a data model that represents the configuration
|
115
|
+
and port settings for quantum machine controllers.
|
116
|
+
|
117
|
+
NOTE: Interface must match OPX Config for first set of parameters, remainder are ours
|
118
|
+
https://docs.quantum-machines.co/1.2.1/assets/qua_config.html#/paths/~1/get
|
119
|
+
|
120
|
+
Attributes
|
121
|
+
----------
|
122
|
+
controllers : dict[ControllerRef, OPXControllerConfig | OPX1000ControllerConfig]
|
123
|
+
A dictionary mapping controller references to their respective configurations.
|
124
|
+
The configurations can be either OPXControllerConfig or OPX1000ControllerConfig.
|
125
|
+
Derived from OPX Config.
|
126
|
+
octaves : dict[OctaveRef, OctaveConfig]
|
127
|
+
A dictionary mapping octave references to their respective configurations.
|
128
|
+
Derived from OPX Config.
|
129
|
+
port_config : dict[PortRef, DrivePortConfig | FluxPortConfig | ReadoutPortConfig]
|
130
|
+
A dictionary mapping port references to their respective port configurations.
|
131
|
+
The configurations can be DrivePortConfig, FluxPortConfig, or ReadoutPortConfig.
|
132
|
+
Not derived from OPX Config, this is our custom config.
|
133
|
+
"""
|
134
|
+
|
135
|
+
controllers: dict[ControllerRef, OPXControllerConfig | OPX1000ControllerConfig] = Field(
|
136
|
+
default={},
|
137
|
+
)
|
138
|
+
octaves: dict[OctaveRef, OctaveConfig] = Field(default={})
|
139
|
+
port_config: dict[PortRef, DrivePortConfig | FluxPortConfig | ReadoutPortConfig]
|
@@ -0,0 +1,35 @@
|
|
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 pydantic import BaseModel
|
15
|
+
from pydantic.dataclasses import dataclass
|
16
|
+
|
17
|
+
from boulderopalscaleupsdk.device.controller.quantum_machines import QuantumMachinesControllerInfo
|
18
|
+
from boulderopalscaleupsdk.device.processor import SuperconductingProcessor
|
19
|
+
|
20
|
+
|
21
|
+
class Device(BaseModel):
|
22
|
+
"""Device specification."""
|
23
|
+
|
24
|
+
processor: SuperconductingProcessor # | OtherProcessorTypes
|
25
|
+
controller_info: QuantumMachinesControllerInfo # | OtherControllerInfoTypes
|
26
|
+
|
27
|
+
|
28
|
+
@dataclass
|
29
|
+
class InvalidDevice:
|
30
|
+
message: str
|
31
|
+
|
32
|
+
|
33
|
+
@dataclass
|
34
|
+
class InvalidDeviceComponent:
|
35
|
+
message: str
|
@@ -0,0 +1,23 @@
|
|
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
|
+
__all__ = [
|
15
|
+
"ComponentParameter",
|
16
|
+
"DurationComponentParameter",
|
17
|
+
"FloatComponentParameter",
|
18
|
+
"SuperconductingProcessor",
|
19
|
+
"SuperconductingProcessorTemplate",
|
20
|
+
]
|
21
|
+
|
22
|
+
from .common import ComponentParameter, DurationComponentParameter, FloatComponentParameter
|
23
|
+
from .superconducting_processor import SuperconductingProcessor, SuperconductingProcessorTemplate
|
@@ -0,0 +1,148 @@
|
|
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 collections.abc import Callable
|
15
|
+
from datetime import datetime, timezone
|
16
|
+
from typing import Annotated, Any, Generic, Literal, TypeVar
|
17
|
+
|
18
|
+
from pydantic import BaseModel, BeforeValidator, ConfigDict, TypeAdapter
|
19
|
+
|
20
|
+
from boulderopalscaleupsdk.common.dtypes import Duration, DurationNsLike, ISO8601DatetimeUTCLike
|
21
|
+
from boulderopalscaleupsdk.common.typeclasses import Combine
|
22
|
+
|
23
|
+
T = TypeVar("T")
|
24
|
+
T2 = TypeVar("T2")
|
25
|
+
CalibrationStatusT = Literal["approximate", "bad", "good", "stale", "unmeasured"]
|
26
|
+
|
27
|
+
|
28
|
+
class ComponentParameter(BaseModel, Generic[T]):
|
29
|
+
dtype: Literal["component-parameter"] = "component-parameter"
|
30
|
+
value: T
|
31
|
+
err_minus: T | None = None
|
32
|
+
err_plus: T | None = None
|
33
|
+
calibration_status: CalibrationStatusT = "unmeasured"
|
34
|
+
updated_at: ISO8601DatetimeUTCLike | None = None
|
35
|
+
|
36
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
def from_value(
|
40
|
+
cls,
|
41
|
+
value: Any,
|
42
|
+
target_type: type[float | int | Duration],
|
43
|
+
) -> "ComponentParameter":
|
44
|
+
"""
|
45
|
+
Create a ComponentParameter instance from a value, ensuring it matches the target type.
|
46
|
+
|
47
|
+
Parameters
|
48
|
+
----------
|
49
|
+
value : Any
|
50
|
+
The input value to validate and convert.
|
51
|
+
target_type : type
|
52
|
+
The expected type of the value (e.g., float, int, Duration).
|
53
|
+
|
54
|
+
Returns
|
55
|
+
-------
|
56
|
+
ComponentParameter
|
57
|
+
A validated ComponentParameter instance.
|
58
|
+
|
59
|
+
Raises
|
60
|
+
------
|
61
|
+
ValueError
|
62
|
+
If the value does not match the target type.
|
63
|
+
"""
|
64
|
+
match value:
|
65
|
+
case value if isinstance(value, cls):
|
66
|
+
if not isinstance(value.value, target_type):
|
67
|
+
raise TypeError("invalid value type")
|
68
|
+
return value
|
69
|
+
case _ if target_type in [float, int]:
|
70
|
+
return cls(**value)
|
71
|
+
case _ if target_type is Duration:
|
72
|
+
value_copy = value.copy()
|
73
|
+
duration_ns = TypeAdapter(DurationNsLike).validate_python(value_copy.pop("value"))
|
74
|
+
return cls(value=duration_ns, **value_copy) # type: ignore[arg-type]
|
75
|
+
case _:
|
76
|
+
return cls.model_validate(value)
|
77
|
+
|
78
|
+
@staticmethod
|
79
|
+
def combine(
|
80
|
+
combine_instance: Combine[T],
|
81
|
+
) -> "Combine[ComponentParameter[T]]":
|
82
|
+
def _combine(
|
83
|
+
first: ComponentParameter[T],
|
84
|
+
other: ComponentParameter[Any],
|
85
|
+
) -> ComponentParameter[T]:
|
86
|
+
return ComponentParameter(
|
87
|
+
value=combine_instance.combine(first.value, other.value),
|
88
|
+
err_minus=combine_instance.combine_option(
|
89
|
+
first.err_minus,
|
90
|
+
other.err_minus,
|
91
|
+
),
|
92
|
+
err_plus=combine_instance.combine_option(
|
93
|
+
first.err_plus,
|
94
|
+
other.err_plus,
|
95
|
+
),
|
96
|
+
calibration_status=other.calibration_status,
|
97
|
+
updated_at=datetime.now(tz=timezone.utc),
|
98
|
+
)
|
99
|
+
|
100
|
+
return Combine[ComponentParameter[T]].create(_combine)
|
101
|
+
|
102
|
+
def map(self, fn: Callable[[T], T2]) -> "ComponentParameter[T2]":
|
103
|
+
return ComponentParameter(
|
104
|
+
value=fn(self.value),
|
105
|
+
err_minus=fn(self.err_minus) if self.err_minus is not None else None,
|
106
|
+
err_plus=fn(self.err_plus) if self.err_plus is not None else None,
|
107
|
+
calibration_status=self.calibration_status,
|
108
|
+
updated_at=self.updated_at,
|
109
|
+
)
|
110
|
+
|
111
|
+
def merge_with(self, other: "ComponentParameter[T]", combine_value: "Combine[T]"):
|
112
|
+
combined = ComponentParameter[T].combine(combine_value).combine(self, other)
|
113
|
+
self.value = combined.value
|
114
|
+
self.err_minus = combined.err_minus
|
115
|
+
self.err_plus = combined.err_plus
|
116
|
+
self.calibration_status = combined.calibration_status
|
117
|
+
self.updated_at = combined.updated_at
|
118
|
+
|
119
|
+
|
120
|
+
def get_calibration_status_from_thresholds(
|
121
|
+
value: float,
|
122
|
+
confidence_interval: float,
|
123
|
+
good_threshold: float,
|
124
|
+
approximate_threshold: float,
|
125
|
+
) -> CalibrationStatusT:
|
126
|
+
if good_threshold <= 0 or approximate_threshold <= 0 or good_threshold >= approximate_threshold:
|
127
|
+
raise ValueError(
|
128
|
+
f"Invalid thresholds: good: {good_threshold}, approximate: {approximate_threshold}",
|
129
|
+
)
|
130
|
+
|
131
|
+
relative_uncertainty = abs(confidence_interval / value)
|
132
|
+
|
133
|
+
if relative_uncertainty < good_threshold:
|
134
|
+
return "good"
|
135
|
+
if relative_uncertainty < approximate_threshold:
|
136
|
+
return "approximate"
|
137
|
+
return "bad"
|
138
|
+
|
139
|
+
|
140
|
+
FloatComponentParameter = Annotated[
|
141
|
+
ComponentParameter[float],
|
142
|
+
BeforeValidator(lambda value: ComponentParameter.from_value(value, float)),
|
143
|
+
]
|
144
|
+
|
145
|
+
DurationComponentParameter = Annotated[
|
146
|
+
ComponentParameter[Duration],
|
147
|
+
BeforeValidator(lambda value: ComponentParameter.from_value(value, Duration)),
|
148
|
+
]
|
@@ -0,0 +1,291 @@
|
|
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 copy import deepcopy
|
15
|
+
from typing import Annotated, Any, Literal, Union, get_args, get_origin
|
16
|
+
|
17
|
+
from pydantic import BaseModel, Field
|
18
|
+
|
19
|
+
from boulderopalscaleupsdk.common.dtypes import (
|
20
|
+
Duration,
|
21
|
+
TimeUnit,
|
22
|
+
)
|
23
|
+
from boulderopalscaleupsdk.device.common import (
|
24
|
+
Component,
|
25
|
+
ComponentRef,
|
26
|
+
Processor,
|
27
|
+
)
|
28
|
+
from boulderopalscaleupsdk.device.processor import (
|
29
|
+
ComponentParameter,
|
30
|
+
DurationComponentParameter,
|
31
|
+
FloatComponentParameter,
|
32
|
+
)
|
33
|
+
|
34
|
+
|
35
|
+
# Retrieval data models
|
36
|
+
class Transmon(Component[Literal["tunable"]]):
|
37
|
+
dtype: Literal["transmon"] = "transmon"
|
38
|
+
traits: list[Literal["tunable"]] = Field(default=["tunable"])
|
39
|
+
|
40
|
+
freq_01: FloatComponentParameter | None = Field(
|
41
|
+
default=None,
|
42
|
+
json_schema_extra={"display": {"label": "freq_01", "unit": "MHz", "scale": 1e-6}},
|
43
|
+
)
|
44
|
+
anharm: FloatComponentParameter | None = Field(
|
45
|
+
default=None,
|
46
|
+
json_schema_extra={"display": {"label": "anharm", "unit": "MHz", "scale": 1e-6}},
|
47
|
+
)
|
48
|
+
t1: DurationComponentParameter = Field(
|
49
|
+
default=ComponentParameter(value=Duration(0, TimeUnit.NS)),
|
50
|
+
json_schema_extra={"display": {"label": "t1", "unit": "µs", "scale": 1e6}},
|
51
|
+
)
|
52
|
+
t2: DurationComponentParameter = Field(
|
53
|
+
default=ComponentParameter(value=Duration(0, TimeUnit.NS)),
|
54
|
+
json_schema_extra={"display": {"label": "t2", "unit": "µs", "scale": 1e6}},
|
55
|
+
)
|
56
|
+
t2_echo: DurationComponentParameter = Field(
|
57
|
+
default=ComponentParameter(value=Duration(0, TimeUnit.NS)),
|
58
|
+
json_schema_extra={"display": {"label": "t2_echo", "unit": "µs", "scale": 1e6}},
|
59
|
+
)
|
60
|
+
x_vp: FloatComponentParameter = Field(
|
61
|
+
default=ComponentParameter(value=0.0),
|
62
|
+
json_schema_extra={"display": {"label": "x_vp", "unit": "V", "scale": 1}},
|
63
|
+
)
|
64
|
+
sx_vp: FloatComponentParameter = Field(
|
65
|
+
default=ComponentParameter(value=0.0),
|
66
|
+
json_schema_extra={"display": {"label": "sx_vp", "unit": "V", "scale": 1}},
|
67
|
+
)
|
68
|
+
|
69
|
+
# Tunable transmon parameters.
|
70
|
+
dc_bias: FloatComponentParameter = Field(
|
71
|
+
default=ComponentParameter(value=0.0),
|
72
|
+
json_schema_extra={"display": {"label": "dc_bias", "unit": "V", "scale": 1}},
|
73
|
+
)
|
74
|
+
fmax_bias: FloatComponentParameter = Field(
|
75
|
+
default=ComponentParameter(value=0.0),
|
76
|
+
json_schema_extra={"display": {"label": "fmax_bias", "unit": "V", "scale": 1}},
|
77
|
+
)
|
78
|
+
bias_period: FloatComponentParameter = Field(
|
79
|
+
default=ComponentParameter(value=0.0),
|
80
|
+
json_schema_extra={"display": {"label": "bias_period", "unit": "V", "scale": 1}},
|
81
|
+
)
|
82
|
+
|
83
|
+
|
84
|
+
class Resonator(Component[Literal["dispersive"]]):
|
85
|
+
dtype: Literal["resonator"] = "resonator"
|
86
|
+
traits: list[Literal["dispersive"]] = Field(default=["dispersive"])
|
87
|
+
|
88
|
+
frequency_high: FloatComponentParameter | None = Field(
|
89
|
+
default=None,
|
90
|
+
json_schema_extra={"display": {"label": "frequency_high", "unit": "MHz", "scale": 1e-6}},
|
91
|
+
)
|
92
|
+
frequency_low: FloatComponentParameter | None = Field(
|
93
|
+
default=None,
|
94
|
+
json_schema_extra={"display": {"label": "frequency_low", "unit": "MHz", "scale": 1e-6}},
|
95
|
+
)
|
96
|
+
kappa_low: FloatComponentParameter | None = Field(
|
97
|
+
default=None,
|
98
|
+
json_schema_extra={"display": {"label": "kappa_low", "unit": "MHz", "scale": 1e-6}},
|
99
|
+
)
|
100
|
+
kappa_high: FloatComponentParameter | None = Field(
|
101
|
+
default=None,
|
102
|
+
json_schema_extra={"display": {"label": "kappa_high", "unit": "MHz", "scale": 1e-6}},
|
103
|
+
)
|
104
|
+
vp_low: FloatComponentParameter = Field(
|
105
|
+
default=ComponentParameter(value=0.0),
|
106
|
+
json_schema_extra={"display": {"label": "vp_low", "unit": "V", "scale": 1}},
|
107
|
+
)
|
108
|
+
vp_high: FloatComponentParameter = Field(
|
109
|
+
default=ComponentParameter(value=0.0),
|
110
|
+
json_schema_extra={"display": {"label": "vp_high", "unit": "V", "scale": 1}},
|
111
|
+
)
|
112
|
+
|
113
|
+
|
114
|
+
class Port(Component[Literal["drive", "readout", "flux"]]):
|
115
|
+
dtype: Literal["port"] = "port"
|
116
|
+
traits: list[Literal["flux", "drive", "readout"]] = Field(
|
117
|
+
default=["drive", "readout", "flux"],
|
118
|
+
)
|
119
|
+
|
120
|
+
|
121
|
+
class Filter(Component[Literal["filter"]]):
|
122
|
+
dtype: Literal["filter"] = "filter"
|
123
|
+
traits: list = Field(default=[])
|
124
|
+
|
125
|
+
|
126
|
+
class Feedline(Component[Literal["feedline"]]):
|
127
|
+
dtype: Literal["feedline"] = "feedline"
|
128
|
+
traits: list = Field(default=[])
|
129
|
+
|
130
|
+
|
131
|
+
SuperconductingComponentType = Transmon | Resonator | Port | Feedline | Filter
|
132
|
+
|
133
|
+
|
134
|
+
class Edge(BaseModel):
|
135
|
+
dtype: Literal["capacitive-coupling", "inductive-coupling"]
|
136
|
+
u: str
|
137
|
+
v: str
|
138
|
+
|
139
|
+
|
140
|
+
class TemplateParam(BaseModel):
|
141
|
+
template: str
|
142
|
+
vars: dict[int, list[str]]
|
143
|
+
|
144
|
+
|
145
|
+
class ProcessorTemplate(BaseModel):
|
146
|
+
elements: dict[str, Transmon | Resonator | Port | Feedline | Filter]
|
147
|
+
edges: list[Edge]
|
148
|
+
|
149
|
+
|
150
|
+
class SuperconductingProcessorTemplate(BaseModel):
|
151
|
+
build: list[TemplateParam]
|
152
|
+
templates: dict[str, ProcessorTemplate]
|
153
|
+
device_parameters: dict[str, Transmon | Resonator | Port | Feedline | Filter] = Field(
|
154
|
+
default={},
|
155
|
+
)
|
156
|
+
|
157
|
+
|
158
|
+
class SuperconductingProcessor(Processor):
|
159
|
+
nodes: dict[ComponentRef, SuperconductingComponentType]
|
160
|
+
edges: list[Edge]
|
161
|
+
|
162
|
+
@staticmethod
|
163
|
+
def from_template(template: SuperconductingProcessorTemplate) -> "SuperconductingProcessor":
|
164
|
+
_qpu = SuperconductingProcessor(nodes={}, edges=[])
|
165
|
+
for build in template.build:
|
166
|
+
qpu_template = template.templates[build.template]
|
167
|
+
for idx, subs in build.vars.items():
|
168
|
+
for sub in subs:
|
169
|
+
for k, template_params in qpu_template.elements.items():
|
170
|
+
ref = k.replace(f"${idx}", sub)
|
171
|
+
params = template.device_parameters.get(ref)
|
172
|
+
p = deepcopy(params or template_params)
|
173
|
+
p.traits = template_params.traits # type: ignore[reportAttributeAccessIssue]
|
174
|
+
p = _coerce_component_params(p)
|
175
|
+
_qpu.nodes[ref] = p
|
176
|
+
|
177
|
+
for edge in qpu_template.edges:
|
178
|
+
_qpu.edges.append(
|
179
|
+
Edge(
|
180
|
+
u=edge.u.replace(f"${idx}", sub),
|
181
|
+
v=edge.v.replace(f"${idx}", sub),
|
182
|
+
dtype=edge.dtype,
|
183
|
+
),
|
184
|
+
)
|
185
|
+
return _qpu
|
186
|
+
|
187
|
+
@staticmethod
|
188
|
+
def from_dict(data: dict[str, Any]) -> "SuperconductingProcessor":
|
189
|
+
"""
|
190
|
+
Deserializes a SuperconductingProcessor instance from a dictionary.
|
191
|
+
"""
|
192
|
+
return SuperconductingProcessor.model_validate(data)
|
193
|
+
|
194
|
+
def to_dict(self) -> dict[str, Any]:
|
195
|
+
"""
|
196
|
+
Serializes the SuperconductingProcessor instance into a dictionary.
|
197
|
+
"""
|
198
|
+
return self.model_dump()
|
199
|
+
|
200
|
+
def update_component(self, component_id: str, **params: Any) -> None:
|
201
|
+
"""
|
202
|
+
Update the parameters of a specific component.
|
203
|
+
|
204
|
+
Parameters
|
205
|
+
----------
|
206
|
+
component_id : str
|
207
|
+
The ID of the component to update.
|
208
|
+
**params : Any
|
209
|
+
The parameters to update, provided as keyword arguments.
|
210
|
+
|
211
|
+
Raises
|
212
|
+
------
|
213
|
+
KeyError
|
214
|
+
If the component with the given ID does not exist.
|
215
|
+
ValueError
|
216
|
+
If the component does not support the provided parameters.
|
217
|
+
ValidationError
|
218
|
+
If the updated parameters do not conform to the expected schema.
|
219
|
+
"""
|
220
|
+
if component_id not in self.nodes:
|
221
|
+
raise KeyError(f"Component with ID '{component_id}' does not exist.")
|
222
|
+
|
223
|
+
component = self.nodes[component_id]
|
224
|
+
replacement_component = {
|
225
|
+
k: ComponentParameter(value=v).model_dump()
|
226
|
+
if k not in ["dtype", "traits"] and v is not None
|
227
|
+
else v
|
228
|
+
for k, v in params.items()
|
229
|
+
}
|
230
|
+
self.nodes[component_id] = type(component).model_validate(replacement_component)
|
231
|
+
|
232
|
+
def get_component(self, component_id: str) -> SuperconductingComponentType:
|
233
|
+
"""
|
234
|
+
Retrieve the parameters of a specific component.
|
235
|
+
|
236
|
+
Parameters
|
237
|
+
----------
|
238
|
+
component_id : str
|
239
|
+
The ID of the component.
|
240
|
+
|
241
|
+
Returns
|
242
|
+
-------
|
243
|
+
dict
|
244
|
+
A dictionary of the component's parameters.
|
245
|
+
"""
|
246
|
+
return self.nodes[component_id]
|
247
|
+
|
248
|
+
|
249
|
+
def _coerce_component_params(
|
250
|
+
component: SuperconductingComponentType,
|
251
|
+
) -> SuperconductingComponentType:
|
252
|
+
annotations = component.__annotations__
|
253
|
+
|
254
|
+
for field_name in component.model_fields:
|
255
|
+
expected_type = annotations.get(field_name)
|
256
|
+
value = getattr(component, field_name)
|
257
|
+
|
258
|
+
if not isinstance(value, ComponentParameter):
|
259
|
+
continue
|
260
|
+
|
261
|
+
def _unwrap_annotated(tp):
|
262
|
+
"""Unwrap Annotated[...] -> original type."""
|
263
|
+
if get_origin(tp) is Annotated:
|
264
|
+
return get_args(tp)[0]
|
265
|
+
return tp
|
266
|
+
|
267
|
+
def _unwrap_optional(tp):
|
268
|
+
"""Unwrap Optional[T] -> T."""
|
269
|
+
if get_origin(tp) is Union:
|
270
|
+
args = [t for t in get_args(tp) if t is not type(None)]
|
271
|
+
if len(args) == 1:
|
272
|
+
return args[0]
|
273
|
+
return tp
|
274
|
+
|
275
|
+
expected_type = _unwrap_optional(expected_type)
|
276
|
+
expected_type = _unwrap_annotated(expected_type)
|
277
|
+
|
278
|
+
if not isinstance(expected_type, type) or not issubclass(expected_type, ComponentParameter):
|
279
|
+
continue
|
280
|
+
|
281
|
+
try:
|
282
|
+
dumped = value.model_dump()
|
283
|
+
coerced = expected_type(**dumped)
|
284
|
+
setattr(component, field_name, coerced)
|
285
|
+
except Exception as e:
|
286
|
+
raise ValueError(
|
287
|
+
f"Failed to coerce field '{field_name}' of type "
|
288
|
+
"'{expected_type}' with value '{value}': {e}",
|
289
|
+
) from e
|
290
|
+
|
291
|
+
return component
|
@@ -0,0 +1,44 @@
|
|
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
|
+
"""Experiment library."""
|
15
|
+
|
16
|
+
__all__ = [
|
17
|
+
"CWSIterable",
|
18
|
+
"ConstantWaveform",
|
19
|
+
"Experiment",
|
20
|
+
"GaussianWaveform",
|
21
|
+
"HypIterable",
|
22
|
+
"LinspaceIterable",
|
23
|
+
"PowerRabi",
|
24
|
+
"Ramsey",
|
25
|
+
"RangeIterable",
|
26
|
+
"ResonatorSpectroscopy",
|
27
|
+
"ResonatorSpectroscopyByBias",
|
28
|
+
"ResonatorSpectroscopyByPower",
|
29
|
+
]
|
30
|
+
|
31
|
+
from .common import (
|
32
|
+
ConstantWaveform,
|
33
|
+
CWSIterable,
|
34
|
+
Experiment,
|
35
|
+
GaussianWaveform,
|
36
|
+
HypIterable,
|
37
|
+
LinspaceIterable,
|
38
|
+
RangeIterable,
|
39
|
+
)
|
40
|
+
from .power_rabi import PowerRabi
|
41
|
+
from .ramsey import Ramsey
|
42
|
+
from .resonator_spectroscopy import ResonatorSpectroscopy
|
43
|
+
from .resonator_spectroscopy_by_bias import ResonatorSpectroscopyByBias
|
44
|
+
from .resonator_spectroscopy_by_power import ResonatorSpectroscopyByPower
|