boulder-opal-scale-up-sdk 1.0.3__tar.gz → 1.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/PKG-INFO +1 -1
  2. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/common/dtypes.py +12 -1
  3. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/device/config_loader.py +1 -0
  4. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/device/controller/qblox.py +129 -0
  5. boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/device/device.py → boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/device/defcal.py +7 -9
  6. boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/device/device.py +63 -0
  7. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/device/processor/__init__.py +0 -2
  8. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/device/processor/common.py +1 -6
  9. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/device/processor/superconducting_processor.py +11 -4
  10. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/experiments/__init__.py +16 -2
  11. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/experiments/chi01_scan.py +10 -9
  12. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/experiments/common.py +0 -16
  13. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/experiments/power_rabi.py +13 -10
  14. boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/experiments/power_rabi_ef.py +66 -0
  15. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/experiments/ramsey.py +13 -12
  16. boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/experiments/readout_classifier_calibration.py +44 -0
  17. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/experiments/resonator_spectroscopy.py +11 -11
  18. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_bias.py +13 -13
  19. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_power.py +12 -12
  20. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/experiments/t1.py +8 -16
  21. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/experiments/t2.py +9 -8
  22. boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/experiments/t2_echo.py +49 -0
  23. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/experiments/transmon_anharmonicity.py +16 -17
  24. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/experiments/transmon_spectroscopy.py +10 -10
  25. boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/experiments/waveforms.py +63 -0
  26. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/plotting/dtypes.py +1 -0
  27. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/protobuf/v1/device_pb2.py +20 -20
  28. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/protobuf/v1/device_pb2.pyi +4 -2
  29. boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/routines/__init__.py +21 -0
  30. boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/device/__init__.py → boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/routines/common.py +9 -5
  31. boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/routines/resonator_mapping.py +35 -0
  32. boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/routines/transmon_discovery.py +45 -0
  33. boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/routines/transmon_retuning.py +31 -0
  34. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/stubs/maps.py +11 -2
  35. boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/utils/__init__.py +12 -0
  36. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/pyproject.toml +1 -1
  37. boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/device/defcal.py +0 -62
  38. boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/experiments/readout_classifier_calibration.py +0 -28
  39. boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/routines/__init__.py +0 -6
  40. boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/routines/common.py +0 -10
  41. boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/routines/resonator_mapping.py +0 -19
  42. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/LICENSE +0 -0
  43. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/README.md +0 -0
  44. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/__init__.py +0 -0
  45. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/agent/__init__.py +0 -0
  46. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/agent/worker.py +0 -0
  47. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/common/__init__.py +0 -0
  48. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/common/typeclasses.py +0 -0
  49. {boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/stubs → boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/device}/__init__.py +0 -0
  50. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/device/common.py +0 -0
  51. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/device/controller/__init__.py +0 -0
  52. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/device/controller/base.py +0 -0
  53. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/device/controller/quantum_machines.py +0 -0
  54. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/device/controller/resolver.py +0 -0
  55. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/grpc_interceptors/__init__.py +0 -0
  56. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/grpc_interceptors/auth.py +0 -0
  57. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/plotting/__init__.py +0 -0
  58. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/protobuf/v1/agent_pb2.py +0 -0
  59. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/protobuf/v1/agent_pb2.pyi +0 -0
  60. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/protobuf/v1/agent_pb2_grpc.py +0 -0
  61. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/protobuf/v1/device_pb2_grpc.py +0 -0
  62. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/protobuf/v1/task_pb2.py +0 -0
  63. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/protobuf/v1/task_pb2.pyi +0 -0
  64. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/protobuf/v1/task_pb2_grpc.py +0 -0
  65. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/py.typed +0 -0
  66. {boulder_opal_scale_up_sdk-1.0.3/boulderopalscaleupsdk/utils → boulder_opal_scale_up_sdk-1.0.4/boulderopalscaleupsdk/stubs}/__init__.py +0 -0
  67. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/stubs/dtypes.py +0 -0
  68. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/third_party/__init__.py +0 -0
  69. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/third_party/quantum_machines/__init__.py +0 -0
  70. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/third_party/quantum_machines/config.py +0 -0
  71. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/boulderopalscaleupsdk/third_party/quantum_machines/constants.py +0 -0
  72. {boulder_opal_scale_up_sdk-1.0.3 → boulder_opal_scale_up_sdk-1.0.4}/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
