pymodaq 5.0.5__py3-none-any.whl → 5.1.0a0__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.

Potentially problematic release.


This version of pymodaq might be problematic. Click here for more details.

Files changed (53) hide show
  1. pymodaq/control_modules/daq_move.py +77 -64
  2. pymodaq/control_modules/daq_move_ui.py +16 -15
  3. pymodaq/control_modules/daq_viewer.py +95 -87
  4. pymodaq/control_modules/daq_viewer_ui.py +22 -23
  5. pymodaq/control_modules/mocks.py +2 -2
  6. pymodaq/control_modules/move_utility_classes.py +28 -19
  7. pymodaq/control_modules/thread_commands.py +138 -0
  8. pymodaq/control_modules/utils.py +88 -20
  9. pymodaq/control_modules/viewer_utility_classes.py +8 -17
  10. pymodaq/dashboard.py +90 -27
  11. pymodaq/examples/qt_less_standalone_module.py +48 -11
  12. pymodaq/extensions/__init__.py +7 -3
  13. pymodaq/extensions/adaptive/__init__.py +2 -0
  14. pymodaq/extensions/adaptive/adaptive_optimization.py +159 -0
  15. pymodaq/extensions/adaptive/loss_function/_1d_loss_functions.py +73 -0
  16. pymodaq/extensions/adaptive/loss_function/_2d_loss_functions.py +86 -0
  17. pymodaq/extensions/adaptive/loss_function/__init__.py +3 -0
  18. pymodaq/extensions/adaptive/loss_function/loss_factory.py +106 -0
  19. pymodaq/extensions/adaptive/utils.py +97 -0
  20. pymodaq/extensions/bayesian/__init__.py +1 -1
  21. pymodaq/extensions/bayesian/acquisition/__init__.py +2 -0
  22. pymodaq/extensions/bayesian/acquisition/acquisition_function_factory.py +71 -0
  23. pymodaq/extensions/bayesian/acquisition/base_acquisition_function.py +86 -0
  24. pymodaq/extensions/bayesian/bayesian_optimization.py +121 -0
  25. pymodaq/extensions/bayesian/utils.py +27 -286
  26. pymodaq/extensions/daq_logger/daq_logger.py +7 -12
  27. pymodaq/extensions/daq_logger/h5logging.py +1 -1
  28. pymodaq/extensions/daq_scan.py +18 -47
  29. pymodaq/extensions/h5browser.py +3 -34
  30. pymodaq/extensions/optimizers_base/__init__.py +0 -0
  31. pymodaq/extensions/{bayesian/bayesian_optimisation.py → optimizers_base/optimizer.py} +441 -334
  32. pymodaq/extensions/optimizers_base/thread_commands.py +20 -0
  33. pymodaq/extensions/optimizers_base/utils.py +378 -0
  34. pymodaq/extensions/pid/pid_controller.py +6 -10
  35. pymodaq/extensions/utils.py +12 -0
  36. pymodaq/utils/data.py +1 -0
  37. pymodaq/utils/gui_utils/loader_utils.py +2 -0
  38. pymodaq/utils/h5modules/module_saving.py +134 -22
  39. pymodaq/utils/leco/daq_move_LECODirector.py +73 -73
  40. pymodaq/utils/leco/daq_xDviewer_LECODirector.py +36 -84
  41. pymodaq/utils/leco/director_utils.py +25 -10
  42. pymodaq/utils/leco/leco_director.py +65 -26
  43. pymodaq/utils/leco/pymodaq_listener.py +118 -68
  44. pymodaq/utils/leco/utils.py +24 -24
  45. pymodaq/utils/managers/modules_manager.py +37 -8
  46. pymodaq/utils/scanner/scanners/_1d_scanners.py +0 -38
  47. pymodaq/utils/scanner/scanners/_2d_scanners.py +0 -58
  48. {pymodaq-5.0.5.dist-info → pymodaq-5.1.0a0.dist-info}/METADATA +4 -3
  49. {pymodaq-5.0.5.dist-info → pymodaq-5.1.0a0.dist-info}/RECORD +52 -38
  50. {pymodaq-5.0.5.dist-info → pymodaq-5.1.0a0.dist-info}/entry_points.txt +0 -2
  51. pymodaq/utils/leco/desktop.ini +0 -2
  52. {pymodaq-5.0.5.dist-info → pymodaq-5.1.0a0.dist-info}/WHEEL +0 -0
  53. {pymodaq-5.0.5.dist-info → pymodaq-5.1.0a0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,20 @@
1
+ from pymodaq_utils.enums import StrEnum
2
+
3
+
4
+ class OptimizerToRunner(StrEnum):
5
+ """ Allowed Generic commands sent from an Optimizer to its thread running class
6
+
7
+ """
8
+ START = 'start'
9
+ RUN = 'run'
10
+ STOP = 'stop'
11
+ STOPPING = 'stopping'
12
+ BOUNDS = 'bounds'
13
+
14
+ PREDICTION = 'prediction'
15
+
16
+
17
+ class OptimizerThreadStatus(StrEnum):
18
+
19
+ ADD_DATA = "add_data"
20
+
@@ -0,0 +1,378 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created the 31/08/2023
4
+
5
+ @author: Sebastien Weber
6
+ """
7
+ import abc
8
+ from abc import ABC
9
+ from typing import List, TYPE_CHECKING, Union, Dict, Tuple, Iterable
10
+ from pathlib import Path
11
+ import importlib
12
+ import pkgutil
13
+ import inspect
14
+ import numpy as np
15
+ from collections import namedtuple
16
+
17
+ from pymodaq_utils.abstract import abstract_attribute
18
+ from pymodaq_utils.utils import find_dict_in_list_from_key_val, get_entrypoints
19
+ from pymodaq_utils.logger import set_logger, get_module_name
20
+ from pymodaq_utils.enums import BaseEnum
21
+ from pymodaq_utils.config import BaseConfig
22
+
23
+ from pymodaq_gui.plotting.data_viewers.viewer import ViewersEnum
24
+ from pymodaq_gui.managers.parameter_manager import Parameter
25
+
26
+
27
+ from pymodaq_data.data import (DataToExport, DataCalculated,
28
+ DataRaw, Axis)
29
+
30
+ from pymodaq.utils.data import DataActuator, DataToActuators
31
+ from pymodaq.utils.managers.modules_manager import ModulesManager
32
+
33
+
34
+ logger = set_logger(get_module_name(__file__))
35
+
36
+
37
+ class StopType(BaseEnum):
38
+ Predict = 0
39
+
40
+
41
+ StoppingParameters = namedtuple('StoppingParameters',
42
+ ['niter', 'stop_type', 'tolerance', 'npoints'])
43
+
44
+
45
+ class GenericAlgorithm(abc.ABC):
46
+
47
+ def __init__(self, ini_random: int):
48
+
49
+ self._algo = abstract_attribute() #could be a Bayesian on Adapative algorithm
50
+ self._prediction = abstract_attribute() # could be an acquisition function...
51
+
52
+ self._next_point: np.ndarray = None
53
+ self._suggested_coordinates: List[np.ndarray] = []
54
+ self.ini_random_points = ini_random
55
+
56
+ @abc.abstractmethod
57
+ def set_prediction_function(self, kind: str='', **kwargs):
58
+ """ Set/Load a given function/class to predict next probed points"""
59
+
60
+ @abc.abstractmethod
61
+ def update_prediction_function(self):
62
+ """ Update the parameters of the prediction function (kappa decay for instance)"""
63
+
64
+ def set_acquisition_function(self, kind: str, **kwargs):
65
+ """ Deprecated"""
66
+ self.set_prediction_function(kind, **kwargs)
67
+
68
+ def update_acquisition_function(self):
69
+ """ deprecated"""
70
+ self.update_prediction_function()
71
+
72
+ @property
73
+ def _acquisition(self):
74
+ """ deprecated """
75
+ return self._prediction
76
+
77
+ @property
78
+ def tradeoff(self):
79
+ return self._prediction.tradeoff
80
+
81
+ @property
82
+ @abc.abstractmethod
83
+ def bounds(self) -> List[np.ndarray]:
84
+ pass
85
+
86
+ @bounds.setter
87
+ def bounds(self, bounds: Union[Dict[str, Tuple[float, float]], Iterable[np.ndarray]]):
88
+ if isinstance(bounds, dict):
89
+ self._algo.set_bounds(bounds)
90
+ else:
91
+ self._algo.set_bounds(self._algo.space.array_to_params(np.array(bounds)))
92
+
93
+ def get_random_point(self) -> np.ndarray:
94
+ """ Get a random point coordinates in the defined bounds"""
95
+ point = []
96
+ for bound in self.bounds:
97
+ point.append((np.max(bound) - np.min(bound)) * np.random.random_sample() +
98
+ np.min(bound))
99
+ return np.array(point)
100
+
101
+ def ask(self) -> list[np.ndarray]:
102
+ """ Predict next actuator values to probe
103
+
104
+ Return a list of numpy array, one per actuator. In general these array are 0D
105
+ """
106
+ try:
107
+ self._next_point = self.prediction_ask()
108
+ except:
109
+ self.ini_random_points -= 1
110
+ self._next_point = self.get_random_point()
111
+ self._suggested_coordinates.append(self._next_point)
112
+ return [np.atleast_1d(value) for value in self._next_point]
113
+
114
+ @abc.abstractmethod
115
+ def prediction_ask(self) -> np.ndarray:
116
+ """ Ask the prediction function or algo to provide the next point to probe"""
117
+
118
+ @abc.abstractmethod
119
+ def tell(self, function_value: float):
120
+ """ Add next points and function value into the algo"""
121
+
122
+ @property
123
+ @abc.abstractmethod
124
+ def best_fitness(self) -> float:
125
+ pass
126
+
127
+ @property
128
+ @abc.abstractmethod
129
+ def best_individual(self) -> Union[np.ndarray, None]:
130
+ pass
131
+
132
+ @abc.abstractmethod
133
+ def stopping(self, ind_iter: int, stopping_parameters: StoppingParameters) -> bool:
134
+ pass
135
+
136
+
137
+ class OptimizerModelGeneric(ABC):
138
+
139
+ optimization_algorithm: GenericAlgorithm = None
140
+
141
+ actuators_name: List[str] = []
142
+ detectors_name: List[str] = []
143
+
144
+ observables_dim: List[ViewersEnum] = []
145
+
146
+ params = [] # to be subclassed
147
+
148
+ def __init__(self, optimization_controller):
149
+ self.optimization_controller = optimization_controller # instance of the pid_controller using this model
150
+ self.modules_manager: ModulesManager = optimization_controller.modules_manager
151
+
152
+ self.settings = self.optimization_controller.settings.child('models', 'model_params') # set of parameters
153
+ self.check_modules(self.modules_manager)
154
+
155
+ def check_modules(self, modules_manager):
156
+ for act in self.actuators_name:
157
+ if act not in modules_manager.actuators_name:
158
+ logger.warning(f'The actuator {act} defined in the model is'
159
+ f' not present in the Dashboard')
160
+ return False
161
+ for det in self.detectors_name:
162
+ if det not in modules_manager.detectors_name:
163
+ logger.warning(f'The detector {det} defined in the model is'
164
+ f' not present in the Dashboard')
165
+
166
+ def update_detector_names(self):
167
+ names = self.optimization_controller.settings.child(
168
+ 'main_settings', 'detector_modules').value()['selected']
169
+ self.data_names = []
170
+ for name in names:
171
+ name = name.split('//')
172
+ self.data_names.append(name)
173
+
174
+ def update_settings(self, param: Parameter):
175
+ """
176
+ Get a parameter instance whose value has been modified by a user on the UI
177
+ To be overwritten in child class
178
+ """
179
+ ...
180
+
181
+ def update_plots(self):
182
+ """ Called when updating the live plots """
183
+ pass
184
+
185
+ def ini_model_base(self):
186
+ self.modules_manager.selected_actuators_name = self.actuators_name
187
+ self.modules_manager.selected_detectors_name = self.detectors_name
188
+
189
+ self.ini_model()
190
+
191
+ def ini_model(self):
192
+ """ To be subclassed
193
+
194
+ Initialize whatever is needed by your custom model
195
+ """
196
+ raise NotImplementedError
197
+
198
+ def runner_initialized(self):
199
+ """ To be subclassed
200
+
201
+ Initialize whatever is needed by your custom model after the optimization runner is
202
+ initialized
203
+ """
204
+ pass
205
+
206
+ def convert_input(self, measurements: DataToExport) -> float:
207
+ """
208
+ Convert the measurements in the units to be fed to the Optimisation Controller
209
+ Parameters
210
+ ----------
211
+ measurements: DataToExport
212
+ data object exported from the detectors from which the model extract a float value
213
+ (fitness) to be fed to the algorithm
214
+
215
+ Returns
216
+ -------
217
+ float
218
+
219
+ """
220
+ raise NotImplementedError
221
+
222
+ def convert_output(self, outputs: List[np.ndarray], best_individual=None) -> DataToActuators:
223
+ """ Convert the output of the Optimisation Controller in units to be fed into the actuators
224
+ Parameters
225
+ ----------
226
+ outputs: list of numpy ndarray
227
+ output value from the controller from which the model extract a value of the same units as the actuators
228
+ best_individual: np.ndarray
229
+ the coordinates of the best individual so far
230
+ Returns
231
+ -------
232
+ DataToActuatorOpti: derived from DataToExport. Contains value to be fed to the actuators with a a mode
233
+ attribute, either 'rel' for relative or 'abs' for absolute.
234
+
235
+ """
236
+ raise NotImplementedError
237
+
238
+
239
+ class OptimizerModelDefault(OptimizerModelGeneric):
240
+
241
+ actuators_name: List[str] = [] # to be populated dynamically at instantiation
242
+ detectors_name: List[str] = [] # to be populated dynamically at instantiation
243
+
244
+ params = [{'title': 'Optimizing signal', 'name': 'optimizing_signal', 'type': 'group',
245
+ 'children': [
246
+ {'title': 'Get data', 'name': 'data_probe', 'type': 'action'},
247
+ {'title': 'Optimize 0Ds:', 'name': 'optimize_0d', 'type': 'itemselect',
248
+ 'checkbox': True},
249
+ ]},]
250
+
251
+ def __init__(self, optimization_controller):
252
+ self.actuators_name = optimization_controller.modules_manager.actuators_name
253
+ self.detectors_name = optimization_controller.modules_manager.detectors_name
254
+ super().__init__(optimization_controller)
255
+
256
+ self.settings.child('optimizing_signal', 'data_probe').sigActivated.connect(
257
+ self.optimize_from)
258
+
259
+ def ini_model(self):
260
+ pass
261
+
262
+ def optimize_from(self):
263
+ self.modules_manager.get_det_data_list()
264
+ data0D = self.modules_manager.settings['data_dimensions', 'det_data_list0D']
265
+ data0D['selected'] = data0D['all_items']
266
+ self.settings.child('optimizing_signal', 'optimize_0d').setValue(data0D)
267
+
268
+ def update_settings(self, param: Parameter):
269
+ pass
270
+
271
+ def convert_input(self, measurements: DataToExport) -> float:
272
+ """ Convert the measurements in the units to be fed to the Optimisation Controller
273
+
274
+ Parameters
275
+ ----------
276
+ measurements: DataToExport
277
+ data object exported from the detectors from which the model extract a float value
278
+ (fitness) to be fed to the algorithm
279
+
280
+ Returns
281
+ -------
282
+ float
283
+
284
+ """
285
+ data_name: str = self.settings['optimizing_signal', 'optimize_0d']['selected'][0]
286
+ origin, name = data_name.split('/')
287
+ return float(measurements.get_data_from_name_origin(name, origin).data[0][0])
288
+
289
+ def convert_output(self, outputs: List[np.ndarray], best_individual=None) -> DataToActuators:
290
+ """ Convert the output of the Optimisation Controller in units to be fed into the actuators
291
+ Parameters
292
+ ----------
293
+ outputs: list of numpy ndarray
294
+ output value from the controller from which the model extract a value of the same units as the actuators
295
+ best_individual: np.ndarray
296
+ the coordinates of the best individual so far
297
+
298
+ Returns
299
+ -------
300
+ DataToActuators: derived from DataToExport. Contains value to be fed to the actuators
301
+ with a mode attribute, either 'rel' for relative or 'abs' for absolute.
302
+
303
+ """
304
+ return DataToActuators(
305
+ 'outputs', mode='abs',
306
+ data=[DataActuator(self.modules_manager.actuators_name[ind],
307
+ data=float(outputs[ind][0])) for ind in range(len(outputs))])
308
+
309
+
310
+
311
+
312
+ def get_optimizer_models(model_name=None):
313
+ """
314
+ Get Optimizer Models as a list to instantiate Control Actuators per degree of liberty in the model
315
+
316
+ Returns
317
+ -------
318
+ list: list of disct containting the name and python module of the found models
319
+ """
320
+ models_import = []
321
+ discovered_models = get_entrypoints(group='pymodaq.models')
322
+ if len(discovered_models) > 0:
323
+ for pkg in discovered_models:
324
+ try:
325
+ module = importlib.import_module(pkg.value)
326
+ module_name = pkg.value
327
+
328
+ for mod in pkgutil.iter_modules([
329
+ str(Path(module.__file__).parent.joinpath('models'))]):
330
+ try:
331
+ model_module = importlib.import_module(f'{module_name}.models.{mod.name}',
332
+ module)
333
+ classes = inspect.getmembers(model_module, inspect.isclass)
334
+ for name, klass in classes:
335
+ if issubclass(klass, OptimizerModelGeneric):
336
+ if find_dict_in_list_from_key_val(models_import, 'name', mod.name)\
337
+ is None:
338
+ models_import.append({'name': klass.__name__,
339
+ 'module': model_module,
340
+ 'class': klass})
341
+
342
+ except Exception as e:
343
+ logger.warning(str(e))
344
+
345
+ except Exception as e:
346
+ logger.warning(f'Impossible to import the {pkg.value} optimizer model: {str(e)}')
347
+
348
+ if find_dict_in_list_from_key_val(models_import, 'name', 'OptimizerModelDefault') \
349
+ is None:
350
+ models_import.append({'name': 'OptimizerModelDefault',
351
+ 'module': inspect.getmodule(OptimizerModelDefault),
352
+ 'class': OptimizerModelDefault})
353
+ if model_name is None:
354
+ return models_import
355
+ else:
356
+ return find_dict_in_list_from_key_val(models_import, 'name', model_name)
357
+
358
+
359
+ class OptimizerConfig(BaseConfig):
360
+ """Main class to deal with configuration values for this plugin
361
+
362
+ To b subclassed for real implementation if needed, see Optimizer class attribute config_saver
363
+ """
364
+ config_template_path = None
365
+ config_name = f"optimizer_settings"
366
+
367
+
368
+ def find_key_in_nested_dict(dic, key):
369
+ stack = [dic]
370
+ while stack:
371
+ d = stack.pop()
372
+ if key in d:
373
+ return d[key]
374
+ for v in d.values():
375
+ if isinstance(v, dict):
376
+ stack.append(v)
377
+ if isinstance(v, list):
378
+ stack += v
@@ -126,7 +126,7 @@ class DAQ_PID(CustomExt):
126
126
  if self.settings['main_settings', 'pid_controls', 'output_limits', 'output_limit_max_enabled']:
127
127
  output_limits[1] = self.settings['main_settings', 'pid_controls', 'output_limits', 'output_limit_max']
128
128
 
129
- self.PIDThread = QThread()
129
+ self.runner_thread = QThread()
130
130
  pid_runner = PIDRunner(self.model_class, self.modules_manager, setpoints=self.setpoints,
131
131
  params=dict(Kp=self.settings['main_settings', 'pid_controls', 'pid_constants',
132
132
  'kp'],
@@ -140,24 +140,20 @@ class DAQ_PID(CustomExt):
140
140
  auto_mode=False),
141
141
  )
142
142
 
143
- self.PIDThread.pid_runner = pid_runner
143
+ self.runner_thread.pid_runner = pid_runner
144
144
  pid_runner.pid_output_signal.connect(self.process_output)
145
145
  pid_runner.status_sig.connect(self.thread_status)
146
146
  self.command_pid.connect(pid_runner.queue_command)
147
147
 
148
- pid_runner.moveToThread(self.PIDThread)
148
+ pid_runner.moveToThread(self.runner_thread)
149
149
 
150
- self.PIDThread.start()
150
+ self.runner_thread.start()
151
151
  self.get_action('pid_led').set_as_true()
152
152
  self.enable_controls_pid_run(True)
153
153
 
154
154
  else:
155
155
  if hasattr(self, 'PIDThread'):
156
- if self.PIDThread.isRunning():
157
- try:
158
- self.PIDThread.quit()
159
- except Exception:
160
- pass
156
+ self.exit_runner_thread()
161
157
  self.get_action('pid_led').set_as_false()
162
158
  self.enable_controls_pid_run(False)
163
159
 
@@ -462,7 +458,7 @@ class DAQ_PID(CustomExt):
462
458
  """
463
459
  try:
464
460
  try:
465
- self.PIDThread.exit()
461
+ self.runner_thread.exit()
466
462
  except Exception as e:
467
463
  print(e)
468
464
 
@@ -78,6 +78,11 @@ class CustomExt(CustomApp):
78
78
  super().__init__(parent)
79
79
 
80
80
  self.dashboard = dashboard
81
+ self.runner_thread : QtCore.QThread = None
82
+
83
+ @property
84
+ def splash(self):
85
+ return self.dashboard.splash_sc
81
86
 
82
87
  @property
83
88
  def modules_manager(self) -> ModulesManager:
@@ -91,3 +96,10 @@ class CustomExt(CustomApp):
91
96
  """
92
97
  if self.dashboard is not None:
93
98
  return self.dashboard.modules_manager
99
+
100
+ def exit_runner_thread(self, duration : int = 5000):
101
+ self.runner_thread.quit()
102
+ terminated = self.runner_thread.wait(duration)
103
+ if not terminated:
104
+ self.runner_thread.terminate()
105
+ self.runner_thread.wait()
pymodaq/utils/data.py CHANGED
@@ -114,6 +114,7 @@ class DataToActuators(DataToExport):
114
114
  mode: str
115
115
  either 'rel' or 'abs' for a relative or absolute change of the actuator's values
116
116
  """
117
+ mode: str
117
118
 
118
119
  def __init__(self, *args, mode='rel', **kwargs):
119
120
  if mode not in ['rel', 'abs']:
@@ -53,6 +53,8 @@ def load_dashboard_with_preset(preset_name: str, extension_name: str) -> \
53
53
  extension = dashboard.load_pid_module()
54
54
  elif extension_name == 'Bayesian':
55
55
  extension = dashboard.load_bayesian()
56
+ elif extension_name == 'AdaptiveScan':
57
+ extension = dashboard.load_adaptive()
56
58
  else:
57
59
  extension = dashboard.load_extension_from_name(extension_name)
58
60
  else: