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

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 da9a0722 was da9a0722, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

unit test updates

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