3
+ Version: 1.0.4
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
@@ -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,17 @@ 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
+
320
331
  DEFAULT_JOB_HISTORY_PAGE = 1
321
332
  DEFAULT_JOB_HISTORY_PAGE_SIZE = 10
322
333
  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
@@ -373,6 +378,7 @@ class SequenceProgram(BaseModel):
373
378
  weights: dict[str, IndexedData] = {}
374
379
  acquisitions: dict[str, AcquisitionConfig] = {}
375
380
  acquisition_scopes: list[str] = []
381
+ acquisition_shapes: dict[str, tuple[int, ...]] = {}
376
382
  params: SequencerParams = SequencerParams()
377
383
  params_only: bool = False
378
384
 
@@ -417,6 +423,14 @@ class PreparedProgram(BaseModel):
417
423
  modules: dict[ModuleAddrType, PreparedModule] # The set of modules this program will target.
418
424
  sequence_programs: dict[str, PreparedSequenceProgram] # The individual element programs.
419
425
 
426
+ @property
427
+ def sequencers(self) -> dict[SequencerAddr, str]:
428
+ return {psp.sequencer_addr: name for name, psp in self.sequence_programs.items()}
429
+
430
+ def get_sequencer_program(self, seq_addr: SequencerAddr) -> SequenceProgram:
431
+ prog_name = self.sequencers[seq_addr]
432
+ return self.sequence_programs[prog_name].sequence_program
433
+
420
434
  def dumps(self) -> str:
421
435
  return self.model_dump_json()
422
436
 
@@ -428,6 +442,9 @@ class PreparedProgram(BaseModel):
428
442
  # ==================================================================================================
429
443
  # Results
430
444
  # ==================================================================================================
445
+ MAX_ACQUISITION_BINS = 131072
446
+
447
+
431
448
  class OutputScopedAcquisitionData(BaseModel): # pragma: no cover
