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
pymodaq/dashboard.py CHANGED
@@ -8,7 +8,7 @@ import logging
8
8
  from pathlib import Path
9
9
  from importlib import import_module
10
10
  from packaging import version as version_mod
11
- from typing import Tuple, List, Any, TYPE_CHECKING
11
+ from typing import Tuple, List, Any, TYPE_CHECKING, Sequence
12
12
 
13
13
 
14
14
  from qtpy import QtGui, QtWidgets, QtCore
@@ -46,7 +46,6 @@ from pymodaq.utils import config as config_mod_pymodaq
46
46
  from pymodaq.control_modules.daq_move import DAQ_Move
47
47
  from pymodaq.control_modules.daq_viewer import DAQ_Viewer
48
48
  from pymodaq_gui.utils.splash import get_splash_sc
49
-
50
49
  from pymodaq import extensions as extmod
51
50
 
52
51
  logger = set_logger(get_module_name(__file__))
@@ -72,6 +71,7 @@ class ManagerEnums(BaseEnum):
72
71
  overshoot = 2
73
72
  roi = 3
74
73
 
74
+
75
75
  class PymodaqUpdateTableWidget(QTableWidget):
76
76
  '''
77
77
  A class to represent PyMoDAQ and its subpackages'
@@ -113,10 +113,9 @@ class PymodaqUpdateTableWidget(QTableWidget):
113
113
  self.setItem(row, 2, QTableWidgetItem(str(current_version)))
114
114
  self.setItem(row, 3, QTableWidgetItem(str(available_version)))
115
115
 
116
-
117
116
  def get_checked_data(self):
118
- checked = list(map(lambda c : c.isChecked(), self._checkboxes))
119
- return list(np.array(self._package_versions)[checked])
117
+ checked = list(map(lambda c : c.isChecked(), self._checkboxes))
118
+ return list(np.array(self._package_versions)[checked])
120
119
 
121
120
  def sizeHint(self):
122
121
  self.resizeColumnsToContents()
@@ -133,6 +132,7 @@ class PymodaqUpdateTableWidget(QTableWidget):
133
132
 
134
133
  return QSize(width, height)
135
134
 
135
+
136
136
  class DashBoard(CustomApp):
137
137
  """
138
138
  Main class initializing a DashBoard interface to display det and move modules and logger """
@@ -336,9 +336,40 @@ class DashBoard(CustomApp):
336
336
  self.bayesian_window.setWindowTitle('Bayesian Optimiser')
337
337
  self.bayesian_module = extmod.BayesianOptimisation(dockarea=dockarea, dashboard=self)
338
338
  self.extensions['bayesian'] = self.bayesian_module
339
- self.bayesian_window.show()
339
+
340
+ if self.bayesian_module.validate_config():
341
+ self.bayesian_window.show()
342
+ else:
343
+ messagebox(severity='critical', title="Bayesian Optimisation error",
344
+ text=f"""
345
+ <p>Saved Bayesian Optimisation configuration file is not compatible anymore.</p>
346
+ <p>Please delete the file at <b>{self.bayesian_module.config_path}</b>.</p>
347
+ """)
348
+ self.bayesian_module.quit()
340
349
  return self.bayesian_module
341
350
 
351
+ def load_adaptive(self, win=None):
352
+ if win is None:
353
+ self.adaptive_window = QtWidgets.QMainWindow()
354
+ else:
355
+ self.adaptive_window = win
356
+ dockarea = DockArea()
357
+ self.adaptive_window.setCentralWidget(dockarea)
358
+ self.adaptive_window.setWindowTitle('Adaptive Scan')
359
+ self.adaptive_module = extmod.AdaptiveOptimisation(dockarea=dockarea, dashboard=self)
360
+ self.extensions['adaptive'] = self.adaptive_module
361
+
362
+ if self.adaptive_module.validate_config():
363
+ self.adaptive_window.show()
364
+ else:
365
+ messagebox(severity='critical', title="Adaptive Optimisation error",
366
+ text=f"""
367
+ <p>Saved Adaptive Optimisation configuration file is not compatible anymore.</p>
368
+ <p>Please delete the file at <b>{self.adaptive_module.config_path}</b>.</p>
369
+ """)
370
+ self.adaptive_module.quit()
371
+ return self.adaptive_module
372
+
342
373
  def load_extension_from_name(self, name: str) -> dict:
343
374
  return self.load_extensions_module(find_dict_in_list_from_key_val(extensions, 'name', name))
344
375
 
@@ -440,6 +471,7 @@ class DashBoard(CustomApp):
440
471
  self.add_action('do_pid', 'PID module', auto_toolbar=False)
441
472
  self.add_action('console', 'IPython Console', auto_toolbar=False)
442
473
  self.add_action('bayesian', 'Bayesian Optimisation', auto_toolbar=False)
474
+ self.add_action('adaptive', 'Adaptive Scan', auto_toolbar=False)
443
475
 
444
476
  self.add_action('about', 'About', 'information2')
445
477
  self.add_action('help', 'Help', 'help1')
@@ -517,6 +549,8 @@ class DashBoard(CustomApp):
517
549
  self.connect_action('do_pid', lambda: self.load_pid_module())
518
550
  self.connect_action('console', lambda: self.load_console())
519
551
  self.connect_action('bayesian', lambda: self.load_bayesian())
552
+ self.connect_action('adaptive', lambda: self.load_adaptive())
553
+
520
554
 
521
555
  self.connect_action('about', self.show_about)
522
556
  self.connect_action('help', self.show_help)
@@ -598,6 +632,7 @@ class DashBoard(CustomApp):
598
632
  self.extensions_menu.addAction(self.get_action('do_pid'))
599
633
  self.extensions_menu.addAction(self.get_action('console'))
600
634
  self.extensions_menu.addAction(self.get_action('bayesian'))
635
+ self.extensions_menu.addAction(self.get_action('adaptive'))
601
636
 
602
637
  # extensions from plugins
603
638
  extensions_actions = []
@@ -613,13 +648,15 @@ class DashBoard(CustomApp):
613
648
  help_menu.addAction(self.get_action('check_update'))
614
649
  help_menu.addAction(self.get_action('plugin_manager'))
615
650
 
616
- self.overshoot_menu.setEnabled(False)
617
- self.roi_menu.setEnabled(False)
618
- self.remote_menu.setEnabled(False)
619
- self.extensions_menu.setEnabled(False)
620
- self.file_menu.setEnabled(True)
621
- self.settings_menu.setEnabled(True)
622
- self.preset_menu.setEnabled(True)
651
+ status = self.preset_file is None
652
+
653
+ self.overshoot_menu.setEnabled(not status)
654
+ self.roi_menu.setEnabled(not status)
655
+ self.remote_menu.setEnabled(not status)
656
+ self.extensions_menu.setEnabled(not status)
657
+ self.file_menu.setEnabled(status)
658
+ self.settings_menu.setEnabled(status)
659
+ self.preset_menu.setEnabled(status)
623
660
 
624
661
  def start_plugin_manager(self):
625
662
  self.win_plug_manager = QtWidgets.QMainWindow()
@@ -955,13 +992,33 @@ class DashBoard(CustomApp):
955
992
  detector_modules.append(det_mod_tmp)
956
993
  return det_mod_tmp
957
994
 
995
+ def override_det_from_extension(self, overriden_grabbers: Sequence[str] = None):
996
+ """ (Experimental) If an extension adding detectors within the Dashboard need to, it could call this
997
+ method.
998
+
999
+ Then if some other extension trigger a grab from it, the request of a grab won't be done twice
1000
+
1001
+ Parameters
1002
+ ----------
1003
+ overriden_grabbers: Sequence[str]
1004
+ sequence of detector names whose corresponding modules should set their
1005
+ attribute override_grab_from_extension to True.
1006
+ """
1007
+ if overriden_grabbers is not None:
1008
+ for mod_name in overriden_grabbers:
1009
+ mod = self.modules_manager.get_mod_from_name(mod_name, 'det')
1010
+ if mod is not None:
1011
+ mod.override_grab_from_extension = True
1012
+
958
1013
  def add_det_from_extension(self, name: str, daq_type: str, instrument_name: str,
959
1014
  instrument_controller: Any):
1015
+
960
1016
  """ Specific method to add a DAQ_Viewer within the Dashboard. This Particular detector
961
1017
  should be defined in the plugin of the extension and is used to mimic a grab while data
962
1018
  are actually coming from the extension which loaded it
963
1019
 
964
1020
  For an exemple, see the pymodaq_plugins_datamixer plugin and its DataMixer extension
1021
+ or the DAQ_PID extension
965
1022
 
966
1023
  Parameters
967
1024
  ----------
@@ -992,7 +1049,8 @@ class DashBoard(CustomApp):
992
1049
 
993
1050
  def update_module_manager(self):
994
1051
  if self.modules_manager is None:
995
- self.modules_manager = ModulesManager(self.detector_modules, self.actuators_modules)
1052
+ self.modules_manager = ModulesManager(self.detector_modules, self.actuators_modules,
1053
+ parent_name='Dashboard')
996
1054
  else:
997
1055
  self.modules_manager.actuators_all = self.actuators_modules
998
1056
  self.modules_manager.detectors_all = self.detector_modules
@@ -1033,17 +1091,20 @@ class DashBoard(CustomApp):
1033
1091
  self.preset_manager.preset_params.child('Moves').children()]
1034
1092
  plugins += [{'type': 'det', 'value': child} for child in
1035
1093
  self.preset_manager.preset_params.child('Detectors').children()]
1036
-
1037
1094
  for plug in plugins:
1038
- if plug["type"] == 'det':
1039
- plug['ID'] = plug['value']['params', 'detector_settings', 'controller_ID']
1040
- plug['status'] = plug['value']['params', 'detector_settings',
1041
- 'controller_status']
1042
- else:
1043
- plug['ID'] = plug['value']['params', 'move_settings',
1044
- 'multiaxes', 'controller_ID']
1045
- plug['status'] = plug['value'][
1046
- 'params', 'move_settings', 'multiaxes', 'multi_status']
1095
+ if plug["type"] == 'det':
1096
+ try:
1097
+ plug['ID'] = plug['value']['params', 'detector_settings', 'controller_ID']
1098
+ plug['status'] = plug['value']['params', 'detector_settings', 'controller_status']
1099
+ except KeyError as e:
1100
+ raise DetectorError
1101
+ else:
1102
+ try:
1103
+ plug['ID'] = plug['value']['params', 'move_settings','multiaxes', 'controller_ID']
1104
+ plug['status'] = plug['value']['params', 'move_settings', 'multiaxes', 'multi_status']
1105
+ except KeyError as e:
1106
+ raise ActuatorError
1107
+
1047
1108
 
1048
1109
 
1049
1110
  IDs = list(set([plug['ID'] for plug in plugins]))
@@ -1421,8 +1482,11 @@ class DashBoard(CustomApp):
1421
1482
  self.mainwindow.setVisible(True)
1422
1483
  for area in self.dockarea.tempAreas:
1423
1484
  area.window().setVisible(True)
1424
- messagebox(text=f'{str(error)}\nQuitting the application...',
1425
- title='Incompatibility')
1485
+ messagebox(severity='critical', title="Preset loading error",
1486
+ text=f"""
1487
+ <p>Saved preset file is not compatible anymore.</p>
1488
+ <p>Please recreate the preset at <b>{filename}</b>.</p>
1489
+ """)
1426
1490
  logger.exception(str(error))
1427
1491
 
1428
1492
  self.quit_fun()
@@ -1607,7 +1671,6 @@ class DashBoard(CustomApp):
1607
1671
 
1608
1672
  packages_data = np.array(list(zip(packages, current_versions, available_versions)))[new_versions]
1609
1673
 
1610
- #TODO: Remove `or True`
1611
1674
  if len(packages_data) > 0:
1612
1675
  #Create a QDialog window and different graphical components
1613
1676
  dialog = QtWidgets.QDialog()
@@ -13,11 +13,14 @@ For remote control, you need to start a Coordinator, as described for remote con
13
13
 
14
14
  import logging
15
15
  from time import sleep
16
- from typing import List, Union
16
+ from typing import cast, List, Optional
17
17
 
18
18
  from pyleco.utils.listener import Listener
19
19
 
20
20
 
21
+ from pymodaq_data.data import DataWithAxes
22
+ from pymodaq_utils.serialize.factory import SerializableFactory
23
+
21
24
  class QtLessModule:
22
25
  """Some module doing things without Qt.
23
26
 
@@ -33,6 +36,9 @@ class QtLessModule:
33
36
  self._fake_position = 0
34
37
  self.start_listen()
35
38
  self._stored = []
39
+ # register DataWithAxes for deserialization
40
+ cls = DataWithAxes
41
+ SerializableFactory().register_from_type(cls, cls.serialize, cls.deserialize)
36
42
 
37
43
  def start_listen(self) -> None:
38
44
  """Start to listen on incoming commands."""
@@ -45,8 +51,9 @@ class QtLessModule:
45
51
  register_rpc_method = self.communicator.register_rpc_method
46
52
  register_rpc_method(self.set_info)
47
53
  register_rpc_method(self.send_data)
48
- register_rpc_method(self.move_abs)
49
- register_rpc_method(self.move_rel)
54
+ # binary methods can accept additionally binary payload, like serialized pymodaq objects.
55
+ self.listener.register_binary_rpc_method(self.move_abs, accept_binary_input=True)
56
+ self.listener.register_binary_rpc_method(self.move_rel, accept_binary_input=True)
50
57
  register_rpc_method(self.move_home)
51
58
  register_rpc_method(self.get_actuator_value)
52
59
  register_rpc_method(self.stop_motion)
@@ -56,7 +63,17 @@ class QtLessModule:
56
63
  """Stop to listen on incoming commands."""
57
64
  self.listener.stop_listen()
58
65
 
59
- # smethods for being remote controlled
66
+ @staticmethod
67
+ def extract_pymodaq_object(
68
+ value: Optional[float], additional_payload: Optional[List[bytes]]
69
+ ):
70
+ if value is None and additional_payload:
71
+ res = cast(DataWithAxes, SerializableFactory().get_apply_deserializer(additional_payload[0]))
72
+ else:
73
+ res = value
74
+ return res
75
+
76
+ # methods for being remote controlled
60
77
  # these methods are executed and cannot talk to the controlling module directly.
61
78
  # if you need to send a response (for example with a value) you have to store the information and
62
79
  # send it after these methods have been executed.
@@ -73,13 +90,33 @@ class QtLessModule:
73
90
  print("send_data")
74
91
 
75
92
  # actuator commands
76
- def move_abs(self, position: Union[float, str]) -> None:
77
- print("move_abs", position)
78
- self._fake_position = float(position)
79
-
80
- def move_rel(self, position: Union[float, str]) -> None:
81
- print("move_rel", position)
82
- self._fake_position += float(position)
93
+ def move_abs(
94
+ self,
95
+ position: Optional[float],
96
+ additional_payload: Optional[List[bytes]] = None,
97
+ ) -> None:
98
+ """Move to an absolute position.
99
+
100
+ :param position: Deprecated, should be None and content transferred binary.
101
+ :param additional_payload: binary frames containing the position as PyMoDAQ `DataActuator`.
102
+ """
103
+ pos = self.extract_pymodaq_object(position, additional_payload)
104
+ print("move_abs", pos)
105
+ self._fake_position = float(pos)
106
+
107
+ def move_rel(
108
+ self,
109
+ position: Optional[float],
110
+ additional_payload: Optional[List[bytes]] = None,
111
+ ) -> None:
112
+ """Move by a relative position.
113
+
114
+ :param position: Deprecated, should be None and content transferred binary.
115
+ :param additional_payload: binary frames containing the position as PyMoDAQ `DataActuator`.
116
+ """
117
+ pos = self.extract_pymodaq_object(position, additional_payload)
118
+ print("move_rel", pos)
119
+ self._fake_position += float(pos)
83
120
 
84
121
  def move_home(self) -> None:
85
122
  self._fake_position = 0
@@ -11,10 +11,14 @@ from .console import QtConsole
11
11
  from .daq_scan import DAQScan
12
12
  from .daq_logger.daq_logger import DAQ_Logger
13
13
  from .pid.pid_controller import DAQ_PID
14
- from .h5browser import H5Browser
14
+ from .h5browser import H5Browser #backcompat but should be loaded from pymodaq_gui!
15
+
16
+ from .bayesian.bayesian_optimization import BayesianOptimization
17
+ from .bayesian.utils import OptimizerModelDefault
18
+
19
+ from .adaptive.adaptive_optimization import AdaptiveOptimisation
20
+
15
21
 
16
- from .bayesian.bayesian_optimisation import BayesianOptimisation
17
- from .bayesian.utils import BayesianModelDefault, BayesianModelGeneric
18
22
 
19
23
 
20
24
 
@@ -0,0 +1,2 @@
1
+ from . import adaptive_optimization
2
+ from . import utils
@@ -0,0 +1,159 @@
1
+
2
+ from pymodaq_utils import utils
3
+ from pymodaq_utils import config as config_mod
4
+ from pymodaq_utils.logger import set_logger, get_module_name
5
+ from pymodaq_utils.utils import ThreadCommand
6
+
7
+ from pymodaq.extensions.optimizers_base.optimizer import (
8
+ GenericOptimization, OptimizationRunner, optimizer_params)
9
+ from pymodaq.extensions.optimizers_base.utils import OptimizerModelDefault, find_key_in_nested_dict
10
+ from pymodaq.extensions.optimizers_base.thread_commands import OptimizerToRunner
11
+
12
+ from pymodaq.extensions.adaptive.loss_function import LossFunctionFactory,LossDim
13
+ from pymodaq.extensions.adaptive.utils import AdaptiveAlgorithm, AdaptiveConfig
14
+
15
+
16
+ logger = set_logger(get_module_name(__file__))
17
+ config = config_mod.Config()
18
+
19
+
20
+ EXTENSION_NAME = 'AdaptiveScan'
21
+ CLASS_NAME = 'AdaptiveOptimization'
22
+
23
+ STARTING_LOSS_DIM = LossDim.LOSS_1D
24
+
25
+ PREDICTION_NAMES = list(LossFunctionFactory.keys(STARTING_LOSS_DIM))
26
+ PREDICTION_PARAMS = (
27
+ [{'title': 'LossDim', 'name': 'lossdim', 'type': 'str',
28
+ 'value': LossDim.LOSS_1D, 'readonly': True},
29
+ {'title': 'Kind', 'name': 'kind', 'type': 'list',
30
+ 'value': PREDICTION_NAMES[0],
31
+ 'limits': PREDICTION_NAMES}] +
32
+ LossFunctionFactory.get(STARTING_LOSS_DIM,
33
+ PREDICTION_NAMES[0]).params)
34
+
35
+
36
+ class AdaptiveOptimizationRunner(OptimizationRunner):
37
+
38
+
39
+ def __init__(self, *args, **kwargs):
40
+ super().__init__(*args, **kwargs)
41
+
42
+ def queue_command(self, command: ThreadCommand):
43
+ """
44
+ """
45
+ if command.command == OptimizerToRunner.PREDICTION:
46
+ utility_params = {k: v for k, v in command.attribute.items()
47
+ if k not in ("kind", "tradeoff_actual", 'lossdim')}
48
+
49
+ self.optimization_algorithm.set_prediction_function(command.attribute['lossdim'],
50
+ command.attribute['kind'],
51
+ **utility_params)
52
+ else:
53
+ super().queue_command(command)
54
+
55
+
56
+ class AdaptiveOptimisation(GenericOptimization):
57
+ """ PyMoDAQ extension of the DashBoard to perform the optimization of a target signal
58
+ taken form the detectors as a function of one or more parameters controlled by the actuators.
59
+ """
60
+
61
+ runner = AdaptiveOptimizationRunner
62
+ params = optimizer_params(PREDICTION_PARAMS)
63
+ config_saver = AdaptiveConfig
64
+
65
+ DISPLAY_BEST = False
66
+
67
+ def ini_custom_attributes(self):
68
+ """ Here you can reimplement specific attributes"""
69
+ self._base_name: str = 'Adaptive'
70
+ self.settings.child('main_settings', 'ini_random').hide()
71
+
72
+ def validate_config(self) -> bool:
73
+ utility = find_key_in_nested_dict(self.optimizer_config.to_dict(), 'prediction')
74
+ if utility:
75
+ try:
76
+ utility_params = { k : v for k, v in utility.items() \
77
+ if k not in ("kind", "tradeoff_actual", 'lossdim') }
78
+ LossFunctionFactory.create(utility['lossdim'],
79
+ utility['kind'], **utility_params)
80
+ except (ValueError, KeyError):
81
+ return False
82
+
83
+ return True
84
+
85
+ def value_changed(self, param):
86
+ """ to be subclassed for actions to perform when one of the param's value in self.settings is changed
87
+
88
+ For instance:
89
+ if param.name() == 'do_something':
90
+ if param.value():
91
+ print('Do something')
92
+ self.settings.child('main_settings', 'something_done').setValue(False)
93
+
94
+ Parameters
95
+ ----------
96
+ param: (Parameter) the parameter whose value just changed
97
+ """
98
+ super().value_changed(param)
99
+ if param.name() == 'lossdim':
100
+ self.settings.child('main_settings', 'prediction', 'kind').setLimits(
101
+ LossFunctionFactory.keys(param.value())
102
+ )
103
+
104
+ elif param.name() == 'kind':
105
+ utility_settings = self.settings.child('main_settings', 'prediction')
106
+ old_children = utility_settings.children()[2:]
107
+ for child in old_children:
108
+ utility_settings.removeChild(child)
109
+ try:
110
+ params = LossFunctionFactory.get(utility_settings['lossdim'],
111
+ param.value()).params
112
+ utility_settings.addChildren(params)
113
+ except (KeyError, ValueError):
114
+ pass
115
+
116
+ def update_prediction_function(self):
117
+ utility_settings = self.settings.child('main_settings', 'prediction')
118
+ try:
119
+ uparams = {child.name() : child.value() for child in utility_settings.children()}
120
+ LossFunctionFactory.get(uparams['lossdim'], uparams['kind'])
121
+ self.command_runner.emit(
122
+ utils.ThreadCommand(OptimizerToRunner.PREDICTION, uparams))
123
+ except (KeyError, ValueError):
124
+ pass
125
+
126
+ def update_after_actuators_changed(self, actuators: list[str]):
127
+ """ Actions to do after the actuators have been updated
128
+ """
129
+ self.settings.child('main_settings', 'prediction',
130
+ 'lossdim').setValue(LossDim.get_enum_from_dim_as_int(len(actuators)))
131
+ self.update_prediction_function()
132
+
133
+ def adaptive_bounds(self):
134
+ return list(self.format_bounds().values())
135
+
136
+ def set_algorithm(self):
137
+ self.algorithm = AdaptiveAlgorithm(
138
+ ini_random=1,
139
+ bounds=self.adaptive_bounds(),
140
+ loss_type=LossDim(self.settings['main_settings', 'prediction', 'lossdim']),
141
+ kind=self.settings['main_settings', 'prediction', 'kind'])
142
+
143
+
144
+ def main():
145
+ from pymodaq_gui.utils.utils import mkQApp
146
+ from pymodaq.utils.gui_utils.loader_utils import load_dashboard_with_preset
147
+
148
+ app = mkQApp('Adaptive Optimiser')
149
+ preset_file_name = config('presets', f'default_preset_for_scan')
150
+
151
+ dashboard, extension, win = load_dashboard_with_preset(preset_file_name, 'AdaptiveScan')
152
+
153
+ app.exec()
154
+
155
+ return dashboard, extension, win
156
+
157
+ if __name__ == '__main__':
158
+ main()
159
+
@@ -0,0 +1,73 @@
1
+ from typing import TYPE_CHECKING, Callable
2
+
3
+ from .loss_factory import LossFunctionBase, LossFunctionFactory, LossDim
4
+
5
+ from adaptive.learner.learner1D import (
6
+ curvature_loss_function,
7
+ default_loss,
8
+ uniform_loss,
9
+ resolution_loss_function,
10
+ abs_min_log_loss,
11
+ uses_nth_neighbors,
12
+
13
+ )
14
+
15
+
16
+ def default_loss_function(*args, **kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
17
+ return default_loss
18
+
19
+
20
+ def uniform_loss_function(**kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
21
+ return uniform_loss
22
+
23
+
24
+ def abs_min_log_loss_function(**kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
25
+ return abs_min_log_loss
26
+
27
+
28
+
29
+ @LossFunctionFactory.register()
30
+ class DefaultLoss(LossFunctionBase):
31
+ _loss = staticmethod(default_loss_function)
32
+ dim = LossDim.LOSS_1D
33
+ usual_name = 'Default'
34
+ params = []
35
+
36
+
37
+ @LossFunctionFactory.register()
38
+ class UniformLoss(LossFunctionBase):
39
+ _loss = staticmethod(uniform_loss_function)
40
+ dim = LossDim.LOSS_1D
41
+ usual_name = 'Uniform'
42
+ params = []
43
+
44
+
45
+ @LossFunctionFactory.register()
46
+ class CurvatureLoss(LossFunctionBase):
47
+ _loss = staticmethod(curvature_loss_function)
48
+ dim = LossDim.LOSS_1D
49
+ usual_name = 'Curvature'
50
+ params = [
51
+ {'title': 'Area', 'name': 'area_factor', 'type': 'float', 'value': 1.},
52
+ {'title': 'Euclid', 'name': 'euclid_factor', 'type': 'float', 'value': 0.02},
53
+ {'title': 'Horizontal', 'name': 'horizontal_factor', 'type': 'float', 'value': 0.02}
54
+ ]
55
+
56
+
57
+ @LossFunctionFactory.register()
58
+ class ResolutionLoss(LossFunctionBase):
59
+ _loss = staticmethod(resolution_loss_function)
60
+ dim = LossDim.LOSS_1D
61
+ usual_name = 'Resolution'
62
+ params = [
63
+ {'title': 'Min:', 'name': 'min_length', 'type': 'float', 'value': 0., 'min': 0., 'max': 1.},
64
+ {'title': 'Max:', 'name': 'max_length', 'type': 'float', 'value': 1., 'min': 0., 'max': 1.},
65
+ ]
66
+
67
+
68
+ @LossFunctionFactory.register()
69
+ class AbsMinLogLoss(LossFunctionBase):
70
+ _loss = staticmethod(abs_min_log_loss_function)
71
+ dim = LossDim.LOSS_1D
72
+ usual_name = 'AbsMinLog'
73
+ params = []
@@ -0,0 +1,86 @@
1
+ from typing import TYPE_CHECKING, Callable
2
+
3
+ from .loss_factory import LossFunctionBase, LossFunctionFactory, LossDim
4
+
5
+ from adaptive.learner.learner2D import (
6
+ default_loss,
7
+ uniform_loss,
8
+ resolution_loss_function,
9
+ minimize_triangle_surface_loss,
10
+ thresholded_loss_function,
11
+ triangle_loss,
12
+
13
+ )
14
+
15
+
16
+ def default_loss_function(*args, **kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
17
+ return default_loss
18
+
19
+
20
+ def uniform_loss_function(**kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
21
+ return uniform_loss
22
+
23
+
24
+ def triangle_loss_function(**kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
25
+ return triangle_loss
26
+
27
+
28
+ def minimize_triangle_surface_loss_function(**kwargs): #should be wrapped to handle eventual initializing argument, see params attributes below
29
+ return minimize_triangle_surface_loss
30
+
31
+
32
+ @LossFunctionFactory.register()
33
+ class DefaultLoss(LossFunctionBase):
34
+ _loss = staticmethod(default_loss_function)
35
+ dim = LossDim.LOSS_2D
36
+ usual_name = 'Default'
37
+ params = []
38
+
39
+
40
+ @LossFunctionFactory.register()
41
+ class UniformLoss(LossFunctionBase):
42
+ _loss = staticmethod(uniform_loss_function)
43
+ dim = LossDim.LOSS_2D
44
+ usual_name = 'Uniform'
45
+ params = []
46
+
47
+
48
+
49
+ @LossFunctionFactory.register()
50
+ class ResolutionLoss(LossFunctionBase):
51
+ _loss = staticmethod(resolution_loss_function)
52
+ dim = LossDim.LOSS_2D
53
+ usual_name = 'Resolution'
54
+ params = [
55
+ {'title': 'Min:', 'name': 'min_distance', 'type': 'float', 'value': 0., 'min': 0., 'max': 1.},
56
+ {'title': 'Max:', 'name': 'max_distance', 'type': 'float', 'value': 1., 'min': 0., 'max': 1.},
57
+ ]
58
+
59
+
60
+ @LossFunctionFactory.register()
61
+ class MinTriangleLoss(LossFunctionBase):
62
+ _loss = staticmethod(minimize_triangle_surface_loss_function)
63
+ dim = LossDim.LOSS_2D
64
+ usual_name = 'MinTriangle'
65
+ params = []
66
+
67
+
68
+
69
+ @LossFunctionFactory.register()
70
+ class ThresholdLoss(LossFunctionBase):
71
+ _loss = staticmethod(thresholded_loss_function)
72
+ dim = LossDim.LOSS_2D
73
+ usual_name = 'Threshold'
74
+ params = [
75
+ {'title': 'Lower:', 'name': 'lower_threshold', 'type': 'float', 'value': None,},
76
+ {'title': 'Upper:', 'name': 'upper_threshold', 'type': 'float', 'value': None,},
77
+ {'title': 'Priority factor:', 'name': 'priority_factor', 'type': 'float', 'value': 0.1,},
78
+ ]
79
+
80
+
81
+ @LossFunctionFactory.register()
82
+ class TriangleLoss(LossFunctionBase):
83
+ _loss = staticmethod(triangle_loss)
84
+ dim = LossDim.LOSS_2D
85
+ usual_name = 'Triangle'
86
+ params = []
@@ -0,0 +1,3 @@
1
+ from .loss_factory import LossFunctionFactory, LossDim
2
+ from . import _1d_loss_functions # to register inner class
3
+ from . import _2d_loss_functions # to register inner class