pymodaq 5.0.17__py3-none-any.whl → 5.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.
Potentially problematic release.
This version of pymodaq might be problematic. Click here for more details.
- pymodaq/__init__.py +23 -11
- pymodaq/control_modules/__init__.py +1 -0
- pymodaq/control_modules/daq_move.py +458 -246
- pymodaq/control_modules/daq_move_ui/__init__.py +0 -0
- pymodaq/control_modules/daq_move_ui/factory.py +48 -0
- pymodaq/control_modules/{daq_move_ui.py → daq_move_ui/ui_base.py} +168 -210
- pymodaq/control_modules/daq_move_ui/uis/__init__.py +0 -0
- pymodaq/control_modules/daq_move_ui/uis/binary.py +139 -0
- pymodaq/control_modules/daq_move_ui/uis/original.py +120 -0
- pymodaq/control_modules/daq_move_ui/uis/relative.py +124 -0
- pymodaq/control_modules/daq_move_ui/uis/simple.py +126 -0
- pymodaq/control_modules/daq_viewer.py +113 -101
- pymodaq/control_modules/daq_viewer_ui.py +41 -31
- pymodaq/control_modules/mocks.py +2 -2
- pymodaq/control_modules/move_utility_classes.py +113 -41
- pymodaq/control_modules/thread_commands.py +137 -0
- pymodaq/control_modules/ui_utils.py +72 -0
- pymodaq/control_modules/utils.py +107 -63
- pymodaq/control_modules/viewer_utility_classes.py +13 -17
- pymodaq/dashboard.py +1294 -625
- pymodaq/examples/qt_less_standalone_module.py +48 -11
- pymodaq/extensions/__init__.py +8 -3
- pymodaq/extensions/adaptive/__init__.py +2 -0
- pymodaq/extensions/adaptive/adaptive_optimization.py +179 -0
- pymodaq/extensions/adaptive/loss_function/_1d_loss_functions.py +73 -0
- pymodaq/extensions/adaptive/loss_function/_2d_loss_functions.py +73 -0
- pymodaq/extensions/adaptive/loss_function/__init__.py +3 -0
- pymodaq/extensions/adaptive/loss_function/loss_factory.py +110 -0
- pymodaq/extensions/adaptive/utils.py +123 -0
- pymodaq/extensions/bayesian/__init__.py +1 -1
- pymodaq/extensions/bayesian/acquisition/__init__.py +2 -0
- pymodaq/extensions/bayesian/acquisition/acquisition_function_factory.py +80 -0
- pymodaq/extensions/bayesian/acquisition/base_acquisition_function.py +105 -0
- pymodaq/extensions/bayesian/bayesian_optimization.py +143 -0
- pymodaq/extensions/bayesian/utils.py +71 -297
- pymodaq/extensions/daq_logger/daq_logger.py +7 -12
- pymodaq/extensions/daq_logger/h5logging.py +1 -1
- pymodaq/extensions/daq_scan.py +30 -55
- pymodaq/extensions/data_mixer/__init__.py +0 -0
- pymodaq/extensions/data_mixer/daq_0Dviewer_DataMixer.py +97 -0
- pymodaq/extensions/data_mixer/data_mixer.py +262 -0
- pymodaq/extensions/data_mixer/model.py +108 -0
- pymodaq/extensions/data_mixer/models/__init__.py +0 -0
- pymodaq/extensions/data_mixer/models/equation_model.py +91 -0
- pymodaq/extensions/data_mixer/models/gaussian_fit_model.py +65 -0
- pymodaq/extensions/data_mixer/parser.py +53 -0
- pymodaq/extensions/data_mixer/utils.py +23 -0
- pymodaq/extensions/h5browser.py +3 -34
- pymodaq/extensions/optimizers_base/__init__.py +0 -0
- pymodaq/extensions/optimizers_base/optimizer.py +1016 -0
- pymodaq/extensions/optimizers_base/thread_commands.py +22 -0
- pymodaq/extensions/optimizers_base/utils.py +427 -0
- pymodaq/extensions/pid/actuator_controller.py +3 -2
- pymodaq/extensions/pid/daq_move_PID.py +107 -30
- pymodaq/extensions/pid/pid_controller.py +613 -287
- pymodaq/extensions/pid/utils.py +8 -5
- pymodaq/extensions/utils.py +17 -2
- pymodaq/resources/config_template.toml +57 -0
- pymodaq/resources/preset_default.xml +1 -1
- pymodaq/utils/config.py +13 -4
- pymodaq/utils/daq_utils.py +14 -0
- pymodaq/utils/data.py +1 -0
- pymodaq/utils/gui_utils/loader_utils.py +25 -15
- pymodaq/utils/h5modules/module_saving.py +134 -22
- pymodaq/utils/leco/daq_move_LECODirector.py +123 -84
- pymodaq/utils/leco/daq_xDviewer_LECODirector.py +84 -97
- pymodaq/utils/leco/director_utils.py +32 -16
- pymodaq/utils/leco/leco_director.py +104 -27
- pymodaq/utils/leco/pymodaq_listener.py +186 -97
- pymodaq/utils/leco/rpc_method_definitions.py +43 -0
- pymodaq/utils/leco/utils.py +25 -25
- pymodaq/utils/managers/batchscan_manager.py +12 -11
- pymodaq/utils/managers/modules_manager.py +74 -33
- pymodaq/utils/managers/overshoot_manager.py +11 -10
- pymodaq/utils/managers/preset_manager.py +100 -64
- pymodaq/utils/managers/preset_manager_utils.py +163 -107
- pymodaq/utils/managers/remote_manager.py +21 -16
- pymodaq/utils/scanner/scan_factory.py +18 -4
- pymodaq/utils/scanner/scan_selector.py +1 -3
- pymodaq/utils/scanner/scanner.py +35 -6
- pymodaq/utils/scanner/scanners/_1d_scanners.py +15 -46
- pymodaq/utils/scanner/scanners/_2d_scanners.py +21 -68
- pymodaq/utils/scanner/scanners/sequential.py +50 -31
- pymodaq/utils/scanner/scanners/tabular.py +45 -28
- {pymodaq-5.0.17.dist-info → pymodaq-5.1.0.dist-info}/METADATA +7 -6
- pymodaq-5.1.0.dist-info/RECORD +154 -0
- {pymodaq-5.0.17.dist-info → pymodaq-5.1.0.dist-info}/entry_points.txt +0 -2
- pymodaq/extensions/bayesian/bayesian_optimisation.py +0 -685
- pymodaq/utils/leco/desktop.ini +0 -2
- pymodaq-5.0.17.dist-info/RECORD +0 -121
- {pymodaq-5.0.17.dist-info → pymodaq-5.1.0.dist-info}/WHEEL +0 -0
- {pymodaq-5.0.17.dist-info → pymodaq-5.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
PAUSE = 'pause'
|
|
11
|
+
STOP = 'stop'
|
|
12
|
+
STOPPING = 'stopping'
|
|
13
|
+
BOUNDS = 'bounds'
|
|
14
|
+
RESTART = 'restart'
|
|
15
|
+
PREDICTION = 'prediction'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class OptimizerThreadStatus(StrEnum):
|
|
19
|
+
|
|
20
|
+
ADD_DATA = "add_data"
|
|
21
|
+
TRADE_OFF = "tradeoff"
|
|
22
|
+
|
|
@@ -0,0 +1,427 @@
|
|
|
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 collections import OrderedDict
|
|
10
|
+
from typing import List, TYPE_CHECKING, Union, Dict, Tuple, Iterable, Optional
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import importlib
|
|
13
|
+
import pkgutil
|
|
14
|
+
import inspect
|
|
15
|
+
import numpy as np
|
|
16
|
+
from collections import namedtuple
|
|
17
|
+
|
|
18
|
+
from pymodaq_utils.abstract import abstract_attribute
|
|
19
|
+
from pymodaq_utils.utils import find_dict_in_list_from_key_val, get_entrypoints
|
|
20
|
+
from pymodaq_utils.logger import set_logger, get_module_name
|
|
21
|
+
from pymodaq_utils.enums import StrEnum
|
|
22
|
+
from pymodaq_utils.config import BaseConfig
|
|
23
|
+
|
|
24
|
+
from pymodaq_gui.plotting.data_viewers.viewer import ViewersEnum
|
|
25
|
+
from pymodaq_gui.managers.parameter_manager import Parameter
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
from pymodaq_data.data import (DataToExport, DataCalculated,
|
|
29
|
+
DataRaw, Axis)
|
|
30
|
+
|
|
31
|
+
from pymodaq.utils.data import DataActuator, DataToActuators
|
|
32
|
+
from pymodaq.utils.managers.modules_manager import ModulesManager
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from pymodaq.control_modules.daq_move import DAQ_Move
|
|
36
|
+
|
|
37
|
+
logger = set_logger(get_module_name(__file__))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class StopType(StrEnum):
|
|
41
|
+
NONE = 'None'
|
|
42
|
+
ITER = 'Iter'
|
|
43
|
+
PREDICT = 'Predict'
|
|
44
|
+
BEST = 'Best'
|
|
45
|
+
|
|
46
|
+
def tip(self):
|
|
47
|
+
if self == StopType.NONE:
|
|
48
|
+
return 'Stopping only after the number of iteration has been reached'
|
|
49
|
+
elif self == StopType.ITER:
|
|
50
|
+
return 'Stopping only after the number of iteration has been reached'
|
|
51
|
+
elif self == StopType.PREDICT:
|
|
52
|
+
return ('Stopping either after the number of iteration has been reached or the last N'
|
|
53
|
+
'tested coordinates have a standard deviation less than tolerance')
|
|
54
|
+
elif self == StopType.BEST:
|
|
55
|
+
return ('Stopping either after the number of iteration has been reached or the N best '
|
|
56
|
+
'coordinates have a standard deviation less than tolerance')
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
StoppingParameters = namedtuple('StoppingParameters',
|
|
60
|
+
['niter', 'stop_type', 'tolerance', 'npoints'])
|
|
61
|
+
|
|
62
|
+
class PredictionError(Exception):
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def individual_as_dte(individual: dict[str, float], actuators: list['DAQ_Move'],
|
|
67
|
+
name: str = 'Individual') -> DataToExport:
|
|
68
|
+
""" Create a DataToExport from the individual coordinates and the list of selected actuators"""
|
|
69
|
+
return DataToExport(
|
|
70
|
+
name,
|
|
71
|
+
data=[DataCalculated(actuators[ind].title,
|
|
72
|
+
data=[np.atleast_1d(individual[actuators[ind].title])],
|
|
73
|
+
units=actuators[ind].units,
|
|
74
|
+
labels=[actuators[ind].title],
|
|
75
|
+
origin=name)
|
|
76
|
+
for ind in range(len(individual))],)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def individual_as_dta(individual: dict[str, float], actuators: list['DAQ_Move'],
|
|
80
|
+
name: str = 'Individual', mode='abs') -> DataToActuators:
|
|
81
|
+
""" Create a DataToActuators from the individual coordinates and the list of selected actuators"""
|
|
82
|
+
return DataToActuators(
|
|
83
|
+
name, mode=mode,
|
|
84
|
+
data=[DataActuator(actuators[ind].title,
|
|
85
|
+
data=[np.atleast_1d(individual[actuators[ind].title])],
|
|
86
|
+
units=actuators[ind].units,
|
|
87
|
+
labels=[actuators[ind].title],
|
|
88
|
+
origin=name)
|
|
89
|
+
for ind in range(len(individual))],)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class GenericAlgorithm(abc.ABC):
|
|
93
|
+
|
|
94
|
+
def __init__(self, ini_random: int, bounds: OrderedDict[str, tuple[float, float]], actuators: list[str]):
|
|
95
|
+
|
|
96
|
+
self._algo = abstract_attribute() #could be a Bayesian on Adaptive algorithm
|
|
97
|
+
self._prediction = abstract_attribute() # could be an acquisition function...
|
|
98
|
+
|
|
99
|
+
self.actuators = actuators
|
|
100
|
+
self.ini_bounds = bounds
|
|
101
|
+
|
|
102
|
+
self._next_point: Optional[dict[str, float]] = None
|
|
103
|
+
self._suggested_coordinates: list[dict[str, float]] = []
|
|
104
|
+
self.ini_random_points = ini_random
|
|
105
|
+
|
|
106
|
+
@abc.abstractmethod
|
|
107
|
+
def set_prediction_function(self, kind: str='', **kwargs):
|
|
108
|
+
""" Set/Load a given function/class to predict next probed points"""
|
|
109
|
+
|
|
110
|
+
@abc.abstractmethod
|
|
111
|
+
def update_prediction_function(self):
|
|
112
|
+
""" Update the parameters of the prediction function (kappa decay for instance)"""
|
|
113
|
+
|
|
114
|
+
def set_acquisition_function(self, kind: str, **kwargs):
|
|
115
|
+
""" Deprecated"""
|
|
116
|
+
self.set_prediction_function(kind, **kwargs)
|
|
117
|
+
|
|
118
|
+
def update_acquisition_function(self):
|
|
119
|
+
""" deprecated"""
|
|
120
|
+
self.update_prediction_function()
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def _acquisition(self):
|
|
124
|
+
""" deprecated """
|
|
125
|
+
return self._prediction
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def tradeoff(self):
|
|
129
|
+
return self._prediction.tradeoff
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
@abc.abstractmethod
|
|
133
|
+
def bounds(self) -> dict[str, tuple[float, float]]:
|
|
134
|
+
...
|
|
135
|
+
|
|
136
|
+
@bounds.setter
|
|
137
|
+
def bounds(self, bounds: dict[str, tuple[float, float]]):
|
|
138
|
+
...
|
|
139
|
+
|
|
140
|
+
@abc.abstractmethod
|
|
141
|
+
def get_random_point(self) -> dict[str, float]:
|
|
142
|
+
""" Get a random point coordinates in the defined bounds"""
|
|
143
|
+
...
|
|
144
|
+
|
|
145
|
+
def ask(self) -> dict[str, float]:
|
|
146
|
+
""" Predict next actuator values to probe
|
|
147
|
+
|
|
148
|
+
Return a DataToActuator, one DataWithAxes per actuator. In general these dwa are 0D
|
|
149
|
+
"""
|
|
150
|
+
try:
|
|
151
|
+
self._next_point = self.prediction_ask()
|
|
152
|
+
except PredictionError:
|
|
153
|
+
self.ini_random_points -= 1
|
|
154
|
+
self._next_point = self.get_random_point()
|
|
155
|
+
self._suggested_coordinates.append(self._next_point)
|
|
156
|
+
return self._next_point
|
|
157
|
+
|
|
158
|
+
@abc.abstractmethod
|
|
159
|
+
def prediction_ask(self) -> dict[str, float]:
|
|
160
|
+
""" Ask the prediction function or algo to provide the next point to probe"""
|
|
161
|
+
|
|
162
|
+
@abc.abstractmethod
|
|
163
|
+
def tell(self, function_value: float):
|
|
164
|
+
""" Add next points and function value into the algo"""
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
@abc.abstractmethod
|
|
168
|
+
def best_fitness(self) -> float:
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
@abc.abstractmethod
|
|
173
|
+
def best_individual(self) -> Union[dict[str, float], None]:
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
@abc.abstractmethod
|
|
177
|
+
def stopping(self, ind_iter: int, stopping_parameters: StoppingParameters) -> bool:
|
|
178
|
+
pass
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class OptimizerModelGeneric(ABC):
|
|
182
|
+
|
|
183
|
+
optimization_algorithm: GenericAlgorithm = None
|
|
184
|
+
|
|
185
|
+
actuators_name: List[str] = []
|
|
186
|
+
detectors_name: List[str] = []
|
|
187
|
+
|
|
188
|
+
observables_dim: List[ViewersEnum] = []
|
|
189
|
+
|
|
190
|
+
params = [] # to be subclassed
|
|
191
|
+
|
|
192
|
+
def __init__(self, optimization_controller):
|
|
193
|
+
self.optimization_controller = optimization_controller # instance of the pid_controller using this model
|
|
194
|
+
self.modules_manager: ModulesManager = optimization_controller.modules_manager
|
|
195
|
+
|
|
196
|
+
self.settings = self.optimization_controller.settings.child('models', 'model_params') # set of parameters
|
|
197
|
+
self.check_modules(self.modules_manager)
|
|
198
|
+
|
|
199
|
+
@abc.abstractmethod
|
|
200
|
+
def has_fitness_observable(self) -> bool:
|
|
201
|
+
""" Should return True if the model defined a 0D data to be used as fitness value"""
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
def check_modules(self, modules_manager):
|
|
205
|
+
for act in self.actuators_name:
|
|
206
|
+
if act not in modules_manager.actuators_name:
|
|
207
|
+
logger.warning(f'The actuator {act} defined in the model is'
|
|
208
|
+
f' not present in the Dashboard')
|
|
209
|
+
return False
|
|
210
|
+
for det in self.detectors_name:
|
|
211
|
+
if det not in modules_manager.detectors_name:
|
|
212
|
+
logger.warning(f'The detector {det} defined in the model is'
|
|
213
|
+
f' not present in the Dashboard')
|
|
214
|
+
|
|
215
|
+
def update_detector_names(self):
|
|
216
|
+
names = self.optimization_controller.settings.child(
|
|
217
|
+
'main_settings', 'detector_modules').value()['selected']
|
|
218
|
+
self.data_names = []
|
|
219
|
+
for name in names:
|
|
220
|
+
name = name.split('//')
|
|
221
|
+
self.data_names.append(name)
|
|
222
|
+
|
|
223
|
+
def update_settings(self, param: Parameter):
|
|
224
|
+
"""
|
|
225
|
+
Get a parameter instance whose value has been modified by a user on the UI
|
|
226
|
+
To be overwritten in child class
|
|
227
|
+
"""
|
|
228
|
+
...
|
|
229
|
+
|
|
230
|
+
def update_plots(self):
|
|
231
|
+
""" Called when updating the live plots """
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
def ini_model_base(self):
|
|
235
|
+
self.modules_manager.selected_actuators_name = self.actuators_name
|
|
236
|
+
self.modules_manager.selected_detectors_name = self.detectors_name
|
|
237
|
+
|
|
238
|
+
self.ini_model()
|
|
239
|
+
|
|
240
|
+
def ini_model(self):
|
|
241
|
+
""" To be subclassed
|
|
242
|
+
|
|
243
|
+
Initialize whatever is needed by your custom model
|
|
244
|
+
"""
|
|
245
|
+
raise NotImplementedError
|
|
246
|
+
|
|
247
|
+
def runner_initialized(self):
|
|
248
|
+
""" To be subclassed
|
|
249
|
+
|
|
250
|
+
Initialize whatever is needed by your custom model after the optimization runner is
|
|
251
|
+
initialized
|
|
252
|
+
"""
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
def convert_input(self, measurements: DataToExport) -> float:
|
|
256
|
+
"""
|
|
257
|
+
Convert the measurements in the units to be fed to the Optimisation Controller
|
|
258
|
+
Parameters
|
|
259
|
+
----------
|
|
260
|
+
measurements: DataToExport
|
|
261
|
+
data object exported from the detectors from which the model extract a float value
|
|
262
|
+
(fitness) to be fed to the algorithm
|
|
263
|
+
|
|
264
|
+
Returns
|
|
265
|
+
-------
|
|
266
|
+
float
|
|
267
|
+
|
|
268
|
+
"""
|
|
269
|
+
raise NotImplementedError
|
|
270
|
+
|
|
271
|
+
def convert_output(self, outputs: dict[str, Union[float, np.ndarray]],
|
|
272
|
+
best_individual: Optional[dict[str, float]] = None) -> DataToActuators:
|
|
273
|
+
""" Convert the output of the Optimisation Controller in units to be fed into the actuators
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
outputs: dict with name of the actuator as key and the value to move to as a float (or ndarray)
|
|
277
|
+
output value from the controller from which the model extract a value of the same units as the actuators
|
|
278
|
+
best_individual: dict[str, float]
|
|
279
|
+
the coordinates of the best individual so far
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
DataToActuatorOpti: derived from DataToExport. Contains value to be fed to the actuators with a 'mode'
|
|
283
|
+
attribute, either 'rel' for relative or 'abs' for absolute.
|
|
284
|
+
|
|
285
|
+
"""
|
|
286
|
+
raise NotImplementedError
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class OptimizerModelDefault(OptimizerModelGeneric):
|
|
290
|
+
|
|
291
|
+
actuators_name: List[str] = [] # to be populated dynamically at instantiation
|
|
292
|
+
detectors_name: List[str] = [] # to be populated dynamically at instantiation
|
|
293
|
+
|
|
294
|
+
params = [{'title': 'Optimizing signal', 'name': 'optimizing_signal', 'type': 'group',
|
|
295
|
+
'children': [
|
|
296
|
+
{'title': 'Get data', 'name': 'data_probe', 'type': 'action'},
|
|
297
|
+
{'title': 'Optimize 0Ds:', 'name': 'optimize_0d', 'type': 'itemselect',
|
|
298
|
+
'checkbox': True},
|
|
299
|
+
]},]
|
|
300
|
+
|
|
301
|
+
def __init__(self, optimization_controller):
|
|
302
|
+
self.actuators_name = optimization_controller.modules_manager.selected_actuators_name
|
|
303
|
+
self.detectors_name = optimization_controller.modules_manager.selected_detectors_name
|
|
304
|
+
super().__init__(optimization_controller)
|
|
305
|
+
|
|
306
|
+
self.settings.child('optimizing_signal', 'data_probe').sigActivated.connect(
|
|
307
|
+
self.optimize_from)
|
|
308
|
+
|
|
309
|
+
def has_fitness_observable(self) -> bool:
|
|
310
|
+
""" Should return True if the model defined a 0D data to be used as fitness value"""
|
|
311
|
+
return len(self.settings.child('optimizing_signal', 'optimize_0d').value()['selected']) == 1
|
|
312
|
+
|
|
313
|
+
def ini_model(self):
|
|
314
|
+
pass
|
|
315
|
+
|
|
316
|
+
def update_settings(self, param: Parameter):
|
|
317
|
+
pass
|
|
318
|
+
|
|
319
|
+
def convert_input(self, measurements: DataToExport) -> float:
|
|
320
|
+
""" Convert the measurements in the units to be fed to the Optimisation Controller
|
|
321
|
+
|
|
322
|
+
Parameters
|
|
323
|
+
----------
|
|
324
|
+
measurements: DataToExport
|
|
325
|
+
data object exported from the detectors from which the model extract a float value
|
|
326
|
+
(fitness) to be fed to the algorithm
|
|
327
|
+
|
|
328
|
+
Returns
|
|
329
|
+
-------
|
|
330
|
+
float
|
|
331
|
+
|
|
332
|
+
"""
|
|
333
|
+
data_name: str = self.settings['optimizing_signal', 'optimize_0d']['selected'][0]
|
|
334
|
+
origin, name = data_name.split('/')
|
|
335
|
+
return float(measurements.get_data_from_name_origin(name, origin).data[0][0])
|
|
336
|
+
|
|
337
|
+
def convert_output(self, outputs: dict[str, Union[float, np.ndarray]],
|
|
338
|
+
best_individual: Optional[dict[str, float]] = None) -> DataToActuators:
|
|
339
|
+
""" Convert the output of the Optimisation Controller in units to be fed into the actuators
|
|
340
|
+
Parameters
|
|
341
|
+
----------
|
|
342
|
+
outputs: dict with name of the actuator as key and the value to move to as a float (or ndarray)
|
|
343
|
+
output value from the controller from which the model extract a value of the same units as the actuators
|
|
344
|
+
best_individual: dict[str, float]
|
|
345
|
+
the coordinates of the best individual so far
|
|
346
|
+
Returns
|
|
347
|
+
-------
|
|
348
|
+
DataToActuatorOpti: derived from DataToExport. Contains value to be fed to the actuators with a 'mode'
|
|
349
|
+
attribute, either 'rel' for relative or 'abs' for absolute.
|
|
350
|
+
|
|
351
|
+
"""
|
|
352
|
+
return individual_as_dta(outputs, self.modules_manager.actuators, 'outputs', mode='abs')
|
|
353
|
+
|
|
354
|
+
def optimize_from(self):
|
|
355
|
+
self.modules_manager.get_det_data_list()
|
|
356
|
+
data0D = self.modules_manager.settings['data_dimensions', 'det_data_list0D']
|
|
357
|
+
data0D['selected'] = data0D['all_items']
|
|
358
|
+
self.settings.child('optimizing_signal', 'optimize_0d').setValue(data0D)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def get_optimizer_models(model_name=None):
|
|
362
|
+
"""
|
|
363
|
+
Get Optimizer Models as a list to instantiate Control Actuators per degree of liberty in the model
|
|
364
|
+
|
|
365
|
+
Returns
|
|
366
|
+
-------
|
|
367
|
+
list: list of disct containting the name and python module of the found models
|
|
368
|
+
"""
|
|
369
|
+
models_import = []
|
|
370
|
+
discovered_models = get_entrypoints(group='pymodaq.models')
|
|
371
|
+
if len(discovered_models) > 0:
|
|
372
|
+
for pkg in discovered_models:
|
|
373
|
+
try:
|
|
374
|
+
module = importlib.import_module(pkg.value)
|
|
375
|
+
module_name = pkg.value
|
|
376
|
+
|
|
377
|
+
for mod in pkgutil.iter_modules([
|
|
378
|
+
str(Path(module.__file__).parent.joinpath('models'))]):
|
|
379
|
+
try:
|
|
380
|
+
model_module = importlib.import_module(f'{module_name}.models.{mod.name}',
|
|
381
|
+
module)
|
|
382
|
+
classes = inspect.getmembers(model_module, inspect.isclass)
|
|
383
|
+
for name, klass in classes:
|
|
384
|
+
if issubclass(klass, OptimizerModelGeneric):
|
|
385
|
+
if find_dict_in_list_from_key_val(models_import, 'name', mod.name)\
|
|
386
|
+
is None:
|
|
387
|
+
models_import.append({'name': klass.__name__,
|
|
388
|
+
'module': model_module,
|
|
389
|
+
'class': klass})
|
|
390
|
+
|
|
391
|
+
except Exception as e:
|
|
392
|
+
logger.warning(str(e))
|
|
393
|
+
|
|
394
|
+
except Exception as e:
|
|
395
|
+
logger.warning(f'Impossible to import the {pkg.value} optimizer model: {str(e)}')
|
|
396
|
+
|
|
397
|
+
if find_dict_in_list_from_key_val(models_import, 'name', 'OptimizerModelDefault') \
|
|
398
|
+
is None:
|
|
399
|
+
models_import.append({'name': 'OptimizerModelDefault',
|
|
400
|
+
'module': inspect.getmodule(OptimizerModelDefault),
|
|
401
|
+
'class': OptimizerModelDefault})
|
|
402
|
+
if model_name is None:
|
|
403
|
+
return models_import
|
|
404
|
+
else:
|
|
405
|
+
return find_dict_in_list_from_key_val(models_import, 'name', model_name)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class OptimizerConfig(BaseConfig):
|
|
409
|
+
"""Main class to deal with configuration values for this plugin
|
|
410
|
+
|
|
411
|
+
To b subclassed for real implementation if needed, see Optimizer class attribute config_saver
|
|
412
|
+
"""
|
|
413
|
+
config_template_path = None
|
|
414
|
+
config_name = f"optimizer_settings"
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def find_key_in_nested_dict(dic, key):
|
|
418
|
+
stack = [dic]
|
|
419
|
+
while stack:
|
|
420
|
+
d = stack.pop()
|
|
421
|
+
if key in d:
|
|
422
|
+
return d[key]
|
|
423
|
+
for v in d.values():
|
|
424
|
+
if isinstance(v, dict):
|
|
425
|
+
stack.append(v)
|
|
426
|
+
if isinstance(v, list):
|
|
427
|
+
stack += v
|
|
@@ -7,7 +7,8 @@ if TYPE_CHECKING:
|
|
|
7
7
|
class PIDController:
|
|
8
8
|
""" Fake controller object for the DAQ_Move_PID"""
|
|
9
9
|
|
|
10
|
-
def __init__(self, daq_pid: 'DAQ_PID'):
|
|
10
|
+
def __init__(self, daq_pid: 'DAQ_PID', set_point_name: str = 'setpoint'):
|
|
11
11
|
self.curr_point = daq_pid.curr_points_signal
|
|
12
12
|
self.setpoint = daq_pid.setpoints_signal
|
|
13
|
-
self.emit_curr_points = daq_pid.emit_curr_points_sig
|
|
13
|
+
self.emit_curr_points = daq_pid.emit_curr_points_sig
|
|
14
|
+
self.queue_points = daq_pid.queue_points[set_point_name]
|
|
@@ -1,23 +1,78 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections import deque
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import numpy as np
|
|
4
|
+
from pymodaq_utils.utils import ThreadCommand
|
|
5
5
|
|
|
6
|
+
from pymodaq.control_modules.move_utility_classes import (
|
|
7
|
+
DAQ_Move_base,
|
|
8
|
+
DataActuator,
|
|
9
|
+
DataActuatorType,
|
|
10
|
+
comon_parameters_fun,
|
|
11
|
+
)
|
|
6
12
|
from pymodaq.extensions.pid.actuator_controller import PIDController
|
|
7
13
|
|
|
8
14
|
|
|
9
15
|
class DAQ_Move_PID(DAQ_Move_base):
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
_controller_units =
|
|
16
|
+
""" """
|
|
17
|
+
|
|
18
|
+
_controller_units = ""
|
|
13
19
|
data_actuator_type = DataActuatorType.DataActuator
|
|
14
20
|
is_multiaxes = False
|
|
15
|
-
stage_names = [
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
stage_names = [
|
|
22
|
+
"",
|
|
23
|
+
]
|
|
24
|
+
params = [ # elements to be added in order to control your custom stage
|
|
25
|
+
{
|
|
26
|
+
"title": "Check stability:",
|
|
27
|
+
"name": "check_stab",
|
|
28
|
+
"type": "bool",
|
|
29
|
+
"value": False,
|
|
30
|
+
"default": "False",
|
|
31
|
+
"tooltip": "Activate to only trigger move_done once standard deviation over queue length is below threshold",
|
|
32
|
+
"children": [
|
|
33
|
+
{
|
|
34
|
+
"title": "Stable:",
|
|
35
|
+
"name": "is_stab",
|
|
36
|
+
"type": "led",
|
|
37
|
+
"value": False,
|
|
38
|
+
"default": False,
|
|
39
|
+
"tooltip": "Red if the standard deviation of the last positions is above threshold, green if below",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"title": "Current stability:",
|
|
43
|
+
"name": "current_stab",
|
|
44
|
+
"type": "float",
|
|
45
|
+
"value": 0,
|
|
46
|
+
"default": 0,
|
|
47
|
+
"readonly": True,
|
|
48
|
+
"tooltip": "",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"title": "Threshold:",
|
|
52
|
+
"name": "threshold",
|
|
53
|
+
"type": "float",
|
|
54
|
+
"value": 0.1,
|
|
55
|
+
"default": 0.1,
|
|
56
|
+
"min": 0,
|
|
57
|
+
"tooltip": "Standard deviation threshold to consider the stage stable",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"title": "Queue length:",
|
|
61
|
+
"name": "queue_length",
|
|
62
|
+
"type": "int",
|
|
63
|
+
"default": 10,
|
|
64
|
+
"value": 50,
|
|
65
|
+
"min": 0,
|
|
66
|
+
"tooltip": "Length of the queue used to compute the standard deviation for stability check",
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
] + comon_parameters_fun(is_multiaxes, stage_names, master=False)
|
|
71
|
+
# params = comon_parameters_fun(is_multiaxes, stage_names, master=False)
|
|
18
72
|
|
|
19
73
|
def ini_attributes(self):
|
|
20
74
|
self.controller: PIDController = None
|
|
75
|
+
self.last_positions = deque(maxlen=self.settings["check_stab", "queue_length"])
|
|
21
76
|
|
|
22
77
|
def update_position(self, dict_val: dict):
|
|
23
78
|
self.current_value = dict_val[self.parent.title]
|
|
@@ -30,48 +85,70 @@ class DAQ_Move_PID(DAQ_Move_base):
|
|
|
30
85
|
def close(self):
|
|
31
86
|
pass
|
|
32
87
|
|
|
33
|
-
def
|
|
34
|
-
|
|
88
|
+
def user_condition_to_reach_target(self):
|
|
89
|
+
cond = super().user_condition_to_reach_target()
|
|
90
|
+
parameter_stab = self.settings.child("check_stab")
|
|
35
91
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
92
|
+
if parameter_stab.value():
|
|
93
|
+
if len(self.controller.queue_points) >= self.settings["check_stab", "queue_length"]:
|
|
94
|
+
self.last_positions = deque(
|
|
95
|
+
self.controller.queue_points, maxlen=self.settings["check_stab", "queue_length"]
|
|
96
|
+
)
|
|
97
|
+
current_stab = np.std(self.last_positions)
|
|
98
|
+
parameter_stab.child("current_stab").setValue(current_stab)
|
|
99
|
+
cond = current_stab <= parameter_stab["threshold"]
|
|
100
|
+
else:
|
|
101
|
+
cond = False
|
|
102
|
+
|
|
103
|
+
parameter_stab.child("is_stab").setValue(cond)
|
|
40
104
|
|
|
105
|
+
return cond
|
|
106
|
+
|
|
107
|
+
def commit_settings(self, param):
|
|
108
|
+
if param.name() == "check_stab":
|
|
109
|
+
pass
|
|
110
|
+
elif param.name() == "queue_length":
|
|
111
|
+
self.last_positions = deque(
|
|
112
|
+
self.controller.queue_points, maxlen=self.settings["check_stab", "queue_length"]
|
|
113
|
+
)
|
|
114
|
+
param.setOpts(max=self.controller.queue_points.maxlen)
|
|
115
|
+
|
|
116
|
+
def ini_stage(self, controller: PIDController = None):
|
|
117
|
+
""" """
|
|
118
|
+
self.controller = controller
|
|
41
119
|
self.controller.curr_point.connect(self.update_position)
|
|
42
120
|
|
|
121
|
+
self.settings.child("check_stab", "queue_length").setValue(self.controller.queue_points.maxlen)
|
|
122
|
+
|
|
43
123
|
info = "PID stage"
|
|
44
124
|
initialized = True
|
|
45
125
|
return info, initialized
|
|
46
126
|
|
|
47
127
|
def move_abs(self, position: DataActuator):
|
|
48
|
-
"""
|
|
49
|
-
"""
|
|
128
|
+
""" """
|
|
50
129
|
position = self.check_bound(position)
|
|
51
|
-
self.
|
|
130
|
+
self.target_value = position
|
|
52
131
|
|
|
53
|
-
self.controller.setpoint.emit({self.parent.title: self.
|
|
132
|
+
self.controller.setpoint.emit({self.parent.title: self.target_value})
|
|
54
133
|
|
|
55
134
|
def move_rel(self, position: DataActuator):
|
|
56
|
-
"""
|
|
57
|
-
"""
|
|
135
|
+
""" """
|
|
58
136
|
position = self.check_bound(self.current_value + position) - self.current_value
|
|
59
|
-
self.
|
|
137
|
+
self.target_value = position + self.current_value
|
|
60
138
|
|
|
61
|
-
self.controller.setpoint.emit({self.parent.title: self.
|
|
139
|
+
self.controller.setpoint.emit({self.parent.title: self.target_value})
|
|
62
140
|
self.poll_moving()
|
|
63
141
|
|
|
64
142
|
def move_home(self):
|
|
65
|
-
"""
|
|
66
|
-
"""
|
|
67
|
-
self.emit_status(ThreadCommand('Update_Status', ['Move Home not implemented']))
|
|
143
|
+
""" """
|
|
144
|
+
self.emit_status(ThreadCommand("Update_Status", ["Move Home not implemented"]))
|
|
68
145
|
|
|
69
146
|
def stop_motion(self):
|
|
70
147
|
"""
|
|
71
|
-
|
|
148
|
+
Call the specific move_done function (depending on the hardware).
|
|
72
149
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
150
|
+
See Also
|
|
151
|
+
--------
|
|
152
|
+
move_done
|
|
76
153
|
"""
|
|
77
154
|
self.move_done()
|