432
449
  """
433
450
  Scoped acquisition data for a single path in `OutputScopedAcquisition`.
@@ -544,6 +561,118 @@ class OutputIndexedAcquisition(BaseModel): # pragma: no cover
544
561
  OutputSequencerAcquisitions = dict[str, OutputIndexedAcquisition] # pragma: no cover
545
562
 
546
563
 
564
+ @dataclass
565
+ class SequencerResults:
566
+ """
567
+ Sequencer results formatted as a complex signal.
568
+
569
+ The real component corresponds to results on path0, whilst the imaginary component corresponds
570
+ to the results on path1.
571
+ """
572
+
573
+ scopes: dict[str, np.ndarray]
574
+ bins: dict[str, np.ndarray]
575
+
576
+
577
+ def process_sequencer_output(
578
+ program: SequenceProgram,
579
+ output: OutputSequencerAcquisitions,
580
+ ) -> SequencerResults:
581
+ """
582
+ Process the output from executing a sequencer into a simplified SequencerResults data structure.
583
+
584
+ Parameters
585
+ ----------
586
+ program: SequenceProgram
587
+ The corresponding program that was executed
588
+ output: OutputSequencerAcquisitions
589
+ The results of one sequencer's execution
590
+
591
+ Returns
592
+ -------
593
+ SequencerResults
594
+ """
595
+ bins = {}
596
+ scopes = {}
597
+ for acq_ref, acq_result in output.items():
598
+ acquisition = acq_result.acquisition
599
+
600
+ raw_bin = acquisition.bins.integration
601
+ shape = program.acquisition_shapes.get(acq_ref)
602
+ if shape is None or len(shape) == 1:
603
+ bins[acq_ref] = np.array(raw_bin.path0) + 1j * np.array(raw_bin.path1)
604
+ else:
605
+ bse = BitStrideArrayEncoding.from_desired(shape)
606
+ bins[acq_ref] = bse.decode(raw_bin.path0) + 1j * bse.decode(raw_bin.path1)
607
+
608
+ raw_scope = acquisition.scope
609
+ if acq_ref in program.acquisition_scopes:
610
+ scopes[acq_ref] = np.array(raw_scope.path0.data) + 1j * np.array(raw_scope.path1.data)
611
+
612
+ return SequencerResults(scopes=scopes, bins=bins)
613
+
614
+
615
+ @dataclass
616
+ class BitStrideArrayEncoding:
617
+ """
618
+ Encode a multi-dimensional array such that each dimensional index occupies an integer number of
619
+ bits (the bit-stride).
620
+
621
+ In this encoding, a linear index is calculated by left-shifting each element in the index by its
622
+ corresponding bit-stride:
623
+
624
+ linear_index = Σ (ii << bb) ∀ (ii, bb) ∈ zip(index, bit_strides)
625
+
626
+ Examples
627
+ --------
628
+ Given a desired_shape of (3, 5), the first index will have a bit-stride of 2 and the second
629
+ index will have a bit-stride of 3. To determine the linear index for a sample, we will use:
630
+
631
+ def linear_index(idx0: int, idx1: int) -> int:
632
+ return (idx0 << 2) + (idx1 << 3)
633
+
634
+ Note, the encoded data will occupy 2^2 * 2^3 samples and have (2^2 * 2^3 - 3 * 5) unused data
635
+ points.
636
+
637
+ >>> bse = BitStrideArrayEncoding.from_desired((3, 5))
638
+ >>> bse.encoded_shape
639
+ (4, 8)
640
+ >>> bse.bit_stride
641
+ (2, 3)
642
+ """
643
+
644
+ desired_shape: tuple[int, ...]
645
+ encoded_shape: tuple[int, ...]
646
+ bit_stride: tuple[int, ...]
647
+
648
+ @staticmethod
649
+ def _round_power2_32bit(val: int) -> int:
650
+ val -= 1
651
+ val |= val >> 1
652
+ val |= val >> 2
653
+ val |= val >> 4
654
+ val |= val >> 8
655
+ val |= val >> 16
656
+ val += 1
657
+ return val
658
+
659
+ @classmethod
660
+ def from_desired(cls, desired_shape: tuple[int, ...]) -> Self:
661
+ encoded_shape = tuple(
662
+ BitStrideArrayEncoding._round_power2_32bit(dim) for dim in desired_shape
663
+ )
664
+ bit_stride = tuple(int(math.log2(dim)) for dim in encoded_shape)
665
+ return cls(
666
+ desired_shape=desired_shape,
667
+ encoded_shape=encoded_shape,
668
+ bit_stride=bit_stride,
669
+ )
670
+
671
+ def decode(self, values: list[float]) -> np.ndarray:
672
+ decoded = np.reshape(values, self.encoded_shape)
673
+ return decoded[tuple(slice(0, dim) for dim in self.desired_shape)]
674
+
675
+
547
676
  # ==================================================================================================
548
677
  # Utilities
549
678
  # ==================================================================================================
@@ -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.dataclasses import dataclass
16
+ from pydantic import BaseModel
16
17
 
17
18
 
18
- @dataclass
19
- class InvalidDevice:
20
- message: str
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,63 @@
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
+ @dataclass
33
+ class DeviceData:
34
+ # TODO: retire DeviceInfo the next SDK release
35
+ qpu: SuperconductingProcessor # | OtherSDKProcessorType
36
+ controller_info: QBLOXControllerInfo | QuantumMachinesControllerInfo
37
+ _defcals: dict[tuple[str, tuple[str, ...]], DefCalData]
38
+
39
+ def get_defcal(self, gate: str, addr: tuple[str, ...]) -> DefCalData | EmptyDefCalData:
40
+ """
41
+ Get the defcal data for a specific gate and address alias.
42
+ """
43
+ if self._defcals == {}:
44
+ return EmptyDefCalData(message="No defcal data available in a fresh device.")
45
+ _addr = tuple(i.lower() for i in sorted(addr))
46
+ defcal = self._defcals.get((gate, _addr))
47
+ if defcal is None:
48
+ return EmptyDefCalData(
49
+ message=f"No defcal data found for gate '{gate}' and address '{_addr}'.",
50
+ )
51
+ return defcal
52
+
53
+
54
+ class DeviceSummary(BaseModel):
55
+ id: str
56
+ organization_id: str
57
+ name: str
58
+ provider: str
59
+ updated_at: ISO8601DatetimeUTCLike
60
+ created_at: ISO8601DatetimeUTCLike
61
+
62
+ def __str__(self):
63
+ 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) - to_float(self.err_minus),
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
- ]
@@ -26,7 +26,6 @@ from boulderopalscaleupsdk.device.common import (
26
26
  )
27
27
  from boulderopalscaleupsdk.device.processor import (
28
28
  ComponentParameter,
29
- DurationComponentParameter,
30
29
  FloatComponentParameter,
31
30
  )
32
31
 
@@ -44,15 +43,15 @@ class Transmon(Component[Literal["tunable"]]):
44
43
  default=ComponentParameter(value=0.0),
45
44
  json_schema_extra={"display": {"label": "anharm", "unit": "MHz", "scale": 1e-6}},
46
45
  )
47
- t1: DurationComponentParameter = Field(
46
+ t1: ComponentParameter[Duration] = Field(
48
47
  default=ComponentParameter(value=Duration(0, TimeUnit.NS)),
49
48
  json_schema_extra={"display": {"label": "t1", "unit": "µs", "scale": 1e6}},
50
49
  )
51
- t2: DurationComponentParameter = Field(
50
+ t2: ComponentParameter[Duration] = Field(
52
51
  default=ComponentParameter(value=Duration(0, TimeUnit.NS)),
53
52
  json_schema_extra={"display": {"label": "t2", "unit": "µs", "scale": 1e6}},
54
53
  )
55
- t2_echo: DurationComponentParameter = Field(
54
+ t2_echo: ComponentParameter[Duration] = Field(
56
55
  default=ComponentParameter(value=Duration(0, TimeUnit.NS)),
57
56
  json_schema_extra={"display": {"label": "t2_echo", "unit": "µs", "scale": 1e6}},
58
57
  )
@@ -64,6 +63,14 @@ class Transmon(Component[Literal["tunable"]]):
64
63
  default=ComponentParameter(value=0.0),
65
64
  json_schema_extra={"display": {"label": "sx_vp", "unit": "V", "scale": 1}},
66
65
  )
66
+ x_ef_vp: FloatComponentParameter = Field(
67
+ default=ComponentParameter(value=0.0),
68
+ json_schema_extra={"display": {"label": "x_ef_vp", "unit": "V", "scale": 1}},
69
+ )
70
+ sx_ef_vp: FloatComponentParameter = Field(
71
+ default=ComponentParameter(value=0.0),
72
+ json_schema_extra={"display": {"label": "sx_ef_vp", "unit": "V", "scale": 1}},
73
+ )
67
74
 
68
75
  # Tunable transmon parameters.
69
76
  dc_bias: FloatComponentParameter = Field(
@@ -19,32 +19,38 @@ __all__ = [
19
19
  "CWSIterable",
20
20
  "Chi01Scan",
21
21
  "ConstantWaveform",
22
+ "DragCosineWaveform",
22
23
  "Experiment",
23
24
  "GaussianWaveform",
24
25
  "HypIterable",
25
26
  "LinspaceIterable",
27
+ "LogspaceIterable",
26
28
  "PowerRabi",
29
+ "PowerRabiEF",
27
30
  "Ramsey",
28
31
  "RangeIterable",
29
32
  "ReadoutClassifierCalibration",
30
33
  "ResonatorSpectroscopy",
31
34
  "ResonatorSpectroscopyByBias",
32
35
  "ResonatorSpectroscopyByPower",
36
+ "T2Echo",
33
37
  "TransmonAnharmonicity",
34
38
  "TransmonSpectroscopy",
39
+ "Waveform",
40
+ "update_amplitude",
35
41
  ]
36
42
 
37
43
  from .chi01_scan import Chi01Scan
38
44
  from .common import (
39
- ConstantWaveform,
40
45
  CWSIterable,
41
46
  Experiment,
42
- GaussianWaveform,
43
47
  HypIterable,
44
48
  LinspaceIterable,
49
+ LogspaceIterable,
45
50
  RangeIterable,
46
51
  )
47
52
  from .power_rabi import PowerRabi
53
+ from .power_rabi_ef import PowerRabiEF
48
54
  from .ramsey import Ramsey
49
55
  from .readout_classifier_calibration import ReadoutClassifierCalibration
50
56
  from .resonator_spectroscopy import ResonatorSpectroscopy
@@ -52,5 +58,13 @@ from .resonator_spectroscopy_by_bias import ResonatorSpectroscopyByBias
52
58
  from .resonator_spectroscopy_by_power import ResonatorSpectroscopyByPower
53
59
  from .t1 import T1
54
60
  from .t2 import T2
61
+ from .t2_echo import T2Echo
55
62
  from .transmon_anharmonicity import TransmonAnharmonicity
56
63
  from .transmon_spectroscopy import TransmonSpectroscopy
64
+ from .waveforms import (
65
+ ConstantWaveform,
66
+ DragCosineWaveform,
67
+ GaussianWaveform,
68
+ Waveform,
69
+ update_amplitude,
70
+ )
@@ -14,13 +14,13 @@
14
14
  from pydantic import PrivateAttr
15
15
 
16
16
  from .common import (
17
- ConstantWaveform,
18
17
  CWSIterable,
19
18
  Experiment,
20
19
  HypIterable,
21
20
  LinspaceIterable,
22
21
  RangeIterable,
23
22
  )
23
+ from .waveforms import ConstantWaveform
24
24
 
25
25
  DEFAULT_RECYCLE_DELAY_NS = 10_000
26
26
  DEFAULT_SHOT_COUNT = 100
@@ -34,20 +34,20 @@ class Chi01Scan(Experiment):
34
34
  Attributes
35
35
  ----------
36
36
  transmon : str
37
- The reference for the transmon to target in the experiment.
37
+ The reference for the transmon to target.
38
38
  frequencies : list[int] or LinspaceIterable or RangeIterable or CWSIterable
39
39
  or HypIterable or None, optional
40
- The frequencies at which to scan.
41
- If None, frequencies around the readout frequency will be used.
40
+ The frequencies at which to scan, in Hz.
41
+ Defaults to a scan around the readout frequency.
42
42
  recycle_delay_ns : int, optional
43
- The delay time between consecutive shots of the experiment, in nanoseconds.
44
- Defaults to 10000 ns.
43
+ The delay between consecutive shots, in nanoseconds. Defaults to 10,000 ns.
45
44
  shot_count : int, optional
46
- The number of shots to be taken in the experiment.
47
- Defaults to 100.
45
+ The number of shots to take. Defaults to 100.
48
46
  measure_waveform : ConstantWaveform or None, optional
49
47
  The waveform to use for the measurement pulse.
50
- If not provided, the measurement defcal will be used.
48
+ Defaults to the measurement defcal.
49
+ run_mixer_calibration: bool
50
+ Whether to run mixer calibrations before running a program. Defaults to False.
51
51
  """
