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,111 @@
1
+ from abc import ABC, abstractmethod
2
+ from contextlib import contextmanager
3
+ from types import MethodType
4
+ from typing import Callable
5
+
6
+
7
+ class FunctionOverrideHandler(ABC):
8
+ """
9
+ A base class that allows functions to be overridden, restored, and temporarily replaced.
10
+
11
+ Attributes
12
+ ----------
13
+ _default_functions : dict
14
+ A dictionary storing the default functions of the object.
15
+ _functions : dict
16
+ A dictionary storing the current functions, which may include overridden versions.
17
+ """
18
+
19
+ def __init__(self):
20
+ """
21
+ Initializes the handler with empty dictionaries for default and overridden functions.
22
+ """
23
+ self._default_functions = {}
24
+ self._functions = {}
25
+
26
+ def override_function(self, func_name, new_func):
27
+ """
28
+ Overrides a function with a new implementation.
29
+
30
+ Parameters
31
+ ----------
32
+ func_name : str
33
+ The name of the function to override.
34
+ new_func : function
35
+ The new function implementation.
36
+ """
37
+ if func_name in self._functions:
38
+ self._functions[func_name] = MethodType(new_func, self)
39
+ else:
40
+ raise AttributeError(f"Function '{func_name}' not found in the object.")
41
+
42
+ def restore_function(self, func_name):
43
+ """
44
+ Restores a function to its default implementation.
45
+
46
+ Parameters
47
+ ----------
48
+ func_name : str
49
+ The name of the function to restore.
50
+ """
51
+ if func_name in self._default_functions:
52
+ self._functions[func_name] = self._default_functions[func_name]
53
+ else:
54
+ raise AttributeError(
55
+ f"Default for function '{func_name}' not found in the object."
56
+ )
57
+
58
+ @contextmanager
59
+ def temporary_override(self, func_name, temp_func):
60
+ """
61
+ Temporarily overrides a function within a context manager.
62
+
63
+ Parameters
64
+ ----------
65
+ func_name : str
66
+ The name of the function to override temporarily.
67
+ temp_func : function
68
+ The temporary function implementation.
69
+
70
+ Yields
71
+ ------
72
+ None
73
+ """
74
+ if func_name not in self._functions:
75
+ raise AttributeError(f"Function '{func_name}' not found in the object.")
76
+
77
+ original_func = self._functions[func_name]
78
+ try:
79
+ self._functions[func_name] = MethodType(temp_func, self)
80
+ yield
81
+ finally:
82
+ self._functions[func_name] = original_func # Restore the original
83
+
84
+ def restore_all_functions(self):
85
+ """
86
+ Restores all overridden functions to their default implementations.
87
+ """
88
+ self._functions = self._default_functions.copy()
89
+
90
+ def call(self, func_name, *args, **kwargs):
91
+ """
92
+ Calls a function by its name, passing any provided arguments.
93
+
94
+ Parameters
95
+ ----------
96
+ func_name : str
97
+ The name of the function to call.
98
+ *args : tuple
99
+ Positional arguments to pass to the function.
100
+ **kwargs : dict
101
+ Keyword arguments to pass to the function.
102
+
103
+ Returns
104
+ -------
105
+ Any
106
+ The return value of the called function.
107
+ """
108
+ if func_name in self._functions:
109
+ return self._functions[func_name](*args, **kwargs)
110
+ else:
111
+ raise AttributeError(f"Function '{func_name}' not found in Instrument.")
@@ -0,0 +1,12 @@
1
+ import json
2
+
3
+ from laboneq import serializers
4
+
5
+
6
+ def w_save(obj, path):
7
+ serializers.save(obj, path)
8
+ # Re-open the file, read the content, and re-save it pretty-printed
9
+ with open(path, "r", encoding="utf-8") as f:
10
+ data = json.load(f)
11
+ with open(path, "w", encoding="utf-8") as f:
12
+ json.dump(data, f, indent=4, ensure_ascii=False)
@@ -0,0 +1,2 @@
1
+ from ._instrument import Instrument
2
+ from .local_oscillator import LocalOscillator
@@ -0,0 +1,190 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ import Pyro5.server
4
+ from blinker import NamedSignal
5
+
6
+ from sqil_core.config_log import logger
7
+ from sqil_core.experiment._events import (
8
+ after_experiment,
9
+ after_sequence,
10
+ before_experiment,
11
+ before_sequence,
12
+ one_time_listener,
13
+ )
14
+ from sqil_core.experiment.helpers._function_override_handler import (
15
+ FunctionOverrideHandler,
16
+ )
17
+
18
+
19
+ @Pyro5.server.expose
20
+ class Instrument(FunctionOverrideHandler, ABC):
21
+ """
22
+ Base class for instruments with configurable behavior.
23
+
24
+ Supports overriding `connect`, `setup`, and `disconnect` methods
25
+ via a configuration dictionary.
26
+ """
27
+
28
+ def __init__(self, id: str, config: dict):
29
+ """
30
+ Initializes the instrument with an ID and configuration.
31
+
32
+ If `connect`, `setup`, or `disconnect` are provided in `config`,
33
+ they override the default implementations.
34
+ """
35
+ super().__init__()
36
+
37
+ self._id = id
38
+ self._type = config.get("type", "")
39
+ self._model = config.get("model", "")
40
+ self._name = config.get("name", "")
41
+ self._address = config.get("address", "")
42
+ self._variables = config.get("variables", {})
43
+ self._config = config
44
+ self._device = None
45
+
46
+ self._default_functions = {
47
+ "connect": self._default_connect,
48
+ "setup": self._default_setup,
49
+ "disconnect": self._default_disconnect,
50
+ "on_before_experiment": self._default_on_before_experiment,
51
+ "on_after_experiment": self._default_on_after_experiment,
52
+ "on_before_sequence": self._default_on_before_sequence,
53
+ "on_after_sequence": self._default_on_after_sequence,
54
+ }
55
+ self._functions = self._default_functions.copy()
56
+
57
+ # Override functions if provided in config
58
+ for method_name in self._default_functions:
59
+ if method := config.get(method_name):
60
+ self.override_function(method_name, method)
61
+
62
+ self._default_functions = self._functions.copy()
63
+ self._device = self.connect() # Auto-connect on instantiation
64
+
65
+ # Subscribe to events
66
+ self._subscribe_to_events()
67
+
68
+ def _subscribe_to_events(self):
69
+ before_experiment.connect(
70
+ lambda *a, **kw: self.call("on_before_experiment", *a, **kw), weak=False
71
+ )
72
+ before_sequence.connect(
73
+ lambda *a, **kw: self.call("on_before_sequence", *a, **kw), weak=False
74
+ )
75
+ after_sequence.connect(
76
+ lambda *a, **kw: self.call("on_after_sequence", *a, **kw), weak=False
77
+ )
78
+ after_experiment.connect(
79
+ lambda *a, **kw: self.call("on_after_experiment", *a, **kw), weak=False
80
+ )
81
+
82
+ def connect(self, *args, **kwargs):
83
+ """Calls the overridden or default `connect` method."""
84
+ return self.call("connect", *args, **kwargs)
85
+
86
+ @abstractmethod
87
+ def _default_connect(self, *args, **kwargs):
88
+ """Default `connect` implementation (must be overridden)."""
89
+ pass
90
+
91
+ def setup(self, *args, **kwargs):
92
+ """Calls the overridden or default `setup` method."""
93
+ return self.call("setup", *args, **kwargs)
94
+
95
+ @abstractmethod
96
+ def _default_setup(self, *args, **kwargs):
97
+ """Default `setup` implementation (must be overridden)."""
98
+ pass
99
+
100
+ def disconnect(self, *args, **kwargs):
101
+ """Calls the overridden or default `disconnect` method."""
102
+ return self.call("disconnect", *args, **kwargs)
103
+
104
+ @abstractmethod
105
+ def _default_disconnect(self, *args, **kwargs):
106
+ pass
107
+
108
+ def on_before_experiment(self, *args, **kwargs):
109
+ """Calls the overridden or default `on_before_experiment` method."""
110
+ return self.call("on_before_experiment", *args, **kwargs)
111
+
112
+ def _default_on_before_experiment(self, *args, **kwargs):
113
+ pass
114
+
115
+ def on_before_sequence(self, *args, **kwargs):
116
+ """Calls the overridden or default `on_before_sequence` method."""
117
+ return self.call("on_before_sequence", *args, **kwargs)
118
+
119
+ def _default_on_before_sequence(self, *args, **kwargs):
120
+ pass
121
+
122
+ def on_after_experiment(self, *args, **kwargs):
123
+ """Calls the overridden or default `on_after_experiment` method."""
124
+ return self.call("on_after_experiment", *args, **kwargs)
125
+
126
+ def _default_on_after_experiment(self, *args, **kwargs):
127
+ pass
128
+
129
+ def on_after_sequence(self, *args, **kwargs):
130
+ """Calls the overridden or default `on_after_sequence` method."""
131
+ return self.call("on_after_sequence", *args, **kwargs)
132
+
133
+ def _default_on_after_sequence(self, *args, **kwargs):
134
+ pass
135
+
136
+ def __getattr__(self, name):
137
+ """
138
+ Dynamically expose all attributes to Pyro server.
139
+ """
140
+ if name in self.__dict__:
141
+ return self.__dict__[name]
142
+ raise AttributeError(
143
+ f"'{self.__class__.__name__}' object has no attribute '{name}'"
144
+ )
145
+
146
+ def get_variable(self, key, *args, **kwargs):
147
+ var = self._variables.get(key, None)
148
+ if callable(var):
149
+ var = var(*args, **kwargs)
150
+ return var
151
+
152
+ @property
153
+ def id(self):
154
+ """Instrument ID (read-only)."""
155
+ return self._id
156
+
157
+ @property
158
+ def type(self):
159
+ """Instrument type (read-only)."""
160
+ return self._type
161
+
162
+ @property
163
+ def model(self):
164
+ """Instrument model (read-only)."""
165
+ return self._model
166
+
167
+ @property
168
+ def name(self):
169
+ """Instrument name (read-only)."""
170
+ return self._name
171
+
172
+ @property
173
+ def address(self):
174
+ """Instrument address (read-only)."""
175
+ return self._address
176
+
177
+ @property
178
+ def variables(self):
179
+ """Instrument variables (read-only)."""
180
+ return self._variables
181
+
182
+ @property
183
+ def config(self):
184
+ """Instrument configuration dictionary (read-only)."""
185
+ return self._config
186
+
187
+ @property
188
+ def device(self):
189
+ """Raw instrument instance (read-only)."""
190
+ return self._device