source: sasview/src/sas/qtgui/Perspectives/Fitting/MultiConstraint.py @ 09e0c32

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 09e0c32 was 09e0c32, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Added tooltip on COnstraints table.
Added "validate" parameter to Constraint, allowing for looser validation
of complex, multi-fitpage setups.

  • Property mode set to 100644
File size: 5.8 KB
Line 
1"""
2Widget for parameter constraints.
3"""
4import os
5import webbrowser
6
7# numpy methods required for the validator! Don't remove.
8# pylint: disable=unused-import,unused-wildcard-import,redefined-builtin
9from numpy import *
10
11from PyQt5 import QtWidgets
12
13import sas.qtgui.Utilities.GuiUtils as GuiUtils
14
15# Local UI
16from sas.qtgui.Perspectives.Fitting.UI.MultiConstraintUI import Ui_MultiConstraintUI
17
18class MultiConstraint(QtWidgets.QDialog, Ui_MultiConstraintUI):
19    """
20    Logic class for interacting with MultiConstrainedUI view
21    """
22    def __init__(self, parent=None, params=None, constraint=None):
23        """
24        parent: ConstraintWidget object
25        params: tuple of strings describing model parameters
26        """
27        super(MultiConstraint, self).__init__()
28
29        self.setupUi(self)
30        self.setModal(True)
31        self.params = params
32        self.parent = parent
33        # Text of the constraint
34        self.function = None
35        # Should this constraint be validated?
36        self.validate = True
37
38        self.input_constraint = constraint
39        if self.input_constraint is not None:
40            variable = constraint.value
41            self.function = constraint.func
42            self.params.append(variable)
43            self.model_name = constraint.value_ex
44            # Passed constraint may be too complex for simple validation
45            self.validate = constraint.validate
46        else:
47            self.model_name = self.params[1]
48
49        self.setupLabels()
50        self.setupTooltip()
51
52        # Set param text control to the second parameter passed
53        if self.input_constraint is None:
54            self.txtConstraint.setText(self.params[1])
55        else:
56            self.txtConstraint.setText(self.function)
57        self.cmdOK.clicked.connect(self.accept)
58        self.cmdHelp.clicked.connect(self.onHelp)
59        self.cmdRevert.clicked.connect(self.revert)
60        self.txtConstraint.editingFinished.connect(self.validateFormula)
61
62        # Default focus is on OK
63        self.cmdOK.setFocus()
64
65    def revert(self):
66        """
67        Swap parameters in the view
68        """
69        # Switch parameters
70        self.params[1], self.params[0] = self.params[0], self.params[1]
71        # change fully qualified param name (e.g. M1.sld -> M1.sld_solvent)
72        self.model_name = self.model_name.replace(self.params[0], self.params[1])
73        # Try to swap parameter names in the line edit
74        current_text = self.txtConstraint.text()
75        new_text = current_text.replace(self.params[0], self.params[1])
76        self.txtConstraint.setText(new_text)
77        # Update labels and tooltips
78        self.setupLabels()
79        self.setupTooltip()
80
81    def setupLabels(self):
82        """
83        Setup labels based on current parameters
84        """
85        l1 = str(self.params[0])
86        l2 = str(self.params[1])
87        self.txtParam1.setText(l1)
88        self.txtParam1_2.setText(l1)
89        self.txtParam2.setText(l2)
90
91    def setupTooltip(self):
92        """
93        Tooltip for txtConstraint
94        """
95        tooltip = "E.g.\n%s = 2.0 * (%s)\n" %(self.params[0], self.params[1])
96        tooltip += "%s = sqrt(%s) + 5"%(self.params[0], self.params[1])
97        self.txtConstraint.setToolTip(tooltip)
98
99    def validateFormula(self):
100        """
101        Add visual cues when formula is incorrect
102        """
103        # Don't validate if requested
104        if not self.validate: return
105
106        formula_is_valid = False
107        formula_is_valid = self.validateConstraint(self.txtConstraint.text())
108        if not formula_is_valid:
109            self.cmdOK.setEnabled(False)
110            self.txtConstraint.setStyleSheet("QLineEdit {background-color: red;}")
111        else:
112            self.cmdOK.setEnabled(True)
113            self.txtConstraint.setStyleSheet("QLineEdit {background-color: white;}")
114
115    def validateConstraint(self, constraint_text):
116        """
117        Ensure the constraint has proper form
118        """
119        # 0. none or empty
120        if not constraint_text or not isinstance(constraint_text, str):
121            return False
122
123        param_str = self.model_name
124
125        # 1. just the parameter
126        if param_str == constraint_text:
127            return True
128
129        # 2. ensure the text contains parameter name
130        parameter_string_start = constraint_text.find(param_str)
131        if parameter_string_start < 0:
132            return False
133        parameter_string_end = parameter_string_start + len(param_str)
134
135        # 3. parameter name should be a separate word, but can have "()[]*+-/ " around
136        #valid_neighbours = "()[]*+-/ "
137        #start_loc = parameter_string_start -1
138        #end_loc = parameter_string_end
139        #if not any([constraint_text[start_loc] == ch for ch in valid_neighbours]):
140        #    return False
141        #if end_loc < len(constraint_text):
142        #    if not any([constraint_text[end_loc] == ch for ch in valid_neighbours]):
143        #        return False
144
145        # 4. replace parameter name with "1" and try to evaluate the expression
146        try:
147            expression_to_evaluate = constraint_text.replace(param_str, "1.0")
148            eval(expression_to_evaluate)
149        except Exception:
150            # Too many cases to cover individually, just a blanket
151            # Exception should be sufficient
152            # Note that in current numpy things like sqrt(-1) don't
153            # raise but just return warnings
154            return False
155
156        return True
157
158    def onHelp(self):
159        """
160        Display related help section
161        """
162        try:
163            help_location = GuiUtils.HELP_DIRECTORY_LOCATION + \
164            "/user/qtgui/Perspectives/Fitting/fitting_help.html#simultaneous-fits-with-constraints"
165            webbrowser.open('file://' + os.path.realpath(help_location))
166        except AttributeError:
167            # No manager defined - testing and standalone runs
168            pass
169
170
Note: See TracBrowser for help on using the repository browser.