52
52
 
53
53
  _experiment_name: str = PrivateAttr("chi01_scan")
@@ -59,3 +59,4 @@ class Chi01Scan(Experiment):
59
59
  recycle_delay_ns: int = DEFAULT_RECYCLE_DELAY_NS
60
60
  shot_count: int = DEFAULT_SHOT_COUNT
61
61
  measure_waveform: ConstantWaveform | None = None
62
+ run_mixer_calibration: bool = False
@@ -14,7 +14,6 @@
14
14
  from typing import Literal
15
15
 
16
16
  from pydantic import BaseModel, ConfigDict, Field
17
- from pydantic.dataclasses import dataclass
18
17
 
19
18
 
20
19
  class Experiment(BaseModel):
@@ -98,18 +97,3 @@ class HypIterable(_RangeIterable):
98
97
  width: float
99
98
  # The number of values. Defaults to 51.
100
99
  count: int = Field(default=51)
101
-
102
-
103
- @dataclass
104
- class ConstantWaveform:
105
- duration_ns: int
106
- scale: float
107
- waveform_type: Literal["constant"] = "constant"
108
-
109
-
110
- @dataclass
111
- class GaussianWaveform:
112
- duration_ns: int
113
- amplitude: float
114
- sigma: float
115
- waveform_type: Literal["gaussian"] = "gaussian"
@@ -14,14 +14,13 @@
14
14
  from pydantic import PrivateAttr
