Source code for pylorenzmie.lmtool.OptimizerWidget
import logging
from pyqtgraph.parametertree import (Parameter, ParameterTree)
from pyqtgraph.Qt.QtCore import (pyqtProperty, pyqtSlot, pyqtSignal)
from pylorenzmie.lib import LMObject
logger = logging.getLogger(__name__)
[docs]
class OptimizerWidget(ParameterTree):
'''Parameter-tree widget for configuring scipy least_squares settings.
Emits :attr:`settingChanged` (name, value) whenever any setting changes.
Enforces the constraint that Levenberg-Marquardt requires ``loss='linear'``.
'''
#: Emitted with ``(name, value)`` whenever a solver setting changes.
settingChanged = pyqtSignal(str, object)
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._buildTree()
self._consistencyCheck()
self._connectSignals()
def _buildTree(self) -> None:
p = [
{'name': 'fraction', 'type': 'float',
'value': 0.25, 'limits': (0.01, 1), 'step': 0.01,
'tip': 'fraction of pixels to fit'},
{'name': 'method', 'type': 'list',
'values': {'Levenberg-Marquardt': 'lm',
'Dogbox': 'dogbox',
'Trust Region Reflective': 'trf'},
'default': 'lm'},
{'name': 'loss', 'type': 'list',
'values': 'linear soft_l1 huber cauchy arctan'.split(),
'default': 'linear'},
{'name': 'ftol', 'type': 'float',
'value': 1e-4, 'limits': [1e-8, 1e-2], 'default': 1e-4},
{'name': 'xtol', 'type': 'float',
'value': 1e-6, 'limits': [1e-8, 1e-2], 'default': 1e-6},
{'name': 'gtol', 'type': 'float',
'value': 1e-6, 'limits': [1e-8, 1e-2], 'default': 1e-6},
{'name': 'diff_step', 'type': 'float',
'value': 1e-5, 'limits': [1e-8, 1e-2], 'default': 1e-5},
]
self.params = Parameter.create(name='params',
type='group',
children=p)
self.setParameters(self.params, showTop=False)
def _consistencyCheck(self) -> None:
if self.params.child('method').value() == 'lm':
widget = self.params.child('loss')
widget.setValue('linear')
widget.setOpts(enabled=False)
self.settingChanged.emit('loss', 'linear')
else:
self.params.child('loss').setOpts(enabled=True)
def _connectSignals(self) -> None:
method_param = self.params.child('method')
method_param.sigValueChanged.connect(self._updateLoss)
for p in self.params:
p.sigValueChanged.connect(self._handleValueChanged)
@pyqtSlot(object, object)
def _updateLoss(self, widget: 'QWidget', value: LMObject.Property):
self._consistencyCheck()
@pyqtSlot(object, object)
def _handleValueChanged(self, widget: 'QWidget',
value: LMObject.Property):
logger.debug(f'emitting {widget.name()} {value}')
self.settingChanged.emit(widget.name(), value)
@pyqtProperty(dict)
def settings(self) -> dict[str, LMObject.Property]:
return {p.name(): p.value() for p in self.params}
@settings.setter
def settings(self, settings: dict[str, LMObject.Property]) -> None:
for name, value in settings.items():
try:
widget = self.params.child(name)
widget.setValue(value)
except KeyError:
logger.debug(f'{name} unknown')
[docs]
@classmethod
def example(cls) -> None:
from pyqtgraph import mkQApp
app = mkQApp()
widget = cls()
widget.show()
widget.settings = {'ftol': 1e-3, 'method': 'dogbox'}
app.exec()
if __name__ == '__main__': # pragma: no cover
OptimizerWidget.example()