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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since ba01ad1 was ba01ad1, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Added batch fit constraints.
Cleaned up interactions between constraints in various tabs

  • Property mode set to 100755
File size: 6.9 KB
Line 
1"""
2Widget for multi-model constraints.
3"""
4import os
5from numpy import *
6
7from PyQt5 import QtCore
8from PyQt5 import QtGui
9from PyQt5 import QtWidgets
10import webbrowser
11
12import sas.qtgui.Utilities.GuiUtils as GuiUtils
13ALLOWED_OPERATORS = ['=','<','>','>=','<=']
14
15# Local UI
16from sas.qtgui.Perspectives.Fitting.UI.ComplexConstraintUI import Ui_ComplexConstraintUI
17from sas.qtgui.Perspectives.Fitting.Constraints import Constraint
18
19class ComplexConstraint(QtWidgets.QDialog, Ui_ComplexConstraintUI):
20    def __init__(self, parent=None, tabs=None):
21        super(ComplexConstraint, self).__init__()
22
23        self.setupUi(self)
24        #self.setFixedSize(self.minimumSizeHint())
25        self.setModal(True)
26
27        # Useful globals
28        self.tabs = tabs
29        self.params = None
30        self.tab_names = None
31        self.operator = '='
32
33        self.setupData()
34        self.setupWidgets()
35        self.setupSignals()
36        #self.setupTooltip()
37
38        self.setFixedSize(self.minimumSizeHint())
39
40    def setupData(self):
41        """
42        Digs into self.tabs and pulls out relevant info
43        """
44        self.tab_names = [tab.kernel_module.name for tab in self.tabs]
45        self.params = [tab.getParamNames() for tab in self.tabs]
46        pass
47
48    def setupSignals(self):
49        """
50        Signals from various elements
51        """
52        self.cmdOK.clicked.connect(self.accept)
53        self.cmdHelp.clicked.connect(self.onHelp)
54        self.cmdRevert.clicked.connect(self.onRevert)
55        self.txtConstraint.editingFinished.connect(self.validateFormula)
56
57        self.cbParam1.currentIndexChanged.connect(self.onParamIndexChange)
58        self.cbParam2.currentIndexChanged.connect(self.onParamIndexChange)
59        self.cbOperator.currentIndexChanged.connect(self.onOperatorChange)
60
61    def setupWidgets(self):
62        """
63        Setup widgets based on current parameters
64        """
65        self.txtName1.setText(self.tab_names[0])
66        self.txtName2.setText(self.tab_names[1])
67
68        self.cbParam1.clear()
69        self.cbParam1.addItems(self.params[0])
70        self.cbParam2.clear()
71        self.cbParam2.addItems(self.params[1])
72
73        self.txtParam.setText(self.tab_names[0] + ":" + self.cbParam1.currentText())
74
75        self.cbOperator.clear()
76        self.cbOperator.addItems(ALLOWED_OPERATORS)
77        self.txtOperator.setText(self.cbOperator.currentText())
78
79        self.txtConstraint.setText(self.tab_names[1]+"."+self.cbParam2.currentText())
80
81    def setupTooltip(self):
82        """
83        Tooltip for txtConstraint
84        """
85        tooltip = "E.g.\n%s = 2.0 * (%s)\n" %(self.params[0], self.params[1])
86        tooltip += "%s = sqrt(%s) + 5"%(self.params[0], self.params[1])
87        self.txtConstraint.setToolTip(tooltip)
88
89    def onParamIndexChange(self, index):
90        """
91        Respond to parameter combo box changes
92        """
93        # Find out the signal source
94        source = self.sender().objectName()
95        if source == "cbParam1":
96            self.txtParam.setText(self.tab_names[0] + ":" + self.cbParam1.currentText())
97        else:
98            self.txtConstraint.setText(self.tab_names[1] + "." + self.cbParam2.currentText())
99        pass
100
101    def onOperatorChange(self, index):
102        """
103        Respond to operator combo box changes
104        """
105        self.txtOperator.setText(self.cbOperator.currentText())
106
107    def onRevert(self):
108        """
109        switch M1 <-> M2
110        """
111        # Switch parameters
112        self.params[1], self.params[0] = self.params[0], self.params[1]
113        self.tab_names[1], self.tab_names[0] = self.tab_names[0], self.tab_names[1]
114        # Try to swap parameter names in the line edit
115        current_text = self.txtConstraint.text()
116        new_text = current_text.replace(self.cbParam1.currentText(), self.cbParam2.currentText())
117        self.txtConstraint.setText(new_text)
118        # Update labels and tooltips
119        index1 = self.cbParam1.currentIndex()
120        index2 = self.cbParam2.currentIndex()
121        indexOp = self.cbOperator.currentIndex()
122        self.setupWidgets()
123
124        # Original indices
125        self.cbParam1.setCurrentIndex(index2)
126        self.cbParam2.setCurrentIndex(index1)
127        self.cbOperator.setCurrentIndex(indexOp)
128        #self.setupTooltip()
129
130    def validateFormula(self):
131        """
132        Add visual cues when formula is incorrect
133        """
134        formula_is_valid = False
135        formula_is_valid = self.validateConstraint(self.txtConstraint.text())
136        if not formula_is_valid:
137            self.cmdOK.setEnabled(False)
138            self.txtConstraint.setStyleSheet("QLineEdit {background-color: red;}")
139        else:
140            self.cmdOK.setEnabled(True)
141            self.txtConstraint.setStyleSheet("QLineEdit {background-color: white;}")
142
143    def validateConstraint(self, constraint_text):
144        """
145        Ensure the constraint has proper form
146        """
147        # 0. none or empty
148        if not constraint_text or not isinstance(constraint_text, str):
149            return False
150
151        # M1.scale  --> model_str='M1', constraint_text='scale'
152        param_str = self.cbParam2.currentText()
153        constraint_text = constraint_text.strip()
154        model_str = self.txtName2.text()
155
156        # 0. Has to contain the model name
157        if model_str != self.txtName2.text():
158            return False
159
160        # Remove model name from constraint
161        constraint_text = constraint_text.replace(model_str+".",'')
162
163        # 1. just the parameter
164        if param_str == constraint_text:
165            return True
166
167        # 2. ensure the text contains parameter name
168        parameter_string_start = constraint_text.find(param_str)
169        if parameter_string_start < 0:
170            return False
171        parameter_string_end = parameter_string_start + len(param_str)
172
173        # 3. replace parameter name with "1" and try to evaluate the expression
174        try:
175            expression_to_evaluate = constraint_text.replace(param_str, "1.0")
176            eval(expression_to_evaluate)
177        except Exception:
178            # Too many cases to cover individually, just a blanket
179            # Exception should be sufficient
180            # Note that in current numpy things like sqrt(-1) don't
181            # raise but just return warnings
182            return False
183
184        return True
185
186    def constraint(self):
187        """
188        Return the generated constraint as tuple (model1, param1, operator, constraint)
189        """
190        return (self.txtName1.text(), self.cbParam1.currentText(), self.cbOperator.currentText(), self.txtConstraint.text())
191
192    def onHelp(self):
193        """
194        Display related help section
195        """
196        try:
197            help_location = GuiUtils.HELP_DIRECTORY_LOCATION + \
198            "/user/sasgui/perspectives/fitting/fitting_help.html#simultaneous-fits-with-constraints"
199            webbrowser.open('file://' + os.path.realpath(help_location))
200        except AttributeError:
201            # No manager defined - testing and standalone runs
202            pass
203
204
205
Note: See TracBrowser for help on using the repository browser.