15
15
 
16
16
  from .common import (
17
- ConstantWaveform,
18
17
  CWSIterable,
19
18
  Experiment,
20
- GaussianWaveform,
21
19
  HypIterable,
22
20
  LinspaceIterable,
23
21
  RangeIterable,
24
22
  )
23
+ from .waveforms import ConstantWaveform, Waveform
25
24
 
26
25
  DEFAULT_SHOT_COUNT = 400
27
26
 
@@ -33,29 +32,33 @@ class PowerRabi(Experiment):
33
32
  Parameters
34
33
  ----------
35
34
  transmon : str
36
- The reference for the transmon to target in the experiment.
35
+ The reference for the transmon to target.
37
36
  scales : list[float] or LinspaceIterable or RangeIterable
38
37
  or CWSIterable or HypIterable or None, optional
39
- The scaling factors for the drive pulse amplitude. If None, a default scan will be used.
40
- drive_waveform : GaussianWaveform
38
+ The scaling factors for the drive pulse amplitude.
39
+ If None, a default scan will be used.
40
+ drive_waveform : Waveform
41
41
  The waveform to use for the drive pulse.
42
42
  recycle_delay_ns : int
43
- The delay time between consecutive shots of the experiment, in nanoseconds.
43
+ The delay between consecutive shots, in nanoseconds.
44
44
  shot_count : int, optional
