boulder-opal-scale-up-sdk 1.0.6__py3-none-any.whl → 1.0.8__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.
Files changed (46) hide show
  1. {boulder_opal_scale_up_sdk-1.0.6.dist-info → boulder_opal_scale_up_sdk-1.0.8.dist-info}/METADATA +4 -6
  2. {boulder_opal_scale_up_sdk-1.0.6.dist-info → boulder_opal_scale_up_sdk-1.0.8.dist-info}/RECORD +46 -39
  3. boulderopalscaleupsdk/common/dtypes.py +36 -10
  4. boulderopalscaleupsdk/device/controller/qblox.py +159 -54
  5. boulderopalscaleupsdk/device/device.py +0 -1
  6. boulderopalscaleupsdk/device/processor/superconducting_processor.py +21 -1
  7. boulderopalscaleupsdk/experiments/chi01_scan.py +1 -1
  8. boulderopalscaleupsdk/experiments/cz_spectroscopy_by_bias.py +29 -48
  9. boulderopalscaleupsdk/experiments/drag_leakage_calibration.py +1 -4
  10. boulderopalscaleupsdk/experiments/fine_amplitude_calibration.py +4 -3
  11. boulderopalscaleupsdk/experiments/power_rabi.py +3 -3
  12. boulderopalscaleupsdk/experiments/power_rabi_ef.py +3 -3
  13. boulderopalscaleupsdk/experiments/ramsey.py +1 -1
  14. boulderopalscaleupsdk/experiments/ramsey_ef.py +1 -1
  15. boulderopalscaleupsdk/experiments/readout_classifier.py +2 -2
  16. boulderopalscaleupsdk/experiments/readout_optimization.py +3 -3
  17. boulderopalscaleupsdk/experiments/resonator_spectroscopy.py +1 -1
  18. boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_bias.py +3 -3
  19. boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_power.py +1 -1
  20. boulderopalscaleupsdk/experiments/t1.py +1 -1
  21. boulderopalscaleupsdk/experiments/t2.py +2 -2
  22. boulderopalscaleupsdk/experiments/t2_echo.py +1 -1
  23. boulderopalscaleupsdk/experiments/transmon_anharmonicity.py +1 -1
  24. boulderopalscaleupsdk/experiments/transmon_spectroscopy.py +2 -2
  25. boulderopalscaleupsdk/experiments/voltage_bias_fine_tune.py +7 -5
  26. boulderopalscaleupsdk/experiments/waveforms.py +15 -0
  27. boulderopalscaleupsdk/experiments/zz_ramsey.py +8 -14
  28. boulderopalscaleupsdk/plotting/dtypes.py +1 -1
  29. boulderopalscaleupsdk/protobuf/v1/device_pb2.py +33 -39
  30. boulderopalscaleupsdk/protobuf/v1/device_pb2.pyi +10 -18
  31. boulderopalscaleupsdk/protobuf/v1/device_pb2_grpc.py +0 -34
  32. boulderopalscaleupsdk/protobuf/v1/resource_pb2.py +40 -0
  33. boulderopalscaleupsdk/protobuf/v1/resource_pb2.pyi +52 -0
  34. boulderopalscaleupsdk/protobuf/v1/resource_pb2_grpc.py +104 -0
  35. boulderopalscaleupsdk/routines/__init__.py +2 -0
  36. boulderopalscaleupsdk/routines/coupler_discovery.py +37 -0
  37. boulderopalscaleupsdk/routines/one_qubit_calibration.py +4 -0
  38. boulderopalscaleupsdk/routines/resonator_mapping.py +1 -1
  39. boulderopalscaleupsdk/routines/transmon_retuning.py +0 -4
  40. boulderopalscaleupsdk/solutions/__init__.py +22 -0
  41. boulderopalscaleupsdk/solutions/common.py +23 -0
  42. boulderopalscaleupsdk/solutions/placeholder_solution.py +28 -0
  43. boulderopalscaleupsdk/third_party/quantum_machines/__init__.py +15 -0
  44. boulderopalscaleupsdk/third_party/quantum_machines/config.py +0 -3
  45. {boulder_opal_scale_up_sdk-1.0.6.dist-info → boulder_opal_scale_up_sdk-1.0.8.dist-info}/LICENSE +0 -0
  46. {boulder_opal_scale_up_sdk-1.0.6.dist-info → boulder_opal_scale_up_sdk-1.0.8.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: boulder-opal-scale-up-sdk
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Summary: Q-CTRL Boulder Opal Scale Up Python SDK
5
5
  License: https://q-ctrl.com/terms
6
6
  Keywords: black opal,boulder opal,fire opal,nisq,open controls,q control,q ctrl,q-control,q-ctrl,qcontrol,qctrl,quantum,quantum algorithms,quantum circuits,quantum coding,quantum coding software,quantum computing,quantum control,quantum control software,quantum control theory,quantum engineering,quantum error correction,quantum firmware,quantum fundamentals,quantum sensing,qubit,qudit
@@ -14,10 +14,8 @@ Classifier: Environment :: Console
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: Intended Audience :: Education
16
16
  Classifier: Intended Audience :: Science/Research
17
- Classifier: License :: Other/Proprietary License
18
17
  Classifier: Natural Language :: English
19
18
  Classifier: Operating System :: OS Independent
20
- Classifier: Programming Language :: Python :: 3
21
19
  Classifier: Programming Language :: Python :: 3.11
22
20
  Classifier: Programming Language :: Python :: 3.12
23
21
  Classifier: Topic :: Internet :: WWW/HTTP
@@ -27,13 +25,13 @@ Classifier: Topic :: Software Development :: Embedded Systems
27
25
  Classifier: Topic :: System :: Distributed Computing
28
26
  Provides-Extra: quantum-machines
29
27
  Requires-Dist: attrs (>=25.1.0,<26.0.0)
30
- Requires-Dist: googleapis-common-protos (>=1.69.2,<2.0.0)
31
- Requires-Dist: numpy (>=1.26.4,<2.0.0)
28
+ Requires-Dist: googleapis-common-protos (>=1.69.2,<1.70.0)
29
+ Requires-Dist: numpy (>=2.3.3,<3.0.0)
32
30
  Requires-Dist: pydantic (>=2.10.4,<3.0.0)
33
31
  Requires-Dist: pydantic-settings (>=2.7.0,<3.0.0)
34
32
  Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
35
33
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
36
- Requires-Dist: qm-qua (==1.2.1) ; extra == "quantum-machines"
34
+ Requires-Dist: qm-qua ; extra == "quantum-machines"
37
35
  Project-URL: Facebook, https://www.facebook.com/qctrl
38
36
  Project-URL: GitHub, https://github.com/qctrl
39
37
  Project-URL: Homepage, https://q-ctrl.com
@@ -2,7 +2,7 @@ boulderopalscaleupsdk/__init__.py,sha256=nD3YDqPiE52mmuUrIlDUrYSyljpMsDJvc5HsubB
2
2
  boulderopalscaleupsdk/agent/__init__.py,sha256=aFkAtHJDOdXA126JklxYz0ix1k4lCcLLS9DQp8zUKMk,1092
3
3
  boulderopalscaleupsdk/agent/worker.py,sha256=mCwVMbWw2saAQKyQuozhEz9JJDwpezesItZim-5qWQ8,9176
4
4
  boulderopalscaleupsdk/common/__init__.py,sha256=vLaJ1FP36xV5eX6VD7nShqxVBm3UzsBMIK1pmSiTIag,550
5
- boulderopalscaleupsdk/common/dtypes.py,sha256=YAq0qb85-YkGP1idsO5uplm_xVvTEcED9_A81-Jco3c,10722
5
+ boulderopalscaleupsdk/common/dtypes.py,sha256=r5ht75tCHNZUfq7jlmlUVEtKgYFOo1n35zgOWj6n6z8,11639
6
6
  boulderopalscaleupsdk/common/typeclasses.py,sha256=dUpM4vDNQCxVuB45LTyEWO7SuLyzUlG6mJuseM80Chw,3668
7
7
  boulderopalscaleupsdk/constants.py,sha256=2B3LrPH8bAQzzlkRYP5nyIkRT3LmfdIzzxiFKHWICPQ,682
8
8
  boulderopalscaleupsdk/device/__init__.py,sha256=vLaJ1FP36xV5eX6VD7nShqxVBm3UzsBMIK1pmSiTIag,550
@@ -10,70 +10,77 @@ boulderopalscaleupsdk/device/common.py,sha256=NxiFdpHXd1Es2_8iqUeAqhL4R4W-POzHoW
10
10
  boulderopalscaleupsdk/device/config_loader.py,sha256=kGUaUtYAREYN7grtQ9SUO3dPUqiHVUnVOQbXFZpXvtw,3414
11
11
  boulderopalscaleupsdk/device/controller/__init__.py,sha256=ePkrCbX1ORm05fS0BEQ8cx7DPxGGN6IkUoNH6eQL1DM,1136
12
12
  boulderopalscaleupsdk/device/controller/base.py,sha256=xuT4VzRulknxppiQpzYYn_TwuRNfV9wHMqjN2okA8Lw,793
13
- boulderopalscaleupsdk/device/controller/qblox.py,sha256=c5RXqS9z2e-6Kso_eJIznIqJMR0kqL1NJRvJboYLafE,29377
13
+ boulderopalscaleupsdk/device/controller/qblox.py,sha256=1ZsfvUe7Vbu-KZf9W2YluJ1eeUqLWDivFYvd1PvXeDc,33312
14
14
  boulderopalscaleupsdk/device/controller/quantum_machines.py,sha256=dkBSAqdEDADpn-qq7SkiZ-Rfnwh1NLQruqJGEW06ikU,7784
15
15
  boulderopalscaleupsdk/device/controller/resolver.py,sha256=hW5U58dmJf_IGmD4EK_bbn0XU-N9autFBFHmqbRnY2Y,3812
16
16
  boulderopalscaleupsdk/device/defcal.py,sha256=3IPmcLx89ZDv9sCPncozWA-hhnZRZK3OiOiBcAW05lI,739
17
- boulderopalscaleupsdk/device/device.py,sha256=smNgGtPs4xx2xyBiEtu178EiAW0ylMklv-wOSSnvoGA,2212
17
+ boulderopalscaleupsdk/device/device.py,sha256=3IzYd0n_Nus4n5bgZKIraUXWLcsfoThCkjQYG0ZVSZY,2170
18
18
  boulderopalscaleupsdk/device/processor/__init__.py,sha256=YLn1UTCIX0CYfah1muLXrhujJx7sz56JZQ4Pn4DnKd0,972
19
19
  boulderopalscaleupsdk/device/processor/common.py,sha256=rF2mq6X9Zl99mMc5JOqvKZEKCnfA0uUPsrwVWRiIOUA,9661
20
- boulderopalscaleupsdk/device/processor/superconducting_processor.py,sha256=pt4hWdRoBD8EWYlG1HDPfumZNdRzo7Be9FDqHoRAFDw,13394
20
+ boulderopalscaleupsdk/device/processor/superconducting_processor.py,sha256=8rkfvkMIc7wTnqEaRTyl9A-4ds_D-wIXU4Ovgd7YfC8,14371
21
21
  boulderopalscaleupsdk/errors.py,sha256=iN3Y1bQ-GnSLuTCKLG3H1OTbUpW-ibFIEfT3jPuMW48,735
22
22
  boulderopalscaleupsdk/experiments/__init__.py,sha256=zozsQtxhXz1s1cNoUoJ2lQlAOAbp2ozVhquxXw3KrF8,2485
23
- boulderopalscaleupsdk/experiments/chi01_scan.py,sha256=vcvRbC7UyE_7UfduhcaVJEo5qJxwXQcPTjigXOKBtCc,2280
23
+ boulderopalscaleupsdk/experiments/chi01_scan.py,sha256=L9Xf-01l7uAGwg8FhnqWuG3QmE567meYVUD_0RTWYYI,2290
24
24
  boulderopalscaleupsdk/experiments/common.py,sha256=m-A91hOcLYHBqL9jUI4-ia_13U0-wv711aMgCyjiGqw,2535
25
- boulderopalscaleupsdk/experiments/cz_spectroscopy_by_bias.py,sha256=UFGe_gZ2Z2iHIc5BBuAT5brznutlWOOcT7zkx6hU32s,2937
26
- boulderopalscaleupsdk/experiments/drag_leakage_calibration.py,sha256=ey0g5hJBGgrtGdwtrMXGCYvd6TvgWIG63wfSJ6ahGzE,2400
27
- boulderopalscaleupsdk/experiments/fine_amplitude_calibration.py,sha256=3Us-C9Ss5Ks-1TYSIYToEY1hgZr2VF2CY22cgOWk530,1963
28
- boulderopalscaleupsdk/experiments/power_rabi.py,sha256=-0iT1KHY-HecEvjJ5nQcmWyRNd1505xfVmG_j1gqZXs,2453
29
- boulderopalscaleupsdk/experiments/power_rabi_ef.py,sha256=j-XoGcNXPtb-NiwwuXhqZaGl70Fh5RKsdZNFSO2hHz0,2461
30
- boulderopalscaleupsdk/experiments/ramsey.py,sha256=Ym1dqAs3b3nPfNp4T3TPIMTDDMT6eBJ-CUCua9CyKl8,2192
31
- boulderopalscaleupsdk/experiments/ramsey_ef.py,sha256=l_i4s_5bw8QNI6KAoXcyYAjoMZgsfuoCluZOQR-CCaQ,2203
32
- boulderopalscaleupsdk/experiments/readout_classifier.py,sha256=skKl323pGH3hk_SoCSq613QOEJfUNNSrbuQshF5GXpQ,1980
33
- boulderopalscaleupsdk/experiments/readout_optimization.py,sha256=Rsc0G8-4vI5mLh1iYtnnzCROgivSNuAYRNUhACXaJS0,1943
34
- boulderopalscaleupsdk/experiments/resonator_spectroscopy.py,sha256=QMEh52zh-ZsraYn2sq7qnE0g0z0dhBsSndIepBrBzVM,2262
35
- boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_bias.py,sha256=3YmpHDUQCP4P1BKk79tVtvnHm8D80y7h2QTb-UUBPE4,2855
36
- boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_power.py,sha256=jtQHeeQyshlg9_WJwwFONk6FKWRbE88zURwyihY4ZxI,2383
37
- boulderopalscaleupsdk/experiments/t1.py,sha256=Tia4siBGcQ2-WkKw8F5y6smwW5_h3INbKUxM1nPiufI,1635
38
- boulderopalscaleupsdk/experiments/t2.py,sha256=LQHwFyOLa0MiVUxRCcovGBVC32bfA7xzfBILgXq0CCs,1790
39
- boulderopalscaleupsdk/experiments/t2_echo.py,sha256=dgohfuliI2f3KsiyhfSMrwJjwq5r3meakEPmH-3HUAY,1804
40
- boulderopalscaleupsdk/experiments/transmon_anharmonicity.py,sha256=3Y-vk1dLUeP8Vnr38CQ_AoTi63SNqls5m6tswcMETWo,2801
41
- boulderopalscaleupsdk/experiments/transmon_spectroscopy.py,sha256=y6aZwW2jys5JOYqozfTKpLU5ZXlwP1AQVm73MWO-zB4,2402
42
- boulderopalscaleupsdk/experiments/voltage_bias_fine_tune.py,sha256=P5RuASiP3UUudLQIFWIznTuhCOK0ndj9FN9l6bHv_xU,2060
43
- boulderopalscaleupsdk/experiments/waveforms.py,sha256=zyzADCra9T0wphltrhfj75wB2y4qwTSvcU2mBJQNrjU,1712
44
- boulderopalscaleupsdk/experiments/zz_ramsey.py,sha256=mpek0jKVdbOUnXb0KNYsxGTo3MnMR8SG4cYiHOA1KY4,2017
25
+ boulderopalscaleupsdk/experiments/cz_spectroscopy_by_bias.py,sha256=Q0bdoBUevHKyuercyZfHTCEkbm2ST0KqE9WeGCrHMfA,2377
26
+ boulderopalscaleupsdk/experiments/drag_leakage_calibration.py,sha256=JK_GIXIY0GcLGBjEBKwTryfM9ERQVKBBIGIeOyKh2TA,2319
27
+ boulderopalscaleupsdk/experiments/fine_amplitude_calibration.py,sha256=OZXVtS33eYmpuH30zDl4yycdJMNj99DIj_eTvw3DUlk,2039
28
+ boulderopalscaleupsdk/experiments/power_rabi.py,sha256=zCMMO4JvyFvzOzpI75_loVN1O6tvEPx_maJ0m7mp0_E,2473
29
+ boulderopalscaleupsdk/experiments/power_rabi_ef.py,sha256=aEjSzqWORRFt2OZPRD7QzyGJe3vqouAHZcge-N4eZ0Q,2481
30
+ boulderopalscaleupsdk/experiments/ramsey.py,sha256=Y_vTaod2bateukHx28FAK87puRZXlvgFQd8bz38eZhw,2202
31
+ boulderopalscaleupsdk/experiments/ramsey_ef.py,sha256=eV98iMqKCXhs4v66H1n-TzvcQl-y711_1-H-btZzndY,2204
32
+ boulderopalscaleupsdk/experiments/readout_classifier.py,sha256=tEuH5__QTGRPjqUwR5Z6PYZ9skZW592UCH5P-OEkPdw,2000
33
+ boulderopalscaleupsdk/experiments/readout_optimization.py,sha256=pZffzOzN7iNOd1-iKcfvAivecBTSg4dRsKG0J-oT5yg,1963
34
+ boulderopalscaleupsdk/experiments/resonator_spectroscopy.py,sha256=aE5vOhCZiAbPUyeKATbGq1499bLk-Dj89XF_5a6HyRc,2272
35
+ boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_bias.py,sha256=7Aq-VgENmMfGQ7yEz_zW9CaXpCjYdcWxqGK_mXAKewo,2864
36
+ boulderopalscaleupsdk/experiments/resonator_spectroscopy_by_power.py,sha256=xxe43VHofvmzL715r_8FquGMMYIJkkC2Eu5yOLN5N6A,2393
37
+ boulderopalscaleupsdk/experiments/t1.py,sha256=bL5ur-0Z3n34pH5Eski_ctv5CI552bup_chRiTWphaw,1645
38
+ boulderopalscaleupsdk/experiments/t2.py,sha256=Jp3v8aKLIHoFlpEyJVM_aLB_cV8vCj9_2kBWeohSFbs,1800
39
+ boulderopalscaleupsdk/experiments/t2_echo.py,sha256=tFgOWEjGlhEnLnDvR3v-v92MBQlSqG_dSWHMsrSLWjI,1814
40
+ boulderopalscaleupsdk/experiments/transmon_anharmonicity.py,sha256=G8ywCKIccHW4XlJYUoNq2bL0L1547G7VAOGjaXaNShY,2811
41
+ boulderopalscaleupsdk/experiments/transmon_spectroscopy.py,sha256=QFeTRmDE-CM7mIX0GTtVTtg3n-GJ70R1Rl6k9jOAyHU,2412
42
+ boulderopalscaleupsdk/experiments/voltage_bias_fine_tune.py,sha256=fORNVo2PBs-gwNpOk04nD0uPrKqw7I-IoaC9A4lxDME,2114
43
+ boulderopalscaleupsdk/experiments/waveforms.py,sha256=zxEQaSaJP10ZHTZS4wqXD4E-sAc-F0l5H6uBiPjibtg,2239
44
+ boulderopalscaleupsdk/experiments/zz_ramsey.py,sha256=l4cc3xmi1xxvRn-HMt0-9Ff315xGLX8NMkuu-hzEsyU,1780
45
45
  boulderopalscaleupsdk/grpc_interceptors/__init__.py,sha256=PkdOlllLbRWycofvGCCxDY60Ydp_U0QM5l4u3Fsnno4,616
46
46
  boulderopalscaleupsdk/grpc_interceptors/auth.py,sha256=PSe_b9ckqhcb3xf1Y4jpn1TjfEWfokkarqfUHIz6HIs,4165
47
47
  boulderopalscaleupsdk/grpc_interceptors/error.py,sha256=PeEH4eFGFfRNwBw8iDuKVQo02_mItAUVehYa7AiZNqI,12067
48
48
  boulderopalscaleupsdk/plotting/__init__.py,sha256=pkdCky3YHqc3PLRy_h9puJKAO4AqSJGebb26x7XayR4,959
49
- boulderopalscaleupsdk/plotting/dtypes.py,sha256=Sh9L6nRkvw9PIMKaOEieHyLuvTRqWfDMi1h4dnqzeR0,4967
49
+ boulderopalscaleupsdk/plotting/dtypes.py,sha256=Qg98_J4dJkz63vZY8Vg_Sz0BfeG0J7bypPiBIFDPmfk,5012
50
50
  boulderopalscaleupsdk/protobuf/v1/agent_pb2.py,sha256=r8vE_d7e5iG04UCDeV0FgxOVOSflOlwmSs2I5CotDBU,6147
51
51
  boulderopalscaleupsdk/protobuf/v1/agent_pb2.pyi,sha256=bwRNbnwbwkqx7OSE4EurVS6865zqdtqmTcRjwJNH7OI,3082
52
52
  boulderopalscaleupsdk/protobuf/v1/agent_pb2_grpc.py,sha256=gfv0Ncc6_MS38Lzj6NHYXhwJVo7qK7XYQiCsi-KTl5w,8593
53
- boulderopalscaleupsdk/protobuf/v1/device_pb2.py,sha256=eZit0D_DsElSnteQYT3w5cGk4i_YFogdXpGrVLUz3M0,12738
54
- boulderopalscaleupsdk/protobuf/v1/device_pb2.pyi,sha256=PSaHKPtJ-bUb99mja83p9-OynqqyFE44LVQAMpq8tEg,7765
55
- boulderopalscaleupsdk/protobuf/v1/device_pb2_grpc.py,sha256=DxsxBgww66W-pV8Ba01YAKDK-zPC8KGOQOXQnO1WnMk,21122
53
+ boulderopalscaleupsdk/protobuf/v1/device_pb2.py,sha256=_FhknSKCCb44sR2tKefDVOG7uoaxsKz99DASq69cxAc,12066
54
+ boulderopalscaleupsdk/protobuf/v1/device_pb2.pyi,sha256=3yJwp1sxVs0bvwiZ58bEX5aNJmKWQWSfEXFJYo6V33k,7559
55
+ boulderopalscaleupsdk/protobuf/v1/device_pb2_grpc.py,sha256=gHWoNIVcMYx0T7LsbtGNNXIGBMagEgfe-KKbhx5B4I0,19339
56
56
  boulderopalscaleupsdk/protobuf/v1/job_pb2.py,sha256=awz-VTyt6lMJmf8ulW4kxSk-U_pYQOoZAyieww-RvaQ,4737
57
57
  boulderopalscaleupsdk/protobuf/v1/job_pb2.pyi,sha256=C6ygytheTgOohv3bA_CoCFh1Dvqcwwi_1dCsI93kTm4,2354
58
58
  boulderopalscaleupsdk/protobuf/v1/job_pb2_grpc.py,sha256=rwMHUYSaNT8uScrRxwEtR5niA3ghNjqJ6mzKVniBT9Q,6336
59
+ boulderopalscaleupsdk/protobuf/v1/resource_pb2.py,sha256=kHajKhGXlgFDdalqWZ2OoZj6eTACGKqx-QvtvrzXBzg,4259
60
+ boulderopalscaleupsdk/protobuf/v1/resource_pb2.pyi,sha256=666p6LTZOIuT_ms9NWBEuKDQoqy-1jIfIp1xktcnkLw,2306
61
+ boulderopalscaleupsdk/protobuf/v1/resource_pb2_grpc.py,sha256=iE0_cs9JUicHH6R5JM0i12Gygxs03Ag55_N1fYhuOoQ,4669
59
62
  boulderopalscaleupsdk/protobuf/v1/task_pb2.py,sha256=Sd-xzEOJkEZAFw_A0cNXVKOZ_Q71XrIkMl8OTuL9zoo,7132
60
63
  boulderopalscaleupsdk/protobuf/v1/task_pb2.pyi,sha256=o4QeMmUCT_x19eL7dlNE0LtdqJi1e9kv2gMiD7Z4pwg,6048
61
64
  boulderopalscaleupsdk/protobuf/v1/task_pb2_grpc.py,sha256=yP2FZ148RmSYFHivatCXuM13oRBDA5wBwmvhZQNBTXQ,5826
62
65
  boulderopalscaleupsdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
- boulderopalscaleupsdk/routines/__init__.py,sha256=4oxML7Ztq7gRWtvDZW_-QUr_Svez8kKIxXIuYhT3LyU,1009
66
+ boulderopalscaleupsdk/routines/__init__.py,sha256=4WUUHw681esSf8R3K11AAegBbrRhrD7TgOAdj1PZZq0,1081
64
67
  boulderopalscaleupsdk/routines/common.py,sha256=BvZ1DvCJDFHkauOEq8z-Mkj_mwO-1BSRLHkiTaIbJ0U,775
65
- boulderopalscaleupsdk/routines/one_qubit_calibration.py,sha256=n3QifgKVcQu0Idkxj0Hk9husi3xcsoMJz7t3nTvBwEk,1018
66
- boulderopalscaleupsdk/routines/resonator_mapping.py,sha256=Jp7ubmP0knRaoSE6IZnww2q0tixmjZ1HG3NjvEW0Rhs,1186
68
+ boulderopalscaleupsdk/routines/coupler_discovery.py,sha256=8f-zEdO8DoAIsLEP-rJn2f2i4nvD4MJ2Org9lElVPAg,1128
69
+ boulderopalscaleupsdk/routines/one_qubit_calibration.py,sha256=n6CxupOkQal7-JiOqA81Y5Bsr5u51i5-q0bz-eOfim0,1251
70
+ boulderopalscaleupsdk/routines/resonator_mapping.py,sha256=FX2zKeeP-UM09OOr9X451-b9sEvDupcUe4tDXxuRPZQ,1196
67
71
  boulderopalscaleupsdk/routines/transmon_coherence.py,sha256=qwFlrDgwRdo4rWwXJoQ0pLJSKiUnzMYasK6kMHWhlHI,1074
68
72
  boulderopalscaleupsdk/routines/transmon_discovery.py,sha256=ZI-mQQSlUWhU1J_H1ie9Hz4TOK3aMxZqtfX2AgHNrKI,1453
69
- boulderopalscaleupsdk/routines/transmon_retuning.py,sha256=3C9iSE7D3dnPbj-RJw5pogAwS3Hs9C-9uBLQT81qeAA,1423
73
+ boulderopalscaleupsdk/routines/transmon_retuning.py,sha256=sCt__KrabNupd5dJJAcZqk6Jf026r_cN4wm1lJ3YwWs,1239
74
+ boulderopalscaleupsdk/solutions/__init__.py,sha256=4aNTanH2A7QBvkilWYYpdeGZiUssgbqdLbP2rAyLUX4,717
75
+ boulderopalscaleupsdk/solutions/common.py,sha256=4IxWr2sXWYms1K4c460FQ129nlljF9ryJ_oNUQkV9xQ,779
76
+ boulderopalscaleupsdk/solutions/placeholder_solution.py,sha256=lWP9uNW_AnuPEwbHSl1psdqlYchWvfDSceF612taP9o,907
70
77
  boulderopalscaleupsdk/third_party/__init__.py,sha256=b9T2IVfz9bMenSsLQuaf4A14ySinjNnSGx68hLsFHZE,596
71
- boulderopalscaleupsdk/third_party/quantum_machines/__init__.py,sha256=KOAJ_Yq95S2eCyZzHZwswNj8cl7YRbZ8RXwcQJ9awII,2536
72
- boulderopalscaleupsdk/third_party/quantum_machines/config.py,sha256=DVqXEtnMSqKOJvAX2eZptv66cL5uPLJTILiv_E4ZMcA,20764
78
+ boulderopalscaleupsdk/third_party/quantum_machines/__init__.py,sha256=D-6LLQacjr4fBNb4AvF-caQtozOoKiJsv6LuMBUNHEE,3195
79
+ boulderopalscaleupsdk/third_party/quantum_machines/config.py,sha256=LSLmZRbOa8DXqSvXKL0y8mEsNxoRHOdr3i3pYXafjdY,20656
73
80
  boulderopalscaleupsdk/third_party/quantum_machines/constants.py,sha256=5PpAi6MZ53yEinBHerY2ssi30BR37JnLBWL21irpZ8Y,870
74
81
  boulderopalscaleupsdk/utils/__init__.py,sha256=vLaJ1FP36xV5eX6VD7nShqxVBm3UzsBMIK1pmSiTIag,550
75
82
  boulderopalscaleupsdk/utils/serial_utils.py,sha256=SzvY-WFD0b8IGSh6PTv7F9y1W2IPPYmj8pY3AsX-xlM,2062
76
- boulder_opal_scale_up_sdk-1.0.6.dist-info/LICENSE,sha256=wqX4S5Brcwkwo750l9grSspwm0cyMsZtZEa1Vx3_WiE,36587
77
- boulder_opal_scale_up_sdk-1.0.6.dist-info/METADATA,sha256=lFcAjkovhc9tDv3p4aZVIBR0CpqY3zs9MhVfeIkQ5N4,2419
78
- boulder_opal_scale_up_sdk-1.0.6.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
79
- boulder_opal_scale_up_sdk-1.0.6.dist-info/RECORD,,
83
+ boulder_opal_scale_up_sdk-1.0.8.dist-info/LICENSE,sha256=wqX4S5Brcwkwo750l9grSspwm0cyMsZtZEa1Vx3_WiE,36587
84
+ boulder_opal_scale_up_sdk-1.0.8.dist-info/METADATA,sha256=DXWuUYplUMJ_Vr9UirCcED-gLLAqrV7MTCf-zY3ow7k,2312
85
+ boulder_opal_scale_up_sdk-1.0.8.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
86
+ boulder_opal_scale_up_sdk-1.0.8.dist-info/RECORD,,
@@ -33,7 +33,7 @@ from dateutil.parser import isoparse
33
33
  from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, PlainSerializer, TypeAdapter
34
34
  from pydantic.dataclasses import dataclass
35
35
 
36
- from boulderopalscaleupsdk.plotting.dtypes import Plot # noqa: TC001
36
+ from boulderopalscaleupsdk.plotting import Plot
37
37
 
38
38
  GrpcMetadata = list[tuple[str, str | bytes]]
39
39
 
@@ -158,7 +158,7 @@ class Duration:
158
158
 
159
159
  self.value = int(self.value)
160
160
  try:
161
- self._np_rep = np.timedelta64(self.value, self.unit)
161
+ self._np_rep = np.timedelta64(self.value, self.unit.value)
162
162
  except ValueError as e:
163
163
  raise err from e
164
164
 
@@ -179,12 +179,17 @@ class Duration:
179
179
  return bool(self._np_rep == other._np_rep)
180
180
  return False
181
181
 
182
- def convert(self, unit: TimeUnit) -> Duration | InvalidDurationConversion:
183
- val: np.float64 = self._np_rep / np.timedelta64(1, unit)
182
+ def __str__(self):
183
+ return f"{self.value} {self.unit.value}"
184
+
185
+ def convert(self, unit: TimeUnit | str) -> Duration | InvalidDurationConversion:
186
+ unit_enum = TimeUnit(unit) if isinstance(unit, str) else unit
187
+
188
+ val = np.float64(self._np_rep / np.timedelta64(1, unit_enum.value))
184
189
  if val.is_integer():
185
- return Duration(int(val), unit)
190
+ return Duration(int(val), unit_enum)
186
191
  return InvalidDurationConversion(
187
- f"fail to convert to {unit} with {self.value} {self.unit}.",
192
+ f"fail to convert to {unit_enum} with {self.value} {self.unit}.",
188
193
  )
189
194
 
190
195
  def to_ns(self) -> Duration:
@@ -342,14 +347,17 @@ class JobSummary(BaseModel):
342
347
 
343
348
  class JobDataEntry(BaseModel):
344
349
  message: str
345
- created_at: ISO8601DatetimeUTCLike
346
- dt: str
347
- elapsed_time: str
348
- plots: list[Plot] = []
349
350
 
350
351
  class Config:
351
352
  extra = "allow"
352
353
 
354
+ def get_display_items(self) -> list[str | Plot]:
355
+ items: list[str | Plot] = [self.message]
356
+
357
+ if plots := getattr(self, "plots", []):
358
+ items.extend(TypeAdapter(Plot).validate_python(plot) for plot in plots)
359
+ return items
360
+
353
361
 
354
362
  class JobData(BaseModel):
355
363
  id: str
@@ -359,6 +367,24 @@ class JobData(BaseModel):
359
367
  device_name: str
360
368
  data: list[JobDataEntry]
361
369
 
370
+ def get_display_items(self) -> list[str | Plot]:
371
+ items: list[str | Plot] = []
372
+ message = "\n".join(
373
+ [
374
+ "JobData summary:",
375
+ f" - id: {self.id}",
376
+ f" - name: {self.name}",
377
+ f" - session_id: {self.session_id}",
378
+ f" - created_at: {self.created_at.isoformat()}",
379
+ f" - device_name: {self.device_name}",
380
+ ],
381
+ )
382
+ items.append(message)
383
+
384
+ for job_data_entry in self.data:
385
+ items.extend(job_data_entry.get_display_items())
386
+ return items
387
+
362
388
 
363
389
  DEFAULT_JOB_HISTORY_PAGE = 1
364
390
  DEFAULT_JOB_HISTORY_PAGE_SIZE = 10
@@ -20,7 +20,8 @@ __all__ = (
20
20
  "AcquisitionConfig",
21
21
  "BitStrideArrayEncoding",
22
22
  "ChannelType",
23
- "ComplexChannel",
23
+ "IQChannel",
24
+ "IQMixedChannel",
24
25
  "IndexedData",
25
26
  "ModuleAddr",
26
27
  "ModuleAddrType",
@@ -37,11 +38,11 @@ __all__ = (
37
38
  "PortAddrType",
38
39
  "PreparedProgram",
39
40
  "PreparedSequenceProgram",
40
- "RealChannel",
41
41
  "SequenceProgram",
42
42
  "SequencerAddr",
43
43
  "SequencerAddrType",
44
44
  "SequencerResults",
45
+ "SingleChannel",
45
46
  "process_sequencer_output",
46
47
  "validate_channel",
47
48
  )
@@ -54,7 +55,7 @@ from dataclasses import dataclass
54
55
  from typing import Annotated, Any, ClassVar, Literal, Self, TypeVar
55
56
 
56
57
  import numpy as np
57
- from pydantic import BaseModel, BeforeValidator, Field, PlainSerializer, model_validator
58
+ from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, PlainSerializer, model_validator
58
59
 
59
60
  from boulderopalscaleupsdk.device.controller.base import Backend, ControllerType
60
61
 
@@ -204,10 +205,20 @@ PortAddrType = Annotated[PortAddr, _addr_validator(PortAddr), PlainSerializer(st
204
205
  # ==================================================================================================
205
206
  # Signalling Channels
206
207
  # ==================================================================================================
207
- class RealChannel(BaseModel):
208
- """A real channel targeting a single hardware port on a QBLOX module."""
208
+ class IQMixedChannel(BaseModel):
209
+ """
210
+ An IQ-mixed channel.
211
+
212
+ Both sequencer AWG paths are mixed internally and routed to a single module port.
213
+
214
+ ┌─────────┐Path 0 Port
215
+ │ ├───────►│IQMixed ┌─┐
216
+ │Sequencer│ │───────►│─│
217
+ │ ├───────►│Ch └─┘
218
+ └─────────┘Path 1
219
+ """
209
220
 
210
- mode: Literal["real"] = "real"
221
+ mode: Literal["iq-mixed"] = "iq-mixed"
211
222
  port: PortAddrType
212
223
 
213
224
  @property
@@ -219,13 +230,26 @@ class RealChannel(BaseModel):
219
230
  return self.port.direction
220
231
 
221
232
  def __str__(self) -> str:
222
- return f"{self.port!s}[R]"
233
+ return f"{self.port!s}[1]"
234
+
223
235
 
236
+ class IQChannel(BaseModel):
237
+ """
238
+ An IQ channel.
224
239
 
225
- class ComplexChannel(BaseModel):
226
- """A Complex channel targeting a single hardware port on a QBLOX module."""
240
+ The sequencer AWG paths target separate ports for external IQ mixing.
227
241
 
228
- mode: Literal["complex"] = "complex"
242
+ IPort
243
+ ┌─────────┐Path 0 ┌─┐
244
+ │ ├───────►├───────►│─│
245
+ │Sequencer│ │IQ Ch ├─┤
246
+ │ ├───────►├───────►│─│
247
+ └─────────┘Path 1 └─┘
248
+ QPort
249
+
250
+ """
251
+
252
+ mode: Literal["iq"] = "iq"
229
253
  i_port: PortAddrType
230
254
  q_port: PortAddrType
231
255
 
@@ -238,10 +262,10 @@ class ComplexChannel(BaseModel):
238
262
  return self.i_port.direction
239
263
 
240
264
  def __str__(self) -> str:
241
- return f"{self.i_port!s}_{self.q_port.number}[Z]"
265
+ return f"{self.i_port!s}_{self.q_port.number}[iq]"
242
266
 
243
267
  @model_validator(mode="after")
244
- def validate_i_q_ports(self) -> "ComplexChannel":
268
+ def validate_i_q_ports(self) -> "IQChannel":
245
269
  ii = self.i_port
246
270
  qq = self.q_port
247
271
  if ii.cluster != qq.cluster or ii.slot != qq.slot:
@@ -251,7 +275,36 @@ class ComplexChannel(BaseModel):
251
275
  return self
252
276
 
253
277
 
254
- ChannelType = RealChannel | ComplexChannel
278
+ class SingleChannel(BaseModel):
279
+ """
280
+ Single channel.
281
+
282
+ A single sequencer path is routed to a single port.
283
+
284
+ ┌─────────┐Path 0 Port
285
+ │ ├───────►│Single ┌─┐
286
+ │Sequencer│ ├───────►│─│
287
+ │ │ │Ch └─┘
288
+ └─────────┘
289
+ """
290
+
291
+ mode: Literal["single"] = "single"
292
+ port: PortAddrType
293
+ path: Literal[0, 1] = 0
294
+
295
+ @property
296
+ def module(self) -> ModuleAddr:
297
+ return ModuleAddr(self.port.cluster, self.port.slot)
298
+
299
+ @property
300
+ def direction(self) -> Literal["out", "in"]:
301
+ return self.port.direction
302
+
303
+ def __str__(self) -> str:
304
+ return f"{self.port!s}_{self.port.number}[single]"
305
+
306
+
307
+ ChannelType = IQMixedChannel | IQChannel | SingleChannel
255
308
 
256
309
 
257
310
  # ==================================================================================================
@@ -318,12 +371,28 @@ class QBLOXControllerInfo(BaseModel): # pragma: no cover
318
371
  modules: dict[ModuleAddrType, ModuleType]
319
372
  port_config: dict[str, PortConnection]
320
373
 
374
+ @model_validator(mode="after")
375
+ def validate_channels(self) -> Self:
376
+ for port, port_conn in self.port_config.items():
377
+ mod_addr = port_conn.ch_out.module
378
+ mod_type = self.modules[mod_addr]
379
+
380
+ mod_constraints = DEFAULT_MODULE_CONSTRAINTS[mod_type]
381
+ if ch_iss := validate_channel(port_conn.ch_out, mod_constraints):
382
+ raise ValueError(f"Invalid channel for port {port}: {ch_iss}")
383
+ if port_conn.ch_in and (ch_iss := validate_channel(port_conn.ch_in, mod_constraints)):
384
+ raise ValueError(f"Invalid channel for port {port}: {ch_iss}")
385
+
386
+ return self
387
+
321
388
 
322
389
  # ==================================================================================================
323
390
  # Instrument management
324
391
  # ==================================================================================================
325
392
  class SequencerParams(BaseModel):
326
- nco_freq: float | None = Field(default=None)
393
+ model_config = ConfigDict(validate_assignment=True)
394
+
395
+ nco_freq: float | None = Field(default=None, ge=-500e6, le=500e6)
327
396
  gain_awg_path0: float | None = Field(default=None, ge=-1.0, le=1.0)
328
397
  offset_awg_path0: float | None = Field(default=None, ge=-1.0, le=1.0)
329
398
  gain_awg_path1: float | None = Field(default=None, ge=-1.0, le=1.0)
@@ -338,6 +407,8 @@ class SequencerParams(BaseModel):
338
407
 
339
408
 
340
409
  class QcmParams(BaseModel):
410
+ model_config = ConfigDict(validate_assignment=True)
411
+
341
412
  out0_offset: float | None = Field(default=None, ge=-2.5, le=2.5)
342
413
  out1_offset: float | None = Field(default=None, ge=-2.5, le=2.5)
343
414
  out2_offset: float | None = Field(default=None, ge=-2.5, le=2.5)
@@ -354,6 +425,8 @@ class QcmParams(BaseModel):
354
425
 
355
426
 
356
427
  class QcmRfParams(BaseModel):
428
+ model_config = ConfigDict(validate_assignment=True)
429
+
357
430
  out0_att: int | None = Field(default=None, ge=0, le=60, multiple_of=2)
358
431
  out1_att: int | None = Field(default=None, ge=0, le=60, multiple_of=2)
359
432
 
@@ -375,6 +448,8 @@ class QcmRfParams(BaseModel):
375
448
 
376
449
 
377
450
  class QrmRfParams(BaseModel):
451
+ model_config = ConfigDict(validate_assignment=True)
452
+
378
453
  out0_att: int | None = Field(default=None, ge=0, le=60, multiple_of=2)
379
454
 
380
455
  out0_in0_lo_freq: float | None = Field(default=None, gt=0)
@@ -744,6 +819,9 @@ class BitStrideArrayEncoding:
744
819
  # ==================================================================================================
745
820
  # Utilities
746
821
  # ==================================================================================================
822
+ Q1_MAX_WAVEFORM_MEMORY = 16384 # samples
823
+
824
+
747
825
  @dataclasses.dataclass
748
826
  class ModuleConstraints:
749
827
  """Physical constraints of a module."""
@@ -753,6 +831,7 @@ class ModuleConstraints:
753
831
  n_ch_out: int = 0
754
832
  n_ch_in: int = 0
755
833
  n_digital_io: int = 0
834
+ is_rf: bool = False
756
835
 
757
836
  # TODO: Confirm if ordering is important.
758
837
  ch_out_iq_pairs: list[set[int]] = dataclasses.field(default_factory=list)
@@ -773,6 +852,7 @@ DEFAULT_MODULE_CONSTRAINTS: dict[ModuleType, ModuleConstraints] = {
773
852
  n_markers=2,
774
853
  n_ch_out=2,
775
854
  n_ch_in=0,
855
+ is_rf=True,
776
856
  ),
777
857
  ModuleType.QRM: ModuleConstraints(
778
858
  n_sequencers=6,
@@ -787,6 +867,7 @@ DEFAULT_MODULE_CONSTRAINTS: dict[ModuleType, ModuleConstraints] = {
787
867
  n_markers=2,
788
868
  n_ch_out=1,
789
869
  n_ch_in=1,
870
+ is_rf=True,
790
871
  ),
791
872
  ModuleType.QRC: ModuleConstraints(
792
873
  n_sequencers=12,
@@ -835,53 +916,77 @@ def validate_channel(ch: ChannelType, constraint: ModuleConstraints) -> list[str
835
916
  return _validate_input_channel(ch, constraint)
836
917
 
837
918
 
838
- def _validate_output_channel(ch_out: ChannelType, constraint: ModuleConstraints) -> list[str]:
919
+ def _validate_output_channel(ch_out: ChannelType, constraint: ModuleConstraints) -> list[str]: # noqa: C901
839
920
  issues = []
840
- if isinstance(ch_out, RealChannel):
841
- po_out = ch_out.port
842
- if constraint.n_ch_out == 0:
843
- issues.append("module has no output ports.")
844
- elif po_out.number < 0 or po_out.number >= constraint.n_ch_out:
845
- issues.append(
846
- f"output port number {po_out.number} out-of-bounds for module, "
847
- f"must be between [0, {constraint.n_ch_out}).",
848
- )
849
- else:
850
- valid_pairs = constraint.ch_out_iq_pairs
851
- if not valid_pairs:
852
- issues.append("module does not support complex output channels.")
853
- else:
854
- po_out_i = ch_out.i_port
855
- po_out_q = ch_out.q_port
856
- if {po_out_i.number, po_out_q.number} not in valid_pairs:
921
+ match ch_out:
922
+ case IQMixedChannel():
923
+ po_out = ch_out.port
924
+ if constraint.n_ch_out == 0:
925
+ issues.append("module has no output ports.")
926
+ elif po_out.number < 0 or po_out.number >= constraint.n_ch_out:
857
927
  issues.append(
858
- f"invalid output IQ pair {{{po_out_i.number}, {po_out_q.number}}}, module only "
859
- f"supports pairs {valid_pairs}.",
928
+ f"output port number {po_out.number} out-of-bounds for module, "
929
+ f"must be between [0, {constraint.n_ch_out}).",
930
+ )
931
+ case IQChannel():
932
+ valid_pairs = constraint.ch_out_iq_pairs
933
+ if not valid_pairs:
934
+ issues.append("module does not support IQ output channels.")
935
+ else:
936
+ po_out_i = ch_out.i_port
937
+ po_out_q = ch_out.q_port
938
+ if {po_out_i.number, po_out_q.number} not in valid_pairs:
939
+ issues.append(
940
+ f"invalid output IQ pair {{{po_out_i.number}, {po_out_q.number}}}, "
941
+ f"module only supports pairs {valid_pairs}.",
942
+ )
943
+ case SingleChannel():
944
+ po_out = ch_out.port
945
+ if constraint.is_rf:
946
+ issues.append("RF modules cannot use single channels.")
947
+ if constraint.n_ch_out == 0:
948
+ issues.append("module has no output ports.")
949
+ elif po_out.number < 0 or po_out.number >= constraint.n_ch_out:
950
+ issues.append(
951
+ f"output port number {po_out.number} out-of-bounds for module, "
952
+ f"must be between [0, {constraint.n_ch_out}).",
860
953
  )
861
954
  return issues
862
955
 
863
956
 
864
- def _validate_input_channel(ch_in: ChannelType, constraint: ModuleConstraints) -> list[str]:
957
+ def _validate_input_channel(ch_in: ChannelType, constraint: ModuleConstraints) -> list[str]: # noqa: C901
865
958
  issues = []
866
- if isinstance(ch_in, RealChannel):
867
- po_in = ch_in.port
868
- if constraint.n_ch_in == 0:
869
- issues.append("module has no input ports.")
870
- elif po_in.number < 0 or po_in.number >= constraint.n_ch_in:
871
- issues.append(
872
- f"input port number {po_in.number} out-of-bounds for module, "
873
- f"must be between [0, {constraint.n_ch_in}).",
874
- )
875
- else:
876
- valid_pairs = constraint.ch_in_iq_pairs
877
- if not valid_pairs:
878
- issues.append("module does not support complex input channels.")
879
- else:
880
- po_in_i = ch_in.i_port
881
- po_in_q = ch_in.q_port
882
- if {po_in_i.number, po_in_q.number} not in valid_pairs:
959
+ match ch_in:
960
+ case IQMixedChannel():
961
+ po_in = ch_in.port
962
+ if constraint.n_ch_in == 0:
963
+ issues.append("module has no input ports.")
964
+ elif po_in.number < 0 or po_in.number >= constraint.n_ch_in:
965
+ issues.append(
966
+ f"input port number {po_in.number} out-of-bounds for module, "
967
+ f"must be between [0, {constraint.n_ch_in}).",
968
+ )
969
+ case IQChannel():
970
+ valid_pairs = constraint.ch_in_iq_pairs
971
+ if not valid_pairs:
972
+ issues.append("module does not support IQ input channels.")
973
+ else:
974
+ po_in_i = ch_in.i_port
975
+ po_in_q = ch_in.q_port
976
+ if {po_in_i.number, po_in_q.number} not in valid_pairs:
977
+ issues.append(
978
+ f"invalid input IQ pair {{{po_in_i.number}, {po_in_q.number}}}, "
979
+ f"module only supports pairs {valid_pairs}.",
980
+ )
981
+ case SingleChannel():
982
+ po_in = ch_in.port
983
+ if constraint.is_rf:
984
+ issues.append("RF modules cannot use single channels.")
985
+ if constraint.n_ch_in == 0:
986
+ issues.append("module has no input ports.")
987
+ elif po_in.number < 0 or po_in.number >= constraint.n_ch_in:
883
988
  issues.append(
884
- f"invalid input IQ pair {{{po_in_i.number}, {po_in_q.number}}}, module only "
885
- f"supports pairs {valid_pairs}.",
989
+ f"input port number {po_in.number} out-of-bounds for module, "
990
+ f"must be between [0, {constraint.n_ch_in}).",
886
991
  )
887
992
  return issues
@@ -61,7 +61,6 @@ class DeviceSummary(BaseModel):
61
61
  provider: str
62
62
  updated_at: ISO8601DatetimeUTCLike
63
63
  created_at: ISO8601DatetimeUTCLike
64
- copied_from: DeviceName | None = None
65
64
 
66
65
  def __str__(self):
67
66
  return f'DeviceSummary(name="{self.name}", id="{self.id}")'