Changeset ecc5d043 in sasview for src/sas/qtgui/Perspectives/Fitting


Ignore:
Timestamp:
Nov 16, 2018 3:05:20 AM (5 years ago)
Author:
Piotr Rozyczko <piotr.rozyczko@…>
Branches:
ESS_GUI, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
Children:
09e0c32
Parents:
baeac95
Message:

Reworked the complex constraint functionality SASVIEW-1019

Location:
src/sas/qtgui/Perspectives/Fitting
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • src/sas/qtgui/Perspectives/Fitting/ComplexConstraint.py

    r305114c recc5d043  
    1515from sas.qtgui.Perspectives.Fitting import FittingUtilities 
    1616import sas.qtgui.Utilities.GuiUtils as GuiUtils 
     17from sas.qtgui.Perspectives.Fitting.Constraint import Constraint 
     18 
    1719ALLOWED_OPERATORS = ['=','<','>','>=','<='] 
    1820 
     
    2123 
    2224class ComplexConstraint(QtWidgets.QDialog, Ui_ComplexConstraintUI): 
     25    constraintReadySignal = QtCore.pyqtSignal(tuple) 
    2326    def __init__(self, parent=None, tabs=None): 
    2427        super(ComplexConstraint, self).__init__() 
     
    3235        self.tab_names = None 
    3336        self.operator = '=' 
     37        self._constraint = Constraint() 
    3438 
    3539        self.warning = self.lblWarning.text() 
     
    5357        Signals from various elements 
    5458        """ 
    55         self.cmdOK.clicked.connect(self.accept) 
     59        self.cmdOK.clicked.connect(self.onApply) 
    5660        self.cmdHelp.clicked.connect(self.onHelp) 
    5761        self.cmdRevert.clicked.connect(self.onRevert) 
     
    6973        self.txtName2.setText(self.tab_names[1]) 
    7074 
    71         # Show only parameters not already constrained 
     75        self.setupParamWidgets() 
     76 
     77        # Add menu to the Apply button 
     78        all_menu   = QtWidgets.QMenu() 
     79        self.actionAddAll = QtWidgets.QAction(self) 
     80        self.actionAddAll.setObjectName("actionAddAll") 
     81        self.actionAddAll.setText(QtCore.QCoreApplication.translate("self", "Add all")) 
     82        ttip = "Add constraints between all identically named parameters in both fitpages" 
     83        self.actionAddAll.setToolTip(ttip) 
     84        #self.actionAddAll.setStatusTip(ttip) 
     85        self.actionAddAll.triggered.connect(self.onSetAll) 
     86        all_menu.addAction(self.actionAddAll) 
     87        # https://bugreports.qt.io/browse/QTBUG-13663 
     88        all_menu.setToolTipsVisible(True) 
     89        self.cmdOK.setMenu(all_menu) 
     90 
     91    def setupParamWidgets(self): 
     92        """ 
     93        Fill out comboboxes and set labels with non-constrained parameters 
     94        """ 
    7295        self.cbParam1.clear() 
    73         items = [param for i,param in enumerate(self.params[0]) if not self.tabs[0].rowHasConstraint(i)] 
    74         self.cbParam1.addItems(items) 
     96        items1 = [param for param in self.params[0] if not self.tabs[0].paramHasConstraint(param)] 
     97        self.cbParam1.addItems(items1) 
     98 
     99        # M2 doesn't have to be non-constrained 
    75100        self.cbParam2.clear() 
    76         items = [param for i,param in enumerate(self.params[1]) if not self.tabs[1].rowHasConstraint(i)] 
    77         self.cbParam2.addItems(items) 
     101        #items2 = [param for param in self.params[1] if not self.tabs[1].paramHasConstraint(param)] 
     102        items2 = [param for param in self.params[1]] 
     103        self.cbParam2.addItems(items2) 
    78104 
    79105        self.txtParam.setText(self.tab_names[0] + ":" + self.cbParam1.currentText()) 
     
    84110 
    85111        self.txtConstraint.setText(self.tab_names[1]+"."+self.cbParam2.currentText()) 
     112 
     113        # disable Apply if no parameters available 
     114        if len(items1)==0: 
     115            self.cmdOK.setEnabled(False) 
     116            txt = "No parameters in model "+self.tab_names[0] +\ 
     117                " are available for constraining." 
     118            self.lblWarning.setText(txt) 
     119        else: 
     120            self.cmdOK.setEnabled(True) 
     121            txt = "" 
     122            self.lblWarning.setText(txt) 
    86123 
    87124    def setupTooltip(self): 
     
    144181 
    145182        # Original indices 
     183        index2 = index2 if index2 >= 0 else 0 
     184        index1 = index1 if index1 >= 0 else 0 
    146185        self.cbParam1.setCurrentIndex(index2) 
    147186        self.cbParam2.setCurrentIndex(index1) 
     
    205244    def constraint(self): 
    206245        """ 
    207         Return the generated constraint as tuple (model1, param1, operator, constraint) 
    208         """ 
    209         return (self.txtName1.text(), self.cbParam1.currentText(), self.cbOperator.currentText(), self.txtConstraint.text()) 
     246        Return the generated constraint 
     247        """ 
     248        param = self.cbParam1.currentText() 
     249        value = self.cbParam2.currentText() 
     250        func = self.txtConstraint.text() 
     251        value_ex = self.txtName2.text() + "." + self.cbParam2.currentText() 
     252        model1 = self.txtName1.text() 
     253        operator = self.cbOperator.currentText() 
     254 
     255        con = Constraint(self, 
     256                         param=param, 
     257                         value=value, 
     258                         func=func, 
     259                         value_ex=value_ex, 
     260                         operator=operator) 
     261 
     262        return (model1, con) 
     263 
     264    def onApply(self): 
     265        """ 
     266        Respond to Add constraint action. 
     267        Send a signal that the constraint is ready to be applied 
     268        """ 
     269        cons_tuple = self.constraint() 
     270        self.constraintReadySignal.emit(cons_tuple) 
     271        # reload the comboboxes 
     272        self.setupParamWidgets() 
     273 
     274    def onSetAll(self): 
     275        """ 
     276        Set constraints on all identically named parameters between two fitpages 
     277        """ 
     278        # loop over parameters in constrained model 
     279        items1 = [param for param in self.params[0] if not self.tabs[0].paramHasConstraint(param)] 
     280        #items2 = [param for param in self.params[1] if not self.tabs[1].paramHasConstraint(i)] 
     281        items2 = self.params[1] 
     282        for item in items1: 
     283            if item not in items2: continue 
     284            param = item 
     285            value = item 
     286            func = self.txtName2.text() + "." + param 
     287            value_ex = self.txtName1.text() + "." + param 
     288            model1 = self.txtName1.text() 
     289            operator = self.cbOperator.currentText() 
     290 
     291            con = Constraint(self, 
     292                             param=param, 
     293                             value=value, 
     294                             func=func, 
     295                             value_ex=value_ex, 
     296                             operator=operator) 
     297 
     298            self.constraintReadySignal.emit((model1, con)) 
     299 
     300        # reload the comboboxes 
     301        self.setupParamWidgets() 
    210302 
    211303    def onHelp(self): 
  • src/sas/qtgui/Perspectives/Fitting/Constraint.py

    rbaeac95 recc5d043  
    55    hence made into a class. 
    66    """ 
    7     def __init__(self, parent=None, param=None, value=0.0, min=None, max=None, func=None, value_ex=None): 
     7    def __init__(self, parent=None, param=None, value=0.0, 
     8                 min=None, max=None, func=None, value_ex=None, 
     9                 operator="="): 
    810        self._value = value 
    911        self._param = param 
     
    1315        self._min = min 
    1416        self._max = max 
     17        self._operator = operator 
    1518 
    1619    @property 
     
    6972        self._max = val 
    7073 
     74    @property 
     75    def operator(self): 
     76        # operator to use for constraint 
     77        return self._operator 
     78 
     79    @operator.setter 
     80    def operator(self, val): 
     81        self._operator = val 
     82 
  • src/sas/qtgui/Perspectives/Fitting/ConstraintWidget.py

    re5ae812 recc5d043  
    2222    Constraints Dialog to select the desired parameter/model constraints. 
    2323    """ 
    24  
     24    fitCompleteSignal = QtCore.pyqtSignal(tuple) 
    2525    def __init__(self, parent=None): 
    2626        super(ConstraintWidget, self).__init__() 
     
    9393        self.cmdFit.clicked.connect(self.onFit) 
    9494        self.cmdHelp.clicked.connect(self.onHelp) 
     95        self.cmdAdd.clicked.connect(self.showMultiConstraint) 
    9596        self.chkChain.toggled.connect(self.onChainFit) 
    9697 
     
    100101        self.tblConstraints.cellChanged.connect(self.onConstraintChange) 
    101102 
     103        # Internal signals 
     104        self.fitCompleteSignal.connect(self.fitComplete) 
     105 
    102106        # External signals 
    103107        self.parent.tabsModifiedSignal.connect(self.initializeFitList) 
     
    156160        fitter = Fit() 
    157161        fitter.fitter_id = self.page_id 
    158  
    159         # Notify the parent about fitting started 
    160         self.parent.fittingStartedSignal.emit(tabs_to_fit) 
    161162 
    162163        # prepare fitting problems for each tab 
     
    177178        except ValueError: 
    178179            # No parameters selected in one of the tabs 
    179             no_params_msg = "Fitting can not be performed.\n" +\ 
     180            no_params_msg = "Fitting cannot be performed.\n" +\ 
    180181                            "Not all tabs chosen for fitting have parameters selected for fitting." 
    181182            QtWidgets.QMessageBox.warning(self, 
     
    200201        batch_inputs = {} 
    201202        batch_outputs = {} 
     203 
     204        # Notify the parent about fitting started 
     205        self.parent.fittingStartedSignal.emit(tabs_to_fit) 
    202206 
    203207        # new fit thread object 
     
    292296        """ 
    293297        item = self.tblConstraints.item(row, column) 
    294         if column == 0: 
    295             # Update the tabs for fitting list 
    296             constraint = self.available_constraints[row] 
    297             constraint.active = (item.checkState() == QtCore.Qt.Checked) 
     298        if column != 0: return 
     299        # Update the tabs for fitting list 
     300        constraint = self.available_constraints[row] 
     301        constraint.active = (item.checkState() == QtCore.Qt.Checked) 
     302        # Update the constraint formula 
     303        constraint = self.available_constraints[row] 
     304        function = item.text() 
     305        # remove anything left of '=' to get the constraint 
     306        function = function[function.index('=')+1:] 
     307        # No check on function here - trust the user (R) 
     308        if function != constraint.func: 
     309            #from sas.sascalc.fit.expression import compile_constraints 
     310            constraint.func = function 
    298311 
    299312    def onTabCellEntered(self, row, column): 
     
    307320 
    308321    def onFitComplete(self, result): 
     322        """ 
     323        Send the fit complete signal to main thread 
     324        """ 
     325        self.fitCompleteSignal.emit(result) 
     326 
     327    def fitComplete(self, result): 
    309328        """ 
    310329        Respond to the successful fit complete signal 
     
    383402        msg = "Fitting failed: %s s.\n" % reason 
    384403        self.parent.communicate.statusBarUpdateSignal.emit(msg) 
    385   
     404 
    386405    def isTabImportable(self, tab): 
    387406        """ 
     
    596615            # Show the text in the constraint table 
    597616            item = self.uneditableItem(label) 
     617            item = QtWidgets.QTableWidgetItem(label) 
    598618            item.setFlags(item.flags() ^ QtCore.Qt.ItemIsUserCheckable) 
    599619            item.setCheckState(QtCore.Qt.Checked) 
     
    664684        return None 
    665685 
     686    def onAcceptConstraint(self, con_tuple): 
     687        """ 
     688        Receive constraint tuple from the ComplexConstraint dialog and adds contraint 
     689        """ 
     690        #"M1, M2, M3" etc 
     691        model_name, constraint = con_tuple 
     692        constrained_tab = self.getObjectByName(model_name) 
     693        if constrained_tab is None: 
     694            return 
     695 
     696        # Find the constrained parameter row 
     697        constrained_row = constrained_tab.getRowFromName(constraint.param) 
     698 
     699        # Update the tab 
     700        constrained_tab.addConstraintToRow(constraint, constrained_row) 
     701 
    666702    def showMultiConstraint(self): 
    667703        """ 
     
    669705        """ 
    670706        selected_rows = self.selectedParameters(self.tblTabList) 
    671         assert(len(selected_rows)==2) 
     707        if len(selected_rows)!=2: 
     708            msg = "Please select two fit pages from the Source Choice table." 
     709            msgbox = QtWidgets.QMessageBox(self.parent) 
     710            msgbox.setIcon(QtWidgets.QMessageBox.Warning) 
     711            msgbox.setText(msg) 
     712            msgbox.setWindowTitle("2 fit page constraints") 
     713            retval = msgbox.exec_() 
     714            return 
    672715 
    673716        tab_list = [ObjectLibrary.getObject(self.tblTabList.item(s, 0).data(0)) for s in selected_rows] 
    674717        # Create and display the widget for param1 and param2 
    675718        cc_widget = ComplexConstraint(self, tabs=tab_list) 
     719        cc_widget.constraintReadySignal.connect(self.onAcceptConstraint) 
     720 
    676721        if cc_widget.exec_() != QtWidgets.QDialog.Accepted: 
    677722            return 
    678  
    679         constraint = Constraint() 
    680         model1, param1, operator, constraint_text = cc_widget.constraint() 
    681  
    682         constraint.func = constraint_text 
    683         # param1 is the parameter we're constraining 
    684         constraint.param = param1 
    685  
    686         # Find the right tab 
    687         constrained_tab = self.getObjectByName(model1) 
    688         if constrained_tab is None: 
    689             return 
    690  
    691         # Find the constrained parameter row 
    692         constrained_row = constrained_tab.getRowFromName(param1) 
    693  
    694         # Update the tab 
    695         constrained_tab.addConstraintToRow(constraint, constrained_row) 
    696723 
    697724    def getFitPage(self): 
  • src/sas/qtgui/Perspectives/Fitting/FittingWidget.py

    rbaeac95 recc5d043  
    903903        except AttributeError: 
    904904            return None 
     905 
     906    def allParamNames(self): 
     907        """ 
     908        Returns a list of all parameter names defined on the current model 
     909        """ 
     910        all_params = self.kernel_module._model_info.parameters.kernel_parameters 
     911        all_param_names = [param.name for param in all_params] 
     912        # Assure scale and background are always included 
     913        if 'scale' not in all_param_names: 
     914            all_param_names.append('scale') 
     915        if 'background' not in all_param_names: 
     916            all_param_names.append('background') 
     917        return all_param_names 
     918 
     919    def paramHasConstraint(self, param=None): 
     920        """ 
     921        Finds out if the given parameter in the main model has a constraint child 
     922        """ 
     923        if param is None: return False 
     924        if param not in self.allParamNames(): return False 
     925 
     926        for row in range(self._model_model.rowCount()): 
     927            if self._model_model.item(row,0).text() != param: continue 
     928            return self.rowHasConstraint(row) 
     929 
     930        # nothing found 
     931        return False 
    905932 
    906933    def rowHasConstraint(self, row): 
  • src/sas/qtgui/Perspectives/Fitting/UI/ComplexConstraintUI.ui

    r1738173 recc5d043  
    77    <x>0</x> 
    88    <y>0</y> 
    9     <width>367</width> 
    10     <height>199</height> 
     9    <width>478</width> 
     10    <height>257</height> 
    1111   </rect> 
    1212  </property> 
     
    6969           </sizepolicy> 
    7070          </property> 
     71          <property name="sizeAdjustPolicy"> 
     72           <enum>QComboBox::AdjustToContents</enum> 
     73          </property> 
    7174          <item> 
    7275           <property name="text"> 
     
    100103           </sizepolicy> 
    101104          </property> 
     105          <property name="sizeAdjustPolicy"> 
     106           <enum>QComboBox::AdjustToContents</enum> 
     107          </property> 
    102108          <item> 
    103109           <property name="text"> 
     
    161167       <property name="sizeHint" stdset="0"> 
    162168        <size> 
    163          <width>40</width> 
     169         <width>88</width> 
    164170         <height>20</height> 
    165171        </size> 
     
    168174     </item> 
    169175     <item> 
    170       <widget class="QPushButton" name="cmdOK"> 
     176      <widget class="QToolButton" name="cmdOK"> 
     177       <property name="minimumSize"> 
     178        <size> 
     179         <width>93</width> 
     180         <height>28</height> 
     181        </size> 
     182       </property> 
     183       <property name="toolTip"> 
     184        <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Add the constraint as defined by the above expression.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
     185       </property> 
    171186       <property name="text"> 
    172         <string>OK</string> 
    173        </property> 
    174        <property name="default"> 
     187        <string>Add</string> 
     188       </property> 
     189       <property name="popupMode"> 
     190        <enum>QToolButton::MenuButtonPopup</enum> 
     191       </property> 
     192       <property name="toolButtonStyle"> 
     193        <enum>Qt::ToolButtonTextOnly</enum> 
     194       </property> 
     195       <property name="autoRaise"> 
    175196        <bool>false</bool> 
    176197       </property> 
     198       <property name="arrowType"> 
     199        <enum>Qt::DownArrow</enum> 
     200       </property> 
    177201      </widget> 
    178202     </item> 
    179203     <item> 
    180204      <widget class="QPushButton" name="cmdCancel"> 
     205       <property name="toolTip"> 
     206        <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Close the window.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
     207       </property> 
    181208       <property name="text"> 
    182         <string>Cancel</string> 
     209        <string>Close</string> 
    183210       </property> 
    184211      </widget> 
     
    197224 <resources/> 
    198225 <connections> 
    199   <connection> 
    200    <sender>cmdOK</sender> 
    201    <signal>clicked()</signal> 
    202    <receiver>ComplexConstraintUI</receiver> 
    203    <slot>accept()</slot> 
    204    <hints> 
    205     <hint type="sourcelabel"> 
    206      <x>149</x> 
    207      <y>144</y> 
    208     </hint> 
    209     <hint type="destinationlabel"> 
    210      <x>179</x> 
    211      <y>82</y> 
    212     </hint> 
    213    </hints> 
    214   </connection> 
    215226  <connection> 
    216227   <sender>cmdCancel</sender> 
  • src/sas/qtgui/Perspectives/Fitting/UI/ConstraintWidgetUI.ui

    r91ad45c recc5d043  
    77    <x>0</x> 
    88    <y>0</y> 
    9     <width>428</width> 
    10     <height>457</height> 
     9    <width>597</width> 
     10    <height>607</height> 
    1111   </rect> 
    1212  </property> 
     
    1414   <string>Constrained and Simultaneous Fit</string> 
    1515  </property> 
    16   <layout class="QGridLayout" name="gridLayout"> 
     16  <layout class="QGridLayout" name="gridLayout_2"> 
    1717   <item row="0" column="0"> 
    1818    <widget class="QGroupBox" name="groupBox"> 
     
    8989      <string>Constraints</string> 
    9090     </property> 
    91      <layout class="QGridLayout" name="gridLayout_2"> 
     91     <layout class="QGridLayout" name="gridLayout"> 
    9292      <item row="0" column="0"> 
    93        <layout class="QHBoxLayout" name="horizontalLayout"> 
    94         <item> 
    95          <widget class="QLabel" name="label"> 
    96           <property name="text"> 
    97            <string>Special cases</string> 
    98           </property> 
    99          </widget> 
    100         </item> 
    101         <item> 
    102          <widget class="QComboBox" name="cbCases"> 
     93       <layout class="QHBoxLayout" name="horizontalLayout_4"> 
     94        <item> 
     95         <layout class="QHBoxLayout" name="horizontalLayout"> 
    10396          <item> 
    104            <property name="text"> 
    105             <string>None</string> 
    106            </property> 
     97           <widget class="QLabel" name="label"> 
     98            <property name="text"> 
     99             <string>Special cases</string> 
     100            </property> 
     101           </widget> 
    107102          </item> 
     103          <item> 
     104           <widget class="QComboBox" name="cbCases"> 
     105            <item> 
     106             <property name="text"> 
     107              <string>None</string> 
     108             </property> 
     109            </item> 
     110           </widget> 
     111          </item> 
     112         </layout> 
     113        </item> 
     114        <item> 
     115         <spacer name="horizontalSpacer_3"> 
     116          <property name="orientation"> 
     117           <enum>Qt::Horizontal</enum> 
     118          </property> 
     119          <property name="sizeHint" stdset="0"> 
     120           <size> 
     121            <width>40</width> 
     122            <height>20</height> 
     123           </size> 
     124          </property> 
     125         </spacer> 
     126        </item> 
     127        <item> 
     128         <widget class="QPushButton" name="cmdAdd"> 
     129          <property name="toolTip"> 
     130           <string>Define constraints between two fit pages.</string> 
     131          </property> 
     132          <property name="text"> 
     133           <string>Add constraints</string> 
     134          </property> 
    108135         </widget> 
    109136        </item> 
     
    160187        </size> 
    161188       </property> 
     189       <property name="toolTip"> 
     190        <string>Perform simultaneous fitting of selected fit pages.</string> 
     191       </property> 
    162192       <property name="text"> 
    163193        <string>Fit</string> 
     
    179209        </size> 
    180210       </property> 
     211       <property name="toolTip"> 
     212        <string>Display help on constrained and simultaneous fitting.</string> 
     213       </property> 
    181214       <property name="text"> 
    182215        <string>Help</string> 
  • src/sas/qtgui/Perspectives/Fitting/UI/MultiConstraintUI.ui

    r1738173 recc5d043  
    1010    <x>0</x> 
    1111    <y>0</y> 
    12     <width>369</width> 
    13     <height>201</height> 
     12    <width>435</width> 
     13    <height>233</height> 
    1414   </rect> 
    1515  </property> 
     
    184184 <connections> 
    185185  <connection> 
     186   <sender>cmdCancel</sender> 
     187   <signal>clicked()</signal> 
     188   <receiver>MultiConstraintUI</receiver> 
     189   <slot>reject()</slot> 
     190   <hints> 
     191    <hint type="sourcelabel"> 
     192     <x>187</x> 
     193     <y>121</y> 
     194    </hint> 
     195    <hint type="destinationlabel"> 
     196     <x>184</x> 
     197     <y>71</y> 
     198    </hint> 
     199   </hints> 
     200  </connection> 
     201  <connection> 
    186202   <sender>cmdOK</sender> 
    187203   <signal>clicked()</signal> 
     
    199215   </hints> 
    200216  </connection> 
    201   <connection> 
    202    <sender>cmdCancel</sender> 
    203    <signal>clicked()</signal> 
    204    <receiver>MultiConstraintUI</receiver> 
    205    <slot>reject()</slot> 
    206    <hints> 
    207     <hint type="sourcelabel"> 
    208      <x>187</x> 
    209      <y>121</y> 
    210     </hint> 
    211     <hint type="destinationlabel"> 
    212      <x>184</x> 
    213      <y>71</y> 
    214     </hint> 
    215    </hints> 
    216   </connection> 
    217217 </connections> 
    218218</ui> 
  • src/sas/qtgui/Perspectives/Fitting/UnitTesting/ComplexConstraintTest.py

    r2eeda93 recc5d043  
    132132        """ 
    133133        # default data 
    134         self.assertEqual(self.widget.constraint(), ('M1', 'scale', '=', 'M1.scale')) 
     134        c = self.widget.constraint() 
     135        self.assertEqual(c[0], 'M1') 
     136        self.assertEqual(c[1].func, 'M1.scale') 
    135137 
    136138        # Change parameter and operand 
    137139        self.widget.cbOperator.setCurrentIndex(3) 
    138         self.widget.cbParam1.setCurrentIndex(3) 
    139         self.assertEqual(self.widget.constraint(), ('M1', 'bjerrum_length', '>=', 'M1.scale')) 
     140        self.widget.cbParam2.setCurrentIndex(3) 
     141        c = self.widget.constraint() 
     142        self.assertEqual(c[0], 'M1') 
     143        self.assertEqual(c[1].func, 'M1.sld_solvent') 
     144        self.assertEqual(c[1].operator, '>=') 
    140145 
    141146    def testOnHelp(self): 
Note: See TracChangeset for help on using the changeset viewer.