45
- The number of shots to be taken in the experiment. Defaults to 400.
45
+ The number of shots to take. Defaults to 400.
46
46
  pulse_vp : float, optional
47
- The voltage per pulse. If None, a default value will be used.
47
+ The voltage per pulse, in Volts. Defaults to the amplitude of the X gate.
48
48
  measure_waveform : ConstantWaveform or None, optional
49
49
  The waveform to use for the measurement pulse.
50
- If not provided, the measurement defcal will be used.
50
+ Defaults to the measurement defcal.
51
+ run_mixer_calibration: bool
52
+ Whether to run mixer calibrations before running a program. Defaults to False.
51
53
  """
52
54
 
53
55
  _experiment_name: str = PrivateAttr("power_rabi")
54
56
 
55
57
  transmon: str
56
58
  scales: list[float] | LinspaceIterable | RangeIterable | CWSIterable | HypIterable | None = None
57
- drive_waveform: GaussianWaveform
59
+ drive_waveform: Waveform
58
60
  recycle_delay_ns: int
59
61
  shot_count: int = DEFAULT_SHOT_COUNT
60
62
  pulse_vp: float | None = None
61
63
  measure_waveform: ConstantWaveform | None = None
64
+ run_mixer_calibration: bool = False
@@ -0,0 +1,66 @@
1
+ # Copyright 2025 Q-CTRL. All rights reserved.
2
+ #
3
+ # Licensed under the Q-CTRL Terms of service (the "License"). Unauthorized
4
+ # copying or use of this file, via any medium, is strictly prohibited.
5
+ # Proprietary and confidential. You may not use this file except in compliance
6
+ # with the License. You may obtain a copy of the License at
7
+ #
8
+ # https://q-ctrl.com/terms
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS. See the
12
+ # License for the specific language.
13
+
14
+ from pydantic import PrivateAttr
15
+
16
+ from .common import (
17
+ CWSIterable,
18
+ Experiment,
19
+ HypIterable,
20
+ LinspaceIterable,
21
+ RangeIterable,
22
+ )
23
+ from .waveforms import ConstantWaveform, Waveform
24
+
25
+ DEFAULT_RECYCLE_DELAY_NS = 200_000
26
+ DEFAULT_SHOT_COUNT = 400
27
+ DEFAULT_DURATION_NS = 2_000
28
+
29
+
30
+ class PowerRabiEF(Experiment):
31
+ """
32
+ Parameters for running a Power Rabi EF experiment.
33
+
34
+ Parameters
35
+ ----------
36
+ transmon : str
37
+ The reference for the transmon to target.
38
+ scales : list[float] or LinspaceIterable or RangeIterable
39
+ or CWSIterable or HypIterable or None, optional
40
+ The scaling factors for the drive pulse amplitude.
41
+ If None, a default scan will be used.
42
+ drive_waveform : Waveform
43
+ The waveform to use for the drive pulse.
44
+ recycle_delay_ns : int
45
+ The delay between consecutive shots, in nanoseconds.
46
+ shot_count : int, optional
47
+ The number of shots to take. Defaults to 400.
48
+ pulse_vp : float, optional
49
+ The voltage per pulse, in Volts. Defaults to the amplitude of the X gate.
50
+ measure_waveform : ConstantWaveform or None, optional
51
+ The waveform to use for the measurement pulse.
52
+ Defaults to the measurement defcal.
53
+ run_mixer_calibration: bool
54
+ Whether to run mixer calibrations before running a program. Defaults to False.
55
+ """
56
+
57
+ _experiment_name: str = PrivateAttr("power_rabi_ef")
58
+
59
+ transmon: str
60
+ scales: list[float] | LinspaceIterable | RangeIterable | CWSIterable | HypIterable | None = None
61
+ drive_waveform: Waveform
62
+ recycle_delay_ns: int = DEFAULT_RECYCLE_DELAY_NS
63
+ shot_count: int = DEFAULT_SHOT_COUNT
64
+ pulse_vp: float | None = None
65
+ measure_waveform: ConstantWaveform | None = None
66
+ run_mixer_calibration: bool = False