sqil-core 0.1.0__py3-none-any.whl → 1.1.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.
Files changed (36) hide show
  1. sqil_core/__init__.py +1 -0
  2. sqil_core/config_log.py +42 -0
  3. sqil_core/experiment/__init__.py +11 -0
  4. sqil_core/experiment/_analysis.py +125 -0
  5. sqil_core/experiment/_events.py +25 -0
  6. sqil_core/experiment/_experiment.py +553 -0
  7. sqil_core/experiment/data/plottr.py +778 -0
  8. sqil_core/experiment/helpers/_function_override_handler.py +111 -0
  9. sqil_core/experiment/helpers/_labone_wrappers.py +12 -0
  10. sqil_core/experiment/instruments/__init__.py +2 -0
  11. sqil_core/experiment/instruments/_instrument.py +190 -0
  12. sqil_core/experiment/instruments/drivers/SignalCore_SC5511A.py +515 -0
  13. sqil_core/experiment/instruments/local_oscillator.py +205 -0
  14. sqil_core/experiment/instruments/server.py +175 -0
  15. sqil_core/experiment/instruments/setup.yaml +21 -0
  16. sqil_core/experiment/instruments/zurich_instruments.py +55 -0
  17. sqil_core/fit/__init__.py +23 -0
  18. sqil_core/fit/_core.py +179 -31
  19. sqil_core/fit/_fit.py +544 -94
  20. sqil_core/fit/_guess.py +304 -0
  21. sqil_core/fit/_models.py +50 -1
  22. sqil_core/fit/_quality.py +266 -0
  23. sqil_core/resonator/__init__.py +2 -0
  24. sqil_core/resonator/_resonator.py +256 -74
  25. sqil_core/utils/__init__.py +40 -13
  26. sqil_core/utils/_analysis.py +226 -0
  27. sqil_core/utils/_const.py +83 -18
  28. sqil_core/utils/_formatter.py +127 -55
  29. sqil_core/utils/_plot.py +272 -6
  30. sqil_core/utils/_read.py +178 -95
  31. sqil_core/utils/_utils.py +147 -0
  32. {sqil_core-0.1.0.dist-info → sqil_core-1.1.0.dist-info}/METADATA +9 -1
  33. sqil_core-1.1.0.dist-info/RECORD +36 -0
  34. {sqil_core-0.1.0.dist-info → sqil_core-1.1.0.dist-info}/WHEEL +1 -1
  35. sqil_core-0.1.0.dist-info/RECORD +0 -19
  36. {sqil_core-0.1.0.dist-info → sqil_core-1.1.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,205 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ import yaml
4
+ from qcodes.instrument_drivers.rohde_schwarz import RohdeSchwarzSGS100A
5
+ from qcodes_contrib_drivers.drivers.SignalCore.SignalCore import SC5521A
6
+
7
+ from sqil_core.config_log import logger
8
+ from sqil_core.experiment.instruments import Instrument
9
+ from sqil_core.utils._formatter import format_number
10
+
11
+ from .drivers.SignalCore_SC5511A import SignalCore_SC5511A
12
+
13
+
14
+ class LocalOscillatorBase(Instrument, ABC):
15
+ def __init__(self, *args, **kwargs):
16
+ super().__init__(*args, **kwargs)
17
+
18
+ @abstractmethod
19
+ def set_frequency(self, value) -> None:
20
+ """Set the frequency of the local oscillator."""
21
+ pass
22
+
23
+ @abstractmethod
24
+ def set_power(self, value) -> None:
25
+ """Set the power of the local oscillator."""
26
+ pass
27
+
28
+ @abstractmethod
29
+ def turn_on(self) -> None:
30
+ """Turn the local oscillator on."""
31
+ pass
32
+
33
+ @abstractmethod
34
+ def turn_off(self) -> None:
35
+ """Turn the local oscillator off."""
36
+ pass
37
+
38
+
39
+ class SqilRohdeSchwarzSGS100A(LocalOscillatorBase):
40
+ """
41
+ Frequency:
42
+ [1 MHz, 20 GHz], resolution 0.001 Hz
43
+ Power:
44
+ [-120 dB, 25 dB], resolution 0.01 dB
45
+ """
46
+
47
+ def _default_connect(self, *args, **kwargs):
48
+ logger.info(f"Connecting to {self.name} ({self.model})")
49
+ return RohdeSchwarzSGS100A(self.name, self.address)
50
+
51
+ def _default_disconnect(self, *args, **kwargs):
52
+ logger.info(f"Disconnecting from {self.name} ({self.model})")
53
+ self.turn_off()
54
+
55
+ def _default_setup(self, *args, **kwargs):
56
+ logger.info(f"Setting up {self.name}")
57
+ self.turn_off()
58
+ self.set_power(-60)
59
+
60
+ def set_frequency(self, value) -> None:
61
+ self.device.frequency(value)
62
+
63
+ def set_power(self, value) -> None:
64
+ self.device.power(value)
65
+
66
+ def turn_on(self) -> None:
67
+ self.device.on()
68
+
69
+ def turn_off(self) -> None:
70
+ self.device.off()
71
+
72
+
73
+ class SqilSignalCoreSC5511A(LocalOscillatorBase):
74
+ """
75
+ PORT 1 specifications
76
+ Frequency:
77
+ [100 MHz, 20 GHz], resolution 1 Hz
78
+ Power:
79
+ @ freq < 18 GHz: [-20 dBm, 15 dBm], resolution 0.01 dBm
80
+ @ freq > 18 GHz: [-20 dBm, 10 dBm], resolution 0.01 dBm
81
+ """
82
+
83
+ def _default_connect(self, *args, **kwargs):
84
+ logger.info(f"Connecting to {self.name} ({self.model})")
85
+ return SignalCore_SC5511A(self.name, self.address)
86
+
87
+ def _default_disconnect(self, *args, **kwargs):
88
+ logger.info(f"Disconnecting from {self.name} ({self.model})")
89
+ self.turn_off()
90
+
91
+ def _default_setup(self, *args, **kwargs):
92
+ logger.info(f"Setting up {self.name}")
93
+ self.turn_off()
94
+ self.set_power(-40)
95
+ self.device.do_set_reference_source(1) # to enable phase locking
96
+ self.device.do_set_standby(True) # update PLL locking
97
+ self.device.do_set_standby(False)
98
+
99
+ def set_frequency(self, value) -> None:
100
+ self.device.do_set_ref_out_freq(value)
101
+
102
+ def set_power(self, value) -> None:
103
+ self.device.power(value)
104
+
105
+ def turn_on(self) -> None:
106
+ self.device.do_set_output_status(1)
107
+
108
+ def turn_off(self) -> None:
109
+ self.device.do_set_output_status(0)
110
+
111
+
112
+ class SqilSignalCoreSC5521A(LocalOscillatorBase):
113
+ """
114
+ Frequency:
115
+ [160 MHz, 40 GHz], resolution 1 Hz
116
+ Power:
117
+ @ freq < 30 GHz: [-10 dBm, 15 dBm], resolution 0.1 dBm
118
+ @ freq < 35 GHz: [-10 dBm, 10 dBm], resolution 0.1 dBm
119
+ @ freq > 35 GHz: [-10 dBm, 3 dBm], resolution 0.1 dBm
120
+ """
121
+
122
+ def _default_connect(self, *args, **kwargs):
123
+ logger.info(f"Connecting to {self.name} ({self.model})")
124
+ return SC5521A(self.name)
125
+
126
+ def _default_disconnect(self, *args, **kwargs):
127
+ logger.info(f"Disconnecting from {self.name} ({self.model})")
128
+ self.turn_off()
129
+
130
+ def _default_setup(self, *args, **kwargs):
131
+ logger.info(f"Setting up {self.name}")
132
+ self.turn_off()
133
+ self.set_power(-40)
134
+
135
+ def set_frequency(self, value) -> None:
136
+ self.device.clock_frequency(value)
137
+
138
+ def set_power(self, value) -> None:
139
+ self.device.power(value)
140
+
141
+ def turn_on(self) -> None:
142
+ self.device.status("on")
143
+
144
+ def turn_off(self) -> None:
145
+ self.device.status("off")
146
+
147
+
148
+ class LocalOscillator(LocalOscillatorBase):
149
+ def __init__(self, *args, **kwargs):
150
+ super().__init__(*args, **kwargs)
151
+
152
+ model = kwargs.get("config", {}).get("model", "")
153
+ if model == "RohdeSchwarzSGS100A":
154
+ self._device_class = SqilRohdeSchwarzSGS100A
155
+ elif model == "SignalCore_SC5511A":
156
+ self._device_class = SqilSignalCoreSC5511A
157
+ elif model == "SC5521A":
158
+ self._device_class = SqilSignalCoreSC5521A
159
+ else:
160
+ raise ValueError(f"Unsupported model: {model}")
161
+
162
+ self.instrument = self._device_class(self.id, self.config)
163
+
164
+ def _default_connect(self, *args, **kwargs):
165
+ pass
166
+
167
+ def _default_disconnect(self, *args, **kwargs):
168
+ pass
169
+
170
+ def _default_setup(self, *args, **kwargs):
171
+ pass
172
+
173
+ def _default_on_before_experiment(self, *args, sender=None, **kwargs):
174
+ self.turn_on()
175
+
176
+ def _default_on_before_sequence(self, *args, sender=None, **kwargs):
177
+ freq = self.get_variable("frequency", sender)
178
+ power = self.get_variable("power", sender)
179
+ if freq:
180
+ self.set_frequency(freq)
181
+ if power:
182
+ self.set_power(power)
183
+
184
+ def _default_on_after_experiment(self, *args, sender=None, **kwargs):
185
+ self.turn_off()
186
+
187
+ def set_frequency(self, value) -> None:
188
+ logger.info(
189
+ f"Setting frequency to {format_number(value, 5, unit="Hz", latex=False)} for {self.name}"
190
+ )
191
+ self.instrument.set_frequency(value)
192
+
193
+ def set_power(self, value) -> None:
194
+ logger.info(
195
+ f"Setting power to {format_number(value, 4, unit="dB", latex=False)} for {self.name}"
196
+ )
197
+ self.instrument.set_power(value)
198
+
199
+ def turn_on(self) -> None:
200
+ logger.info(f"Turning on {self.name}")
201
+ self.instrument.turn_on()
202
+
203
+ def turn_off(self) -> None:
204
+ logger.info(f"Turning off {self.name}")
205
+ self.instrument.turn_off()
@@ -0,0 +1,175 @@
1
+ import os
2
+ import pickle
3
+ import sys
4
+ from typing import cast
5
+
6
+ import Pyro5.api as pyro
7
+ import Pyro5.errors as pyro_errors
8
+
9
+ from sqil_core.config_log import logger
10
+ from sqil_core.experiment.instruments import Instrument
11
+ from sqil_core.experiment.instruments.local_oscillator import LocalOscillator
12
+ from sqil_core.experiment.instruments.zurich_instruments import ZI_Instrument
13
+ from sqil_core.utils._read import read_yaml
14
+ from sqil_core.utils._utils import _extract_variables_from_module, _hash_file
15
+
16
+ _instrument_classes = {
17
+ "LO": LocalOscillator,
18
+ "ZI": ZI_Instrument,
19
+ }
20
+
21
+
22
+ @pyro.expose
23
+ class InstrumentServer:
24
+ """
25
+ Instruments server. Configures the instruments once and distributes instrument instances to other modules.
26
+ Providing the path of the setup file is not required, but it allows to detect changes in the setup file that
27
+ require the server to restart.
28
+ The server is available at PYRO:SERVER@localhost:9090.
29
+ """
30
+
31
+ NAME = "SERVER"
32
+ PORT = 9090
33
+ URI = f"PYRO:{NAME}@localhost:{PORT}"
34
+
35
+ def __init__(self, instrument_dict: dict, setup_path="") -> None:
36
+ logger.info("Starting server")
37
+ self._instruments = connect_instruments(instrument_dict)
38
+ self._daemon = pyro.Daemon(port=InstrumentServer.PORT)
39
+ self._services: dict[str, pyro.URI] = {}
40
+
41
+ self._setup_path = os.path.abspath(setup_path)
42
+ self._setup_hash = None
43
+ if setup_path:
44
+ self._setup_hash = _hash_file(self._setup_path)
45
+ logger.info(f"Setup path: {self._setup_path}")
46
+ logger.info(f"Setup hash: {str(self.setup_hash)}")
47
+
48
+ def serve(self) -> None:
49
+ self._expose()
50
+ for id, instrument in self._instruments.items():
51
+ uri = self._daemon.register(instrument, objectId=instrument.id)
52
+ self._services[id] = uri
53
+ logger.info(f"Registered {instrument = } with daemon at {uri = }.")
54
+ self._daemon.register(self, objectId=InstrumentServer.NAME)
55
+ with self._daemon:
56
+ logger.info("Local server setup complete! Now listening for requests...")
57
+ self._daemon.requestLoop()
58
+
59
+ def _expose(self) -> None:
60
+ classes = {instrument.__class__ for _, instrument in self._instruments.items()}
61
+ # classes |= {Session}
62
+ for cls in classes:
63
+ pyro.expose(cls)
64
+ logger.info(f"Exposed class {cls} to Pyro5.")
65
+
66
+ def shutdown(self) -> None:
67
+ logger.info("Shutting down the local server...")
68
+ self._disconnect_all()
69
+ with self._daemon:
70
+ self._daemon.shutdown()
71
+ logger.info("Local server shutdown complete!")
72
+
73
+ def _disconnect_all(self) -> None:
74
+ logger.info("Disonnecting instruments...")
75
+ for _, instrument in self._instruments.items():
76
+ instrument.disconnect()
77
+
78
+ @property
79
+ def services(self) -> dict[str, pyro.URI]:
80
+ return {**self._services}
81
+
82
+ @property
83
+ def setup_hash(self) -> str | None:
84
+ return self._setup_hash
85
+
86
+ @property
87
+ def setup_path(self) -> str:
88
+ return self._setup_path
89
+
90
+
91
+ def link_instrument_server() -> tuple[pyro.Proxy, dict[str, pyro.Proxy]]:
92
+ """Link to the instruments server."""
93
+ server = pyro.Proxy(InstrumentServer.URI)
94
+
95
+ if server.setup_path:
96
+ current_hash = _hash_file(server.setup_path)
97
+ print(server.setup_hash, current_hash)
98
+ if current_hash != server.setup_hash:
99
+ message = (
100
+ f"Changes detected in the setup file {server.setup_path}. "
101
+ + "Please restart the server to apply the changes."
102
+ )
103
+ logger.error(message)
104
+ raise Exception(message)
105
+
106
+ try:
107
+ services = cast(dict[str, pyro.URI], server.services)
108
+ except pyro_errors.CommunicationError as err:
109
+ logger.error(f"Local server not found at {InstrumentServer.URI}")
110
+ raise err from None
111
+ else:
112
+ instruments = {id: pyro.Proxy(uri) for id, uri in services.items()}
113
+ return (server, instruments)
114
+
115
+
116
+ def unlink_instrument_server(server: pyro.Proxy, **instruments: pyro.Proxy) -> None:
117
+ """Unlink from the instruments server."""
118
+ server._pyroRelease()
119
+ for _, instrument in instruments.items():
120
+ instrument._pyroRelease()
121
+
122
+
123
+ def start_instrument_server(setup_path: str = ""):
124
+ """Start a new instruments server using the provided setup file.
125
+ If the path to the setup file is not provided it will be guessedby readig ./config.yaml.
126
+ """
127
+ if not setup_path:
128
+ config = read_yaml("config.yaml")
129
+ setup_path = config.get("setup_path", "setup.py")
130
+ setup = _extract_variables_from_module("setup", setup_path)
131
+
132
+ instrument_dict = setup.get("instruments", None)
133
+ if not instrument_dict:
134
+ logger.warning(
135
+ f"Unable to find any instruments in {setup_path}"
136
+ + "Do you have an `instruments` entry in your setup file?"
137
+ )
138
+ return None
139
+
140
+ server = InstrumentServer(instrument_dict, setup_path=setup_path)
141
+ server.serve()
142
+
143
+ return server
144
+
145
+
146
+ def connect_instruments(
147
+ instrument_dict: dict | None,
148
+ ) -> dict[str, Instrument]:
149
+ if not instrument_dict:
150
+ return {}
151
+
152
+ instance_dict = {}
153
+ for instrument_id, config in instrument_dict.items():
154
+ instrument_type = config.get("type")
155
+ instrument_factory = _instrument_classes.get(instrument_type)
156
+
157
+ if not instrument_factory:
158
+ logger.warning(
159
+ f"Unknown instrument type '{instrument_type}' for '{instrument_id}'. "
160
+ f"Available types: {list(_instrument_classes.keys())}"
161
+ )
162
+ continue
163
+
164
+ try:
165
+ instance = instrument_factory(instrument_id, config=config)
166
+ instance_dict[instrument_id] = instance
167
+ logger.info(
168
+ f"Successfully connected to {config.get('name', instrument_id)}"
169
+ )
170
+ except Exception as e:
171
+ logger.error(
172
+ f"Failed to connect to {config.get('name', instrument_id)}: {str(e)}"
173
+ )
174
+ sys.exit(-1)
175
+ return instance_dict
@@ -0,0 +1,21 @@
1
+ param_dict_path: >
2
+ ./param_dict.py
3
+
4
+ instruments:
5
+ sgsa:
6
+ name: "SGSA100"
7
+ address: "TCPIP0::128.178.120.72::inst0::INSTR"
8
+ model: "RohdeSchwarzSGS100A"
9
+ type: "LO"
10
+
11
+ SC5511A:
12
+ name: "SC"
13
+ address: "10003C68"
14
+ model: "SignalCore_SC5511A"
15
+ type: "LO"
16
+
17
+ SC5521A:
18
+ name: "mw1"
19
+ address: "" #name based connection, don't need address
20
+ model: "SC5521A"
21
+ type: "LO"
@@ -0,0 +1,55 @@
1
+ from laboneq.dsl.quantum import QPU
2
+ from laboneq.simple import DeviceSetup, Session
3
+
4
+ from sqil_core.experiment.instruments import Instrument
5
+
6
+
7
+ class ZI_Instrument(Instrument):
8
+ _descriptor = ""
9
+ _generate_setup = None
10
+ _generate_qpu = None
11
+
12
+ def __init__(self, id, config):
13
+ super().__init__(id, config)
14
+ self._descriptor = config.get("descriptor", "")
15
+
16
+ self._generate_setup = config.get("generate_setup", None)
17
+ if not self._generate_setup:
18
+ raise NotImplementedError(
19
+ "get_setup is not implemented in your setup file.\n"
20
+ + "You should define it as part of the zi section of your instruments dictionary.\n"
21
+ + "instruments['zi']['generate_setup']"
22
+ )
23
+
24
+ self._generate_qpu = config.get("generate_qpu", None)
25
+ if not self._generate_qpu:
26
+ raise NotImplementedError(
27
+ "get_qpu is not implemented in your setup file.\n"
28
+ + "You should define it as part of the zi section of your instruments dictionary.\n"
29
+ + "instruments['zi']['generate_qpu']"
30
+ )
31
+
32
+ def generate_setup(self, *params, **kwargs) -> DeviceSetup:
33
+ return self._generate_setup(*params, **kwargs)
34
+
35
+ def generate_qpu(self, *params, **kwargs) -> QPU:
36
+ return self._generate_qpu(*params, **kwargs)
37
+
38
+ def _default_connect(self):
39
+ pass
40
+ # setup = self.config.get("setup_obj", None)
41
+ # if setup is not None:
42
+ # self._session = Session(setup)
43
+ # return self.session
44
+ # raise "Zuirch instruments needs a 'setup_obj' field in your setup file"
45
+
46
+ def _default_setup(self):
47
+ pass
48
+
49
+ def _default_disconnect(self):
50
+ pass
51
+
52
+ @property
53
+ def descriptor(self):
54
+ """LaboneQ descriptor (read-only) - deprecated."""
55
+ return self._descriptor
sqil_core/fit/__init__.py CHANGED
@@ -11,6 +11,29 @@ from ._fit import (
11
11
  fit_decaying_oscillations,
12
12
  fit_gaussian,
13
13
  fit_lorentzian,
14
+ fit_many_decaying_oscillations,
15
+ fit_oscillations,
14
16
  fit_qubit_relaxation_qp,
15
17
  fit_skewed_lorentzian,
18
+ fit_two_gaussians_shared_x0,
19
+ fit_two_lorentzians_shared_x0,
20
+ transform_data,
21
+ )
22
+ from ._guess import (
23
+ decaying_oscillations_bounds,
24
+ decaying_oscillations_guess,
25
+ estimate_peak,
26
+ gaussian_bounds,
27
+ gaussian_guess,
28
+ lorentzian_bounds,
29
+ lorentzian_guess,
30
+ oscillations_bounds,
31
+ oscillations_guess,
32
+ )
33
+ from ._quality import (
34
+ FIT_QUALITY_THRESHOLDS,
35
+ FitQuality,
36
+ evaluate_fit_quality,
37
+ get_best_fit,
38
+ get_best_fit_nrmse_aic,
16
39
  )