source: sasview/src/sas/qtgui/Perspectives/Fitting/ComplexConstraint.py @ e0d5b63

ESS_GUI_opencl
Last change on this file since e0d5b63 was 460bdf4, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Fixed bug for when subsequent C&S jobs would fail.
Modified constraint display to correspond better to the control state.

  • Property mode set to 100644
File size: 10.6 KB
RevLine 
[2d466e4]1"""
2Widget for multi-model constraints.
3"""
4import os
[725d9c06]5
6# numpy methods required for the validator! Don't remove.
7# pylint: disable=unused-import,unused-wildcard-import,redefined-builtin
[2d466e4]8from numpy import *
9
10from PyQt5 import QtCore
11from PyQt5 import QtGui
12from PyQt5 import QtWidgets
13import webbrowser
14
[305114c]15from sas.qtgui.Perspectives.Fitting import FittingUtilities
[2d466e4]16import sas.qtgui.Utilities.GuiUtils as GuiUtils
[ecc5d043]17from sas.qtgui.Perspectives.Fitting.Constraint import Constraint
18
[d72ac57]19#ALLOWED_OPERATORS = ['=','<','>','>=','<=']
20ALLOWED_OPERATORS = ['=']
[2d466e4]21
22# Local UI
23from sas.qtgui.Perspectives.Fitting.UI.ComplexConstraintUI import Ui_ComplexConstraintUI
24
25class ComplexConstraint(QtWidgets.QDialog, Ui_ComplexConstraintUI):
[ecc5d043]26    constraintReadySignal = QtCore.pyqtSignal(tuple)
[2d466e4]27    def __init__(self, parent=None, tabs=None):
[cf9f39e]28        super(ComplexConstraint, self).__init__(parent)
[2d466e4]29
30        self.setupUi(self)
31        self.setModal(True)
32
[33c0561]33        # disable the context help icon
34        windowFlags = self.windowFlags()
35        self.setWindowFlags(windowFlags & ~QtCore.Qt.WindowContextHelpButtonHint)
36
[2d466e4]37        # Useful globals
38        self.tabs = tabs
39        self.params = None
40        self.tab_names = None
41        self.operator = '='
[ecc5d043]42        self._constraint = Constraint()
[2e5081b]43        self.all_menu   = None
[2d466e4]44
[305114c]45        self.warning = self.lblWarning.text()
[2d466e4]46        self.setupData()
47        self.setupSignals()
[305114c]48        self.setupWidgets()
[da9a0722]49        self.setupTooltip()
[2d466e4]50
[14ec91c5]51        # Default focus is on OK
52        self.cmdOK.setFocus()
53
[2d466e4]54    def setupData(self):
55        """
56        Digs into self.tabs and pulls out relevant info
57        """
58        self.tab_names = [tab.kernel_module.name for tab in self.tabs]
59        self.params = [tab.getParamNames() for tab in self.tabs]
60
61    def setupSignals(self):
62        """
63        Signals from various elements
64        """
[ecc5d043]65        self.cmdOK.clicked.connect(self.onApply)
[2d466e4]66        self.cmdHelp.clicked.connect(self.onHelp)
[33c0561]67        self.cmdAddAll.clicked.connect(self.onSetAll)
68
[c5a2722f]69        self.txtConstraint.editingFinished.connect(self.validateFormula)
[2e5081b]70        self.cbModel1.currentIndexChanged.connect(self.onModelIndexChange)
71        self.cbModel2.currentIndexChanged.connect(self.onModelIndexChange)
[2d466e4]72
73        self.cbParam1.currentIndexChanged.connect(self.onParamIndexChange)
74        self.cbParam2.currentIndexChanged.connect(self.onParamIndexChange)
75        self.cbOperator.currentIndexChanged.connect(self.onOperatorChange)
76
77    def setupWidgets(self):
78        """
79        Setup widgets based on current parameters
80        """
[2e5081b]81        self.cbModel1.insertItems(0, self.tab_names)
82        self.cbModel2.insertItems(0, self.tab_names)
[2d466e4]83
[ecc5d043]84        self.setupParamWidgets()
85
[33c0561]86
[2e5081b]87        self.setupMenu()
88
89    def setupMenu(self):
[33c0561]90        # Show Add All button, if necessary
[2e5081b]91        if self.cbModel1.currentText() ==self.cbModel2.currentText():
[33c0561]92            self.cmdAddAll.setVisible(False)
93        else:
94            self.cmdAddAll.setVisible(True)
95        return
[ecc5d043]96
97    def setupParamWidgets(self):
98        """
99        Fill out comboboxes and set labels with non-constrained parameters
100        """
[2d466e4]101        self.cbParam1.clear()
[2e5081b]102        tab_index1 = self.cbModel1.currentIndex()
103        items1 = [param for param in self.params[tab_index1] if not self.tabs[tab_index1].paramHasConstraint(param)]
[ecc5d043]104        self.cbParam1.addItems(items1)
105
106        # M2 doesn't have to be non-constrained
[2d466e4]107        self.cbParam2.clear()
[2e5081b]108        tab_index2 = self.cbModel2.currentIndex()
109        items2 = [param for param in self.params[tab_index2]]
[ecc5d043]110        self.cbParam2.addItems(items2)
[2d466e4]111
[2e5081b]112        self.txtParam.setText(self.tab_names[tab_index1] + ":" + self.cbParam1.currentText())
[2d466e4]113
114        self.cbOperator.clear()
115        self.cbOperator.addItems(ALLOWED_OPERATORS)
116        self.txtOperator.setText(self.cbOperator.currentText())
117
[2e5081b]118        self.txtConstraint.setText(self.tab_names[tab_index2]+"."+self.cbParam2.currentText())
[2d466e4]119
[ecc5d043]120        # disable Apply if no parameters available
121        if len(items1)==0:
122            self.cmdOK.setEnabled(False)
[33c0561]123            self.cmdAddAll.setEnabled(False)
[ecc5d043]124            txt = "No parameters in model "+self.tab_names[0] +\
125                " are available for constraining."
126            self.lblWarning.setText(txt)
127        else:
128            self.cmdOK.setEnabled(True)
[33c0561]129            self.cmdAddAll.setEnabled(True)
[ecc5d043]130            txt = ""
131            self.lblWarning.setText(txt)
132
[2d466e4]133    def setupTooltip(self):
134        """
135        Tooltip for txtConstraint
136        """
[da9a0722]137        p1 = self.tab_names[0] + ":" + self.cbParam1.currentText()
138        p2 = self.tab_names[1]+"."+self.cbParam2.currentText()
139        tooltip = "E.g.\n%s = 2.0 * (%s)\n" %(p1, p2)
140        tooltip += "%s = sqrt(%s) + 5"%(p1, p2)
[2d466e4]141        self.txtConstraint.setToolTip(tooltip)
142
143    def onParamIndexChange(self, index):
144        """
145        Respond to parameter combo box changes
146        """
147        # Find out the signal source
148        source = self.sender().objectName()
[305114c]149        param1 = self.cbParam1.currentText()
150        param2 = self.cbParam2.currentText()
[2d466e4]151        if source == "cbParam1":
[460bdf4]152            self.txtParam.setText(self.cbModel1.currentText() + ":" + param1)
[2d466e4]153        else:
[460bdf4]154            self.txtConstraint.setText(self.cbModel2.currentText() + "." + param2)
[305114c]155        # Check if any of the parameters are polydisperse
156        params_list = [param1, param2]
157        all_pars = [tab.model_parameters for tab in self.tabs]
158        is2Ds = [tab.is2D for tab in self.tabs]
159        txt = ""
160        for pars, is2D in zip(all_pars, is2Ds):
161            if any([FittingUtilities.isParamPolydisperse(p, pars, is2D) for p in params_list]):
162                # no parameters are pd - reset the text to not show the warning
163                txt = self.warning
164        self.lblWarning.setText(txt)
165
[2e5081b]166    def onModelIndexChange(self, index):
167        """
168        Respond to mode combo box changes
169        """
170        # disable/enable Add All
171        self.setupMenu()
172        # Reload parameters
173        self.setupParamWidgets()
[2d466e4]174
175    def onOperatorChange(self, index):
176        """
177        Respond to operator combo box changes
178        """
179        self.txtOperator.setText(self.cbOperator.currentText())
180
181    def validateFormula(self):
182        """
183        Add visual cues when formula is incorrect
184        """
[2e5081b]185        # temporarily disable validation
186        return
187        #
[2d466e4]188        formula_is_valid = self.validateConstraint(self.txtConstraint.text())
189        if not formula_is_valid:
190            self.cmdOK.setEnabled(False)
[33c0561]191            self.cmdAddAll.setEnabled(False)
[2d466e4]192            self.txtConstraint.setStyleSheet("QLineEdit {background-color: red;}")
193        else:
194            self.cmdOK.setEnabled(True)
[33c0561]195            self.cmdAddAll.setEnabled(True)
[2d466e4]196            self.txtConstraint.setStyleSheet("QLineEdit {background-color: white;}")
197
198    def validateConstraint(self, constraint_text):
199        """
200        Ensure the constraint has proper form
201        """
202        # 0. none or empty
203        if not constraint_text or not isinstance(constraint_text, str):
204            return False
205
[2e5081b]206        # M1.scale --> model_str='M1', constraint_text='scale'
[c5a2722f]207        param_str = self.cbParam2.currentText()
[2d466e4]208        constraint_text = constraint_text.strip()
[2e5081b]209        model_str = self.cbModel2.currentText()
[c5a2722f]210
211        # 0. Has to contain the model name
[2e5081b]212        if model_str != model_str:
[c5a2722f]213            return False
214
[ba01ad1]215        # Remove model name from constraint
[c5a2722f]216        constraint_text = constraint_text.replace(model_str+".",'')
[2d466e4]217
218        # 1. just the parameter
219        if param_str == constraint_text:
220            return True
221
222        # 2. ensure the text contains parameter name
223        parameter_string_start = constraint_text.find(param_str)
224        if parameter_string_start < 0:
225            return False
226
[c5a2722f]227        # 3. replace parameter name with "1" and try to evaluate the expression
[2d466e4]228        try:
229            expression_to_evaluate = constraint_text.replace(param_str, "1.0")
230            eval(expression_to_evaluate)
231        except Exception:
232            # Too many cases to cover individually, just a blanket
233            # Exception should be sufficient
234            # Note that in current numpy things like sqrt(-1) don't
235            # raise but just return warnings
236            return False
237
238        return True
239
[c5a2722f]240    def constraint(self):
241        """
[ecc5d043]242        Return the generated constraint
243        """
244        param = self.cbParam1.currentText()
245        value = self.cbParam2.currentText()
246        func = self.txtConstraint.text()
[2e5081b]247        value_ex = self.cbModel2.currentText() + "." + self.cbParam2.currentText()
248        model1 = self.cbModel1.currentText()
[ecc5d043]249        operator = self.cbOperator.currentText()
250
251        con = Constraint(self,
252                         param=param,
253                         value=value,
254                         func=func,
255                         value_ex=value_ex,
256                         operator=operator)
257
258        return (model1, con)
259
260    def onApply(self):
261        """
262        Respond to Add constraint action.
263        Send a signal that the constraint is ready to be applied
264        """
265        cons_tuple = self.constraint()
266        self.constraintReadySignal.emit(cons_tuple)
267        # reload the comboboxes
268        self.setupParamWidgets()
269
270    def onSetAll(self):
271        """
272        Set constraints on all identically named parameters between two fitpages
[c5a2722f]273        """
[ecc5d043]274        # loop over parameters in constrained model
[9c207f5]275        index1 = self.cbModel1.currentIndex()
276        index2 = self.cbModel2.currentIndex()
277        items1 = [param for param in self.params[index1] if not self.tabs[index1].paramHasConstraint(param)]
278        items2 = self.params[index2]
[ecc5d043]279        for item in items1:
280            if item not in items2: continue
281            param = item
282            value = item
[2e5081b]283            func = self.cbModel2.currentText() + "." + param
284            value_ex = self.cbModel1.currentText() + "." + param
285            model1 = self.cbModel1.currentText()
[ecc5d043]286            operator = self.cbOperator.currentText()
287
288            con = Constraint(self,
289                             param=param,
290                             value=value,
291                             func=func,
292                             value_ex=value_ex,
293                             operator=operator)
294
295            self.constraintReadySignal.emit((model1, con))
296
297        # reload the comboboxes
298        self.setupParamWidgets()
[c5a2722f]299
[2d466e4]300    def onHelp(self):
301        """
302        Display related help section
303        """
304        try:
305            help_location = GuiUtils.HELP_DIRECTORY_LOCATION + \
[aed0532]306            "/user/qtgui/Perspectives/Fitting/fitting_help.html#simultaneous-fits-with-constraints"
[2d466e4]307            webbrowser.open('file://' + os.path.realpath(help_location))
308        except AttributeError:
309            # No manager defined - testing and standalone runs
310            pass
311
312
313
Note: See TracBrowser for help on using the repository browser.