diff --git a/src/sas/qtgui/Perspectives/Fitting/ComplexConstraint.py b/src/sas/qtgui/Perspectives/Fitting/ComplexConstraint.py index ac973f5bf6..0d6c8541a9 100644 --- a/src/sas/qtgui/Perspectives/Fitting/ComplexConstraint.py +++ b/src/sas/qtgui/Perspectives/Fitting/ComplexConstraint.py @@ -88,7 +88,6 @@ def setupWidgets(self): self.setupParamWidgets() - self.setupMenu() def setupMenu(self): @@ -110,10 +109,10 @@ def setupParamWidgets(self): # Populate the left combobox parameter arbitrarily with the parameters # from the first tab if `All` option is selected if self.cbModel1.currentText() == "All": - items1 = self.tabs[1].main_params_to_fit + items1 = self.tabs[1].main_params_to_fit + self.tabs[1].poly_params_to_fit else: tab_index1 = self.cbModel1.currentIndex() - items1 = self.tabs[tab_index1].main_params_to_fit + items1 = self.tabs[tab_index1].main_params_to_fit + self.tabs[tab_index1].poly_params_to_fit self.cbParam1.addItems(items1) # Show the previously selected parameter if available if previous_param1 in items1: @@ -126,6 +125,7 @@ def setupParamWidgets(self): self.cbParam2.clear() tab_index2 = self.cbModel2.currentIndex() items2 = [param for param in self.params[tab_index2] if not self.tabs[tab_index2].paramHasConstraint(param)] + self.cbParam2.addItems(items2) # Show the previously selected parameter if available if previous_param2 in items2: diff --git a/src/sas/qtgui/Perspectives/Fitting/Constraint.py b/src/sas/qtgui/Perspectives/Fitting/Constraint.py index b686363827..db1b83390f 100644 --- a/src/sas/qtgui/Perspectives/Fitting/Constraint.py +++ b/src/sas/qtgui/Perspectives/Fitting/Constraint.py @@ -14,6 +14,7 @@ def __init__(self, parent=None, param=None, value=0.0, self._min = min self._max = max self._operator = operator + self._model = None self.validate = True self.active = True @@ -81,3 +82,11 @@ def operator(self): def operator(self, val): self._operator = val + @property + def model(self): + # model this constraint originates from + return self._model + + @model.setter + def model(self, val): + self._model = val diff --git a/src/sas/qtgui/Perspectives/Fitting/ConstraintWidget.py b/src/sas/qtgui/Perspectives/Fitting/ConstraintWidget.py index fdcb33cb63..00a4208a1d 100644 --- a/src/sas/qtgui/Perspectives/Fitting/ConstraintWidget.py +++ b/src/sas/qtgui/Perspectives/Fitting/ConstraintWidget.py @@ -189,7 +189,7 @@ def initializeWidgets(self): # Single Fit is the default, so disable chainfit self.chkChain.setVisible(False) - # disabled constraint + # disabled constraint labels = ['Constraint'] self.tblConstraints.setColumnCount(len(labels)) self.tblConstraints.setHorizontalHeaderLabels(labels) @@ -399,6 +399,10 @@ def onHelp(self): help_location = tree_location + helpfile # OMG, really? Crawling up the object hierarchy... + # + # It's the top level that needs to do the show help. + # Perhaps better to address directly, but it does need to + # be that object. I don't like that the type is hidden. :LW self.parent.parent.showHelp(help_location) def onTabCellEdit(self, row, column): @@ -530,6 +534,7 @@ def onConstraintChange(self, row, column): return # Then check if the parameter is correctly defined with colons # separating model and parameter name + lhs, rhs = re.split(" *= *", item.data(0).strip(), 1) if ":" not in lhs: msg = ("Incorrect constrained parameter definition. Please use " @@ -565,6 +570,7 @@ def onConstraintChange(self, row, column): return new_function = rhs new_tab = self.available_tabs[new_model] + model_key = tab.getModelKey(param) # Make sure we are dealing with fit tabs assert isinstance(tab, FittingWidget) assert isinstance(new_tab, FittingWidget) @@ -573,12 +579,14 @@ def onConstraintChange(self, row, column): # Apply the new constraint constraint = Constraint(param=new_param, func=new_function, value_ex=new_model + "." + new_param) + model_key = tab.getModelKey(new_param) new_tab.addConstraintToRow(constraint=constraint, - row=tab.getRowFromName(new_param)) + row=tab.getRowFromName(new_param), model_key=model_key) # If the constraint is valid and we are changing model or # parameter, delete the old constraint if (self.constraint_accepted and new_model != model or new_param != param): + print(1, param) tab.deleteConstraintOnParameter(param) # Reload the view self.initializeFitList() @@ -593,9 +601,9 @@ def onConstraintChange(self, row, column): font.setItalic(True) brush = QtGui.QBrush(QtGui.QColor('blue')) tab.modifyViewOnRow(tab.getRowFromName(new_param), font=font, - brush=brush) + brush=brush, model_key=model_key) else: - tab.modifyViewOnRow(tab.getRowFromName(new_param)) + tab.modifyViewOnRow(tab.getRowFromName(new_param), model_key=model_key) # reload the view so the user gets a consistent feedback on the # constraints self.initializeFitList() @@ -890,6 +898,7 @@ def deleteConstraint(self):#, row): moniker = constraint[:constraint.index(':')] param = constraint[constraint.index(':')+1:constraint.index('=')].strip() tab = self.available_tabs[moniker] + print(2, param) tab.deleteConstraintOnParameter(param) # Constraints removed - refresh the table widget @@ -903,14 +912,17 @@ def uneditableItem(self, data=""): item.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) return item - def updateFitLine(self, tab): + def updateFitLine(self, tab, model_key="standard"): """ Update a single line of the table widget with tab info """ fit_page = ObjectLibrary.getObject(tab) model = fit_page.kernel_module + if model is None: + logging.warning("No model selected") return + tab_name = tab model_name = model.id moniker = model.name @@ -947,19 +959,25 @@ def updateFitLine(self, tab): self.tblTabList.blockSignals(False) # Check if any constraints present in tab - active_constraint_names = fit_page.getComplexConstraintsForModel() - constraint_names = fit_page.getFullConstraintNameListForModel() - constraints = fit_page.getConstraintObjectsForModel() + constraint_names = fit_page.getComplexConstraintsForAllModels() + constraints = fit_page.getConstraintObjectsForAllModels() + + # these three assignments need proper extension to all models, not just main model + active_constraint_names = fit_page.getComplexConstraintsForModel(model_key=model_key) + constraint_names = fit_page.getFullConstraintNameListForModel(model_key=model_key) + constraints = fit_page.getConstraintObjectsForModel(model_key=model_key) + if not constraints: return self.tblConstraints.setEnabled(True) self.tblConstraints.blockSignals(True) for constraint, constraint_name in zip(constraints, constraint_names): - # Ignore constraints that have no *func* attribute defined - if constraint.func is None: + if not constraint_name and len(constraint_name) < 2: + continue + if constraint_name[0] is None or constraint_name[1] is None: continue # Create the text for widget item - label = moniker + ":"+ constraint_name[0] + " = " + constraint_name[1] + label = moniker + ":" + constraint_name[0] + " = " + constraint_name[1] pos = self.tblConstraints.rowCount() self.available_constraints[pos] = constraint @@ -978,7 +996,7 @@ def updateFitLine(self, tab): self.tblConstraints.setItem(pos, 0, item) self.tblConstraints.blockSignals(False) - def initializeFitList(self): + def initializeFitList(self, row=0, model_key="standard"): """ Fill the list of model/data sets for fitting/constraining """ @@ -1017,7 +1035,7 @@ def initializeFitList(self): self._row_order = tabs for tab in tabs: - self.updateFitLine(tab) + self.updateFitLine(tab, model_key=model_key) self.updateSignalsFromTab(tab) # We have at least 1 fit page, allow fitting self.cmdFit.setEnabled(True) @@ -1084,14 +1102,16 @@ def onAcceptConstraint(self, con_tuple): # Find the constrained parameter row constrained_row = constrained_tab.getRowFromName(constraint.param) + model_key = constrained_tab.getModelKey(constraint.param) # Update the tab - constrained_tab.addConstraintToRow(constraint, constrained_row) + constrained_tab.addConstraintToRow(constraint, constrained_row, model_key=model_key) if not self.constraint_accepted: return # Select this parameter for adjusting/fitting - constrained_tab.changeCheckboxStatus(constrained_row, True) + # constrained_tab.selectCheckbox(constrained_row, model=model) + def showMultiConstraint(self): """ @@ -1211,4 +1231,5 @@ def uncheckConstraint(self, name): # deactivate the constraint tab = self.parent.getTabByName(name[:name.index(":")]) row = tab.getRowFromName(name[name.index(":") + 1:]) - tab.getConstraintForRow(row).active = False + model_key = tab.getModelKey(constraint) + tab.getConstraintForRow(row, model_key=model_key).active = False diff --git a/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py b/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py index 880fdd80ca..43be676848 100644 --- a/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py +++ b/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py @@ -206,9 +206,11 @@ def updateFromConstraints(self, constraint_dict): constraint.param = constraint_param[1] constraint.value_ex = constraint_param[2] constraint.validate = constraint_param[3] + model_key = tab.getModelKey(constraint) tab.addConstraintToRow(constraint=constraint, row=tab.getRowFromName( - constraint_param[1])) + constraint_param[1]), + model_key=model_key) def onParamSave(self): self.currentTab.onCopyToClipboard("Save") @@ -519,9 +521,9 @@ def getActiveConstraintList(self): constraints = [] for tab in self.getFitTabs(): tab_name = tab.modelName() - tab_constraints = tab.getConstraintsForModel() - constraints.extend((tab_name + "." + par, expr) - for par, expr in tab_constraints) + tab_constraints = tab.getConstraintsForAllModels() + constraints.extend((tab_name + "." + par, expr) for par, expr in tab_constraints) + return constraints def getSymbolDictForConstraints(self): diff --git a/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py b/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py index 52769c9520..ba27b0c5b7 100644 --- a/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py +++ b/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py @@ -972,9 +972,15 @@ def isParamPolydisperse(param_name, kernel_params, is2D=False): """ Simple lookup for polydispersity for the given param name """ + # First, check if this is a polydisperse parameter directly + if '.width' in param_name: + return True + parameters = kernel_params.form_volume_parameters if is2D: parameters += kernel_params.orientation_parameters + + # Next, check if the parameter is included in para.polydisperse has_poly = False for param in parameters: if param.name==param_name and param.polydisperse: diff --git a/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py b/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py index df4652d555..12f0bff465 100644 --- a/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py +++ b/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py @@ -103,7 +103,7 @@ class FittingWidget(QtWidgets.QWidget, Ui_FittingWidgetUI): """ Main widget for selecting form and structure factor models """ - constraintAddedSignal = QtCore.pyqtSignal(list) + constraintAddedSignal = QtCore.pyqtSignal(list, str) newModelSignal = QtCore.pyqtSignal() fittingFinishedSignal = QtCore.pyqtSignal(tuple) batchFittingFinishedSignal = QtCore.pyqtSignal(tuple) @@ -255,6 +255,12 @@ def initializeGlobals(self): self._num_shell_params = 0 # Dictionary of {model name: model class} for the current category self.models = {} + # Dictionary of QModels + self.model_dict = {} + self.lst_dict = {} + self.tabToList = {} # tab_id -> list widget + self.tabToKey = {} # tab_id -> model key + # Parameters to fit self.main_params_to_fit = [] self.poly_params_to_fit = [] @@ -356,7 +362,7 @@ def initializeWidgets(self): # Magnetic angles explained in one picture self.magneticAnglesWidget = QtWidgets.QWidget() labl = QtWidgets.QLabel(self.magneticAnglesWidget) - pixmap = QtGui.QPixmap(GuiUtils.IMAGES_DIRECTORY_LOCATION + '/mag_vector.png') + pixmap = QtGui.QPixmap(GuiUtils.IMAGES_DIRECTORY_LOCATION + '/M_angles_pic.png') labl.setPixmap(pixmap) self.magneticAnglesWidget.setFixedSize(pixmap.width(), pixmap.height()) @@ -371,6 +377,22 @@ def initializeModels(self): self._poly_model = ToolTippedItemModel() self._magnet_model = ToolTippedItemModel() + self.model_dict["standard"] = self._model_model + self.model_dict["poly"] = self._poly_model + self.model_dict["magnet"] = self._magnet_model + + self.lst_dict["standard"] = self.lstParams + self.lst_dict["poly"] = self.lstPoly + self.lst_dict["magnet"] = self.lstMagnetic + + self.tabToList[0] = self.lstParams + self.tabToList[3] = self.lstPoly + self.tabToList[4] = self.lstMagnetic + + self.tabToKey[0] = "standard" + self.tabToKey[3] = "poly" + self.tabToKey[4] = "magnet" + # Param model displayed in param list self.lstParams.setModel(self._model_model) self.readCategoryInfo() @@ -426,6 +448,10 @@ def initializeModels(self): self.lstPoly.itemDelegate().combo_updated.connect(self.onPolyComboIndexChange) self.lstPoly.itemDelegate().filename_updated.connect(self.onPolyFilenameChange) + self.lstPoly.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.lstPoly.customContextMenuRequested.connect(self.showModelContextMenu) + self.lstPoly.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False) + # Magnetism model displayed in magnetism list self.lstMagnetic.setModel(self._magnet_model) self.setMagneticModel() @@ -603,6 +629,7 @@ def initializeSignals(self): self.lstParams.installEventFilter(self) self.lstPoly.installEventFilter(self) self.lstMagnetic.installEventFilter(self) + self.lstPoly.selectionModel().selectionChanged.connect(self.onSelectionChanged) # Local signals self.batchFittingFinishedSignal.connect(self.batchFitComplete) @@ -659,11 +686,13 @@ def showModelContextMenu(self, position): When clicked on parameter(s): fitting/constraints options When clicked on white space: model description """ - rows = [s.row() for s in self.lstParams.selectionModel().selectedRows() + # See which model we're dealing with by looking at the tab id + current_list = self.tabToList[self.tabFitting.currentIndex()] + rows = [s.row() for s in current_list.selectionModel().selectedRows() if self.isCheckable(s.row())] menu = self.showModelDescription() if not rows else self.modelContextMenu(rows) try: - menu.exec_(self.lstParams.viewport().mapToGlobal(position)) + menu.exec_(current_list.viewport().mapToGlobal(position)) except AttributeError as ex: logger.error("Error generating context menu: %s" % ex) return @@ -676,11 +705,13 @@ def modelContextMenu(self, rows): num_rows = len(rows) if num_rows < 1: return menu + current_list = self.tabToList[self.tabFitting.currentIndex()] + model_key = self.tabToKey[self.tabFitting.currentIndex()] # Select for fitting param_string = "parameter " if num_rows == 1 else "parameters " to_string = "to its current value" if num_rows == 1 else "to their current values" - has_constraints = any([self.rowHasConstraint(i) for i in rows]) - has_real_constraints = any([self.rowHasActiveConstraint(i) for i in rows]) + has_constraints = any([self.rowHasConstraint(i, model_key=model_key) for i in rows]) + has_real_constraints = any([self.rowHasActiveConstraint(i, model_key=model_key) for i in rows]) self.actionSelect = QtWidgets.QAction(self) self.actionSelect.setObjectName("actionSelect") @@ -718,32 +749,38 @@ def modelContextMenu(self, rows): menu.addAction(self.actionRemoveConstraint) if num_rows == 1 and has_real_constraints: menu.addAction(self.actionEditConstraint) - #if num_rows == 1: - # menu.addAction(self.actionEditConstraint) else: - menu.addAction(self.actionConstrain) if num_rows == 2: menu.addAction(self.actionMutualMultiConstrain) + else: + menu.addAction(self.actionConstrain) # Define the callbacks self.actionConstrain.triggered.connect(self.addSimpleConstraint) self.actionRemoveConstraint.triggered.connect(self.deleteConstraint) self.actionEditConstraint.triggered.connect(self.editConstraint) - self.actionMutualMultiConstrain.triggered.connect(self.showMultiConstraint) + self.actionMutualMultiConstrain.triggered.connect(lambda: self.showMultiConstraint(current_list=current_list)) self.actionSelect.triggered.connect(self.selectParameters) self.actionDeselect.triggered.connect(self.deselectParameters) return menu - def showMultiConstraint(self): + def showMultiConstraint(self, current_list=None): """ Show the constraint widget and receive the expression """ - selected_rows = self.lstParams.selectionModel().selectedRows() + if current_list is None: + current_list = self.lstParams + model = current_list.model() + for key, val in self.model_dict.items(): + if val == model: + model_key = key + + selected_rows = current_list.selectionModel().selectedRows() # There have to be only two rows selected. The caller takes care of that # but let's check the correctness. assert len(selected_rows) == 2 - params_list = [s.data() for s in selected_rows] + params_list = [s.data(role=QtCore.Qt.UserRole) for s in selected_rows] # Create and display the widget for param1 and param2 mc_widget = MultiConstraint(self, params=params_list) # Check if any of the parameters are polydisperse @@ -777,30 +814,101 @@ def showMultiConstraint(self): constraint.validate = mc_widget.validate # Create a new item and add the Constraint object as a child - self.addConstraintToRow(constraint=constraint, row=row) + self.addConstraintToRow(constraint=constraint, row=row, model_key=model_key) + + + def getModelKeyFromName(self, name): + """ + Given parameter name get the model index. + """ + if name in self.getParamNamesMain(): + return "standard" + elif name in self.getParamNamesPoly(): + return "poly" + elif name in self.getParamNamesMagnet(): + return "magnet" + else: + return "standard" def getRowFromName(self, name): """ - Given parameter name get the row number in self._model_model + Given parameter name get the row number in a model. + The model is the main _model_model by default """ - for row in range(self._model_model.rowCount()): - row_name = self._model_model.item(row).text() + model_key = self.getModelKeyFromName(name) + model = self.model_dict[model_key] + + # special case for polydisp + if model == self._poly_model: + name = self.polyParamToName(name) + for row in range(model.rowCount()): + row_name = model.item(row).text() if row_name == name: return row return None def getParamNames(self): """ - Return list of all parameters for the current model + Return list of all active parameters for the current model + """ + main_model_params = self.getParamNamesMain() + poly_model_params = self.getParamNamesPoly() + # magnet_model_params = self.getParamNamesMagnet() + return main_model_params + poly_model_params # + magnet_model_params + + def getParamNamesMain(self): + """ + Return list of main parameters for the current model + """ + main_model_params = [self._model_model.item(row).text() + for row in range(self._model_model.rowCount()) + if self.isCheckable(row, model_key="standard")] + return main_model_params + + def getParamNamesPoly(self): + """ + Return list of polydisperse parameters for the current model """ - return [self._model_model.item(row).text() - for row in range(self._model_model.rowCount()) - if self.isCheckable(row)] + if not self.chkPolydispersity.isChecked(): + return [] + poly_model_params = [self.polyNameToParam(self._poly_model.item(row).text()) + for row in range(self._poly_model.rowCount()) + if self.chkPolydispersity.isChecked() and + self.isCheckable(row, model_key="poly")] + return poly_model_params - def modifyViewOnRow(self, row, font=None, brush=None): + def getParamNamesMagnet(self): + """ + Return list of magnetic parameters for the current model + """ + if not self.chkMagnetism.isChecked(): + return [] + magnetic_model_params = [self._magnet_model.item(row).text() + for row in range(self._magnet_model.rowCount()) + if self.isCheckable(row, model_key="magnet")] + return magnetic_model_params + + def polyParamToName(self, param_name): + """ + Translate polydisperse parameter name into QTable representation + """ + param_name = param_name.replace('.width', '') + param_name = 'Distribution of ' + param_name + return param_name + + def polyNameToParam(self, param_name): + """ + Translate polydisperse parameter name into QTable representation + """ + param_name = param_name.replace('Distribution of ','') + param_name += '.width' + return param_name + + def modifyViewOnRow(self, row, font=None, brush=None, model_key="standard"): """ Change how the given row of the main model is shown """ + model = self.model_dict[model_key] fields_enabled = False if font is None: font = QtGui.QFont() @@ -808,25 +916,38 @@ def modifyViewOnRow(self, row, font=None, brush=None): if brush is None: brush = QtGui.QBrush() fields_enabled = True - self._model_model.blockSignals(True) + model.blockSignals(True) # Modify font and foreground of affected rows - for column in range(0, self._model_model.columnCount()): - self._model_model.item(row, column).setForeground(brush) - self._model_model.item(row, column).setFont(font) + for column in range(0, model.columnCount()): + model.item(row, column).setForeground(brush) + model.item(row, column).setFont(font) # Allow the user to interact or not with the fields depending on # whether the parameter is constrained or not - self._model_model.item(row, column).setEditable(fields_enabled) + model.item(row, column).setEditable(fields_enabled) # Force checkbox selection when parameter is constrained and disable # checkbox interaction - if not fields_enabled and self._model_model.item(row, 0).isCheckable(): - self._model_model.item(row, 0).setCheckState(2) - self._model_model.item(row, 0).setEnabled(False) + if not fields_enabled and model.item(row, 0).isCheckable(): + model.item(row, 0).setCheckState(2) + model.item(row, 0).setEnabled(False) else: # Enable checkbox interaction - self._model_model.item(row, 0).setEnabled(True) - self._model_model.blockSignals(False) + model.item(row, 0).setEnabled(True) + model.blockSignals(False) + + def getModelKey(self, constraint): + """ + Given parameter name get the model index. + """ + if constraint in self.getParamNamesMain(): + return "standard" + elif constraint in self.getParamNamesPoly(): + return "poly" + elif constraint in self.getParamNamesMagnet(): + return "magnet" + else: + return None - def addConstraintToRow(self, constraint=None, row=0): + def addConstraintToRow(self, constraint=None, row=0, model_key="standard"): """ Adds the constraint object to requested row. The constraint is first checked for errors, and a message box interrupting flow is @@ -834,8 +955,9 @@ def addConstraintToRow(self, constraint=None, row=0): """ # Create a new item and add the Constraint object as a child assert isinstance(constraint, Constraint) - assert 0 <= row <= self._model_model.rowCount() - assert self.isCheckable(row) + model = self.model_dict[model_key] + assert 0 <= row <= model.rowCount() + assert self.isCheckable(row, model_key=model_key) # Error checking # First, get a list of constraints and symbols @@ -860,19 +982,20 @@ def addConstraintToRow(self, constraint=None, row=0): # constraint tab that the constraint was not accepted constraint_tab.constraint_accepted = False return + item = QtGui.QStandardItem() item.setData(constraint) - self._model_model.item(row, 1).setChild(0, item) + model.item(row, 1).setChild(0, item) # Set min/max to the value constrained - self.constraintAddedSignal.emit([row]) - # Show visual hints for the constraint + self.constraintAddedSignal.emit([row], model_key) + # Show visual hints for the coself.constraintAddedSignal.emit([row], model_key)nstraint font = QtGui.QFont() font.setItalic(True) brush = QtGui.QBrush(QtGui.QColor('blue')) - self.modifyViewOnRow(row, font=font, brush=brush) + self.modifyViewOnRow(row, font=font, brush=brush, model_key=model_key) # update the main parameter list so the constrained parameter gets # updated when fitting - self.checkboxSelected(self._model_model.item(row, 0)) + self.checkboxSelected(model.item(row, 0), model_key=model_key) self.communicate.statusBarUpdateSignal.emit('Constraint added') if constraint_tab: # Set the constraint_accepted flag to True to inform the @@ -883,45 +1006,50 @@ def addSimpleConstraint(self): """ Adds a constraint on a single parameter. """ + model_key = self.tabToKey[self.tabFitting.currentIndex()] + model = self.model_dict[model_key] min_col = self.lstParams.itemDelegate().param_min max_col = self.lstParams.itemDelegate().param_max - for row in self.selectedParameters(): - assert(self.isCheckable(row)) - param = self._model_model.item(row, 0).text() - value = self._model_model.item(row, 1).text() - min_t = self._model_model.item(row, min_col).text() - max_t = self._model_model.item(row, max_col).text() + for row in self.selectedParameters(model_key=model_key): + # assert(self.isCheckable(row, model_key=model_key)) + param = model.item(row, 0).text() + value = model.item(row, 1).text() + min_t = model.item(row, min_col).text() + max_t = model.item(row, max_col).text() # Create a Constraint object constraint = Constraint(param=param, value=value, min=min_t, max=max_t) # Create a new item and add the Constraint object as a child item = QtGui.QStandardItem() item.setData(constraint) - self._model_model.item(row, 1).setChild(0, item) + model.item(row, 1).setChild(0, item) # Assumed correctness from the validator value = float(value) # BUMPS calculates log(max-min) without any checks, so let's assign minor range min_v = value - (value/10000.0) max_v = value + (value/10000.0) # Set min/max to the value constrained - self._model_model.item(row, min_col).setText(str(min_v)) - self._model_model.item(row, max_col).setText(str(max_v)) - self.constraintAddedSignal.emit([row]) + model.item(row, min_col).setText(str(min_v)) + model.item(row, max_col).setText(str(max_v)) + self.constraintAddedSignal.emit([row], model_key) # Show visual hints for the constraint font = QtGui.QFont() font.setItalic(True) brush = QtGui.QBrush(QtGui.QColor('blue')) - self.modifyViewOnRow(row, font=font, brush=brush) + self.modifyViewOnRow(row, font=font, brush=brush, model_key=model_key) self.communicate.statusBarUpdateSignal.emit('Constraint added') def editConstraint(self): """ Delete constraints from selected parameters. """ - params_list = [s.data() for s in self.lstParams.selectionModel().selectedRows() - if self.isCheckable(s.row())] + current_list = self.tabToList[self.tabFitting.currentIndex()] + model_key = self.tabToKey[self.tabFitting.currentIndex()] + + params_list = [s.data(role=QtCore.Qt.UserRole) for s in current_list.selectionModel().selectedRows() + if self.isCheckable(s.row(), model_key=model_key)] assert len(params_list) == 1 - row = self.lstParams.selectionModel().selectedRows()[0].row() - constraint = self.getConstraintForRow(row) + row = current_list.selectionModel().selectedRows()[0].row() + constraint = self.getConstraintForRow(row, model_key=model_key) # Create and display the widget for param1 and param2 mc_widget = MultiConstraint(self, params=params_list, constraint=constraint) # Check if any of the parameters are polydisperse @@ -953,57 +1081,76 @@ def editConstraint(self): row = self.getRowFromName(constraint.param) # Create a new item and add the Constraint object as a child - self.addConstraintToRow(constraint=constraint, row=row) + self.addConstraintToRow(constraint=constraint, row=row, model_key=model_key) def deleteConstraint(self): """ Delete constraints from selected parameters. """ - params = [s.data() for s in self.lstParams.selectionModel().selectedRows() - if self.isCheckable(s.row())] + current_list = self.tabToList[self.tabFitting.currentIndex()] + model_key = self.tabToKey[self.tabFitting.currentIndex()] + params = [s.data(role=QtCore.Qt.UserRole) for s in current_list.selectionModel().selectedRows() + if self.isCheckable(s.row(), model_key=model_key)] for param in params: - self.deleteConstraintOnParameter(param=param) - def deleteConstraintOnParameter(self, param=None): + self.deleteConstraintOnParameter(param=param, model_key=model_key) + + def deleteConstraintOnParameter(self, param=None, model_key="standard"): """ Delete the constraint on model parameter 'param' """ - min_col = self.lstParams.itemDelegate().param_min - max_col = self.lstParams.itemDelegate().param_max - for row in range(self._model_model.rowCount()): - if not self.isCheckable(row): + param_list = self.lst_dict[model_key] + model = self.model_dict[model_key] + + for row in range(model.rowCount()): + if not self.isCheckable(row, model_key=model_key): continue - if not self.rowHasConstraint(row): + if not self.rowHasConstraint(row, model_key=model_key): continue # Get the Constraint object from of the model item - item = self._model_model.item(row, 1) - constraint = self.getConstraintForRow(row) + item = model.item(row, 1) + constraint = self.getConstraintForRow(row, model_key=model_key) if constraint is None: continue if not isinstance(constraint, Constraint): continue - if param and constraint.param != param: + if "Distribution of" in constraint.param: + cons_param = self.polyNameToParam(constraint.param) + else: + cons_param = constraint.param + if param and cons_param != param: continue # Now we got the right row. Delete the constraint and clean up # Retrieve old values and put them on the model if constraint.min is not None: - self._model_model.item(row, min_col).setText(constraint.min) + try: + min_col = param_list.itemDelegate().param_min + except AttributeError: + min_col = 2 + model.item(row, min_col).setText(constraint.min) if constraint.max is not None: - self._model_model.item(row, max_col).setText(constraint.max) + try: + max_col = param_list.itemDelegate().param_max + except AttributeError: + max_col = 3 + print("max_col", max_col) + model.item(row, max_col).setText(constraint.max) # Remove constraint item item.removeRow(0) - self.constraintAddedSignal.emit([row]) - self.modifyViewOnRow(row) + self.constraintAddedSignal.emit([row], model_key) + self.modifyViewOnRow(row, model_key=model_key) self.communicate.statusBarUpdateSignal.emit('Constraint removed') - def getConstraintForRow(self, row): + def getConstraintForRow(self, row, model_key="standard"): """ For the given row, return its constraint, if any (otherwise None) """ - if not self.isCheckable(row): + model = self.model_dict[model_key] + + if not self.isCheckable(row, model_key=model_key): return None - item = self._model_model.item(row, 1) + item = model.item(row, 1) try: return item.child(0).data() except AttributeError: @@ -1014,35 +1161,43 @@ def allParamNames(self): Returns a list of all parameter names defined on the current model """ all_params = self.kernel_module._model_info.parameters.kernel_parameters - all_param_names = [param.name for param in all_params] - # Assure scale and background are always included - if 'scale' not in all_param_names: - all_param_names.append('scale') - if 'background' not in all_param_names: - all_param_names.append('background') - return all_param_names + all_params = list(self.kernel_module.details.keys()) + + #all_param_names = [param.name for param in all_params] + ## Assure scale and background are always included + #if 'scale' not in all_param_names: + # all_param_names.append('scale') + #if 'background' not in all_param_names: + # all_param_names.append('background') + return all_params def paramHasConstraint(self, param=None): """ - Finds out if the given parameter in the main model has a constraint child + Finds out if the given parameter in all the models has a constraint child """ - if param is None: return False - if param not in self.allParamNames(): return False + if param is None: + return False + if param not in self.allParamNames(): + return False - for row in range(self._model_model.rowCount()): - if self._model_model.item(row,0).text() != param: continue - return self.rowHasConstraint(row) + for model_key in self.model_dict.keys(): + for row in range(self.model_dict[model_key].rowCount()): + if self.model_dict[model_key].item(row,0).data(role=QtCore.Qt.UserRole) != param: + continue + return self.rowHasConstraint(row, model_key=model_key) # nothing found return False - def rowHasConstraint(self, row): + def rowHasConstraint(self, row, model_key="standard"): """ Finds out if row of the main model has a constraint child """ - if not self.isCheckable(row): + model = self.model_dict[model_key] + + if not self.isCheckable(row, model_key=model_key): return False - item = self._model_model.item(row, 1) + item = model.item(row, 1) if not item.hasChildren(): return False c = item.child(0).data() @@ -1050,13 +1205,14 @@ def rowHasConstraint(self, row): return True return False - def rowHasActiveConstraint(self, row): + def rowHasActiveConstraint(self, row, model_key="standard"): """ Finds out if row of the main model has an active constraint child """ - if not self.isCheckable(row): + model = self.model_dict[model_key] + if not self.isCheckable(row, model_key=model_key): return False - item = self._model_model.item(row, 1) + item = model.item(row, 1) if not item.hasChildren(): return False c = item.child(0).data() @@ -1064,13 +1220,14 @@ def rowHasActiveConstraint(self, row): return True return False - def rowHasActiveComplexConstraint(self, row): + def rowHasActiveComplexConstraint(self, row, model_key="standard"): """ Finds out if row of the main model has an active, nontrivial constraint child """ - if not self.isCheckable(row): + model = self.model_dict[model_key] + if not self.isCheckable(row, model_key=model_key): return False - item = self._model_model.item(row, 1) + item = model.item(row, 1) if not item.hasChildren(): return False c = item.child(0).data() @@ -1083,23 +1240,28 @@ def selectParameters(self): Selected parameter is chosen for fitting """ status = QtCore.Qt.Checked - item = self._model_model.itemFromIndex(self.lstParams.currentIndex()) - self.setParameterSelection(status, item=item) + model_key = self.tabToKey[self.tabFitting.currentIndex()] + model = self.model_dict[model_key] + item = model.itemFromIndex(self.lstParams.currentIndex()) + self.setParameterSelection(status, item=item, model_key=model_key) def deselectParameters(self): """ Selected parameters are removed for fitting """ status = QtCore.Qt.Unchecked - item = self._model_model.itemFromIndex(self.lstParams.currentIndex()) - self.setParameterSelection(status, item=item) + model_key = self.tabToKey[self.tabFitting.currentIndex()] + model = self.model_dict[model_key] + item = model.itemFromIndex(self.lstParams.currentIndex()) + self.setParameterSelection(status, item=item, model_key=model_key) - def selectedParameters(self): + def selectedParameters(self, model_key="standard"): """ Returns list of selected (highlighted) parameters """ - return [s.row() for s in self.lstParams.selectionModel().selectedRows() - if self.isCheckable(s.row())] - def setParameterSelection(self, status=QtCore.Qt.Unchecked, item=None): + return [s.row() for s in self.lst_dict[model_key].selectionModel().selectedRows() + if self.isCheckable(s.row(), model_key=model_key)] + + def setParameterSelection(self, status=QtCore.Qt.Unchecked, item=None, model_key="standard"): """ Selected parameters are chosen for fitting """ @@ -1110,56 +1272,85 @@ def setParameterSelection(self, status=QtCore.Qt.Unchecked, item=None): # `item` is also selected! # Otherwise things get confusing. # https://github.com/SasView/sasview/issues/1676 - if item.row() not in self.selectedParameters(): + if item.row() not in self.selectedParameters(model_key=model_key): return - for row in self.selectedParameters(): - self._model_model.item(row, 0).setCheckState(status) + for row in self.selectedParameters(model_key=model_key): + self.model_dict[model_key].item(row, 0).setCheckState(status) - def getConstraintsForModel(self): + def getConstraintsForAllModels(self): """ Return a list of tuples. Each tuple contains constraints mapped as ('constrained parameter', 'function to constrain') e.g. [('sld','5*sld_solvent')] """ - param_number = self._model_model.rowCount() - params = [(self._model_model.item(s, 0).text(), - self._model_model.item(s, 1).child(0).data().func) - for s in range(param_number) if self.rowHasActiveConstraint(s)] + params = [] + for model_key in self.model_dict.keys(): + model = self.model_dict[model_key] + param_number = model.rowCount() + params += [(model.item(s, 0).text(), + model.item(s, 1).child(0).data().func) + for s in range(param_number) if self.rowHasActiveConstraint(s, model_key=model_key)] return params - def getComplexConstraintsForModel(self): + def getComplexConstraintsForAllModels(self): + """ + """ + constraints = [] + for model_key in self.model_dict.keys(): + constraints += self.getComplexConstraintsForModel(model_key=model_key) + + return constraints + + def getComplexConstraintsForModel(self, model_key): """ Return a list of tuples. Each tuple contains constraints mapped as ('constrained parameter', 'function to constrain') e.g. [('sld','5*M2.sld_solvent')]. Only for constraints with defined VALUE """ - param_number = self._model_model.rowCount() - params = [(self._model_model.item(s, 0).text(), - self._model_model.item(s, 1).child(0).data().func) - for s in range(param_number) if self.rowHasActiveComplexConstraint(s)] + model = self.model_dict[model_key] + params = [] + param_number = model.rowCount() + params += [(model.item(s, 0).data(role=QtCore.Qt.UserRole), + model.item(s, 1).child(0).data().func) + for s in range(param_number) if self.rowHasActiveComplexConstraint(s, model_key)] return params - def getFullConstraintNameListForModel(self): + def getFullConstraintNameListForModel(self, model_key): """ Return a list of tuples. Each tuple contains constraints mapped as ('constrained parameter', 'function to constrain') e.g. [('sld','5*M2.sld_solvent')]. Returns a list of all constraints, not only active ones """ - param_number = self._model_model.rowCount() - params = [(self._model_model.item(s, 0).text(), - self._model_model.item(s, 1).child(0).data().func) - for s in range(param_number) if self.rowHasConstraint(s)] + model = self.model_dict[model_key] + param_number = model.rowCount() + params = list() + for s in range(param_number): + if self.rowHasConstraint(s, model_key=model_key): + param_name = model.item(s, 0).text() + if 'Distribution of ' in param_name: + param_name = self.polyNameToParam(model.item(s, 0).text()) + params.append((param_name, model.item(s, 1).child(0).data().func)) return params - def getConstraintObjectsForModel(self): + def getConstraintObjectsForAllModels(self): + """ + """ + constraints = [] + for model_key in self.model_dict.keys(): + constraints += self.getConstraintObjectsForModel(model_key=model_key) + + return constraints + + def getConstraintObjectsForModel(self, model_key): """ Returns Constraint objects present on the whole model """ - param_number = self._model_model.rowCount() - constraints = [self._model_model.item(s, 1).child(0).data() - for s in range(param_number) if self.rowHasConstraint(s)] + model = self.model_dict[model_key] + param_number = model.rowCount() + constraints = [model.item(s, 1).child(0).data() + for s in range(param_number) if self.rowHasConstraint(s, model_key=model_key)] return constraints @@ -1168,7 +1359,9 @@ def getConstraintsForFitting(self): Return a list of constraints in format ready for use in fiting """ # Get constraints - constraints = self.getComplexConstraintsForModel() + constraints = [] + for model_key in self.model_dict.keys(): + constraints += self.getComplexConstraintsForModel(model_key=model_key) # See if there are any constraints across models multi_constraints = [cons for cons in constraints if self.isConstraintMultimodel(cons[1])] @@ -1197,13 +1390,14 @@ def getConstraintsForFitting(self): for cons in multi_constraints: # deactivate the constraint row = self.getRowFromName(cons[0]) - self.getConstraintForRow(row).active = False + model_key = self.getModelKeyFromName(cons[0]) + self.getConstraintForRow(row, model_key=model_key).active = False # uncheck in the constraint tab if constraint_tab: constraint_tab.uncheckConstraint( self.kernel_module.name + ':' + cons[0]) # re-read the constraints - constraints = self.getComplexConstraintsForModel() + constraints = self.getComplexConstraintsForModel(model_key=model_key) return constraints @@ -1351,16 +1545,19 @@ def onSelectionChanged(self): """ React to parameter selection """ - rows = self.lstParams.selectionModel().selectedRows() + current_list = self.tabToList[self.tabFitting.currentIndex()] + model_key = self.tabToKey[self.tabFitting.currentIndex()] + + rows = current_list.selectionModel().selectedRows() # Clean previous messages self.communicate.statusBarUpdateSignal.emit("") if len(rows) == 1: # Show constraint, if present row = rows[0].row() - if not self.rowHasConstraint(row): + if not self.rowHasConstraint(row, model_key=model_key): return - constr = self.getConstraintForRow(row) - func = self.getConstraintForRow(row).func + constr = self.getConstraintForRow(row, model_key=model_key) + func = self.getConstraintForRow(row, model_key=model_key).func if constr.func is not None: # inter-parameter constraint update_text = "Active constraint: "+func @@ -1500,6 +1697,7 @@ def onPolyModelChange(self, top, bottom): if model_column == delegate.poly_parameter: # Is the parameter checked for fitting? value = item.checkState() + if value == QtCore.Qt.Checked: self.poly_params_to_fit.append(parameter_name_w) else: @@ -1598,6 +1796,9 @@ def onMagnetModelChange(self, top, bottom): self.kernel_module.details[parameter_name][pos] = value else: self.magnet_params[parameter_name] = value + #self.kernel_module.setParam(parameter_name) = value + # Force the chart update when actual parameters changed + self.recalculatePlotData() # Update state stack self.updateUndo() @@ -1886,14 +2087,20 @@ def prepareFitters(self, fitter=None, fit_id=0, weight_increase=1): params_to_fit = copy.deepcopy(self.main_params_to_fit) if self.chkPolydispersity.isChecked(): - params_to_fit += self.poly_params_to_fit + for p in self.poly_params_to_fit: + if "Distribution of" in p: + params_to_fit += [self.polyNameToParam(p)] + else: + params_to_fit += [p] if self.chkMagnetism.isChecked() and self.canHaveMagnetism(): params_to_fit += self.magnet_params_to_fit if not params_to_fit: raise ValueError('Fitting requires at least one parameter to optimize.') # Get the constraints. - constraints = self.getComplexConstraintsForModel() + constraints = [] + for model_key in self.model_dict.keys(): + constraints += self.getComplexConstraintsForModel(model_key=model_key) if fitter is None: # For single fits - check for inter-model constraints constraints = self.getConstraintsForFitting() @@ -2350,6 +2557,8 @@ def addBackgroundToModel(self, model): last_row = model.rowCount()-1 model.item(last_row, 0).setEditable(False) model.item(last_row, 4).setEditable(False) + model.item(last_row,0).setData('background', role=QtCore.Qt.UserRole) + def addScaleToModel(self, model): """ @@ -2361,6 +2570,7 @@ def addScaleToModel(self, model): last_row = model.rowCount()-1 model.item(last_row, 0).setEditable(False) model.item(last_row, 4).setEditable(False) + model.item(last_row,0).setData('scale', role=QtCore.Qt.UserRole) def addWeightingToData(self, data): """ @@ -2626,7 +2836,7 @@ def onMainParamsChange(self, top, bottom): model_column = item.column() if model_column == 0: - self.checkboxSelected(item) + self.checkboxSelected(item, model_key="standard") self.cmdFit.setEnabled(self.haveParamsToFit()) # Update state stack self.updateUndo() @@ -2738,22 +2948,27 @@ def setParamEditableByRow(self, row, editable=True): item_name.setCheckState(QtCore.Qt.Unchecked) item_name.setCheckable(False) - def isCheckable(self, row): - return self._model_model.item(row, 0).isCheckable() + def isCheckable(self, row, model_key="standard"): + model = self.model_dict[model_key] + if model.item(row,0) is None: + return False + return model.item(row, 0).isCheckable() - def changeCheckboxStatus(self, row, checkbox_status): + def changeCheckboxStatus(self, row, checkbox_status, model_key="standard"): """ Checks/unchecks the checkbox at given row """ - assert 0<= row <= self._model_model.rowCount() - index = self._model_model.index(row, 0) - item = self._model_model.itemFromIndex(index) + model = self.model_dict[model_key] + + assert 0<= row <= model.rowCount() + index = model.index(row, 0) + item = model.itemFromIndex(index) if checkbox_status: item.setCheckState(QtCore.Qt.Checked) else: item.setCheckState(QtCore.Qt.Unchecked) - def checkboxSelected(self, item): + def checkboxSelected(self, item, model_key="standard"): # Assure we're dealing with checkboxes if not item.isCheckable(): return @@ -2763,18 +2978,22 @@ def checkboxSelected(self, item): # Convert to proper indices and set requested enablement # Careful with `item` NOT being selected. This means we only want to # select that one item. - self.setParameterSelection(status, item=item) + self.setParameterSelection(status, item=item, model_key=model_key) # update the list of parameters to fit - self.main_params_to_fit = self.checkedListFromModel(self._model_model) + self.main_params_to_fit = self.checkedListFromModel("standard") + self.poly_params_to_fit = self.checkedListFromModel("poly") + self.magnet_params_to_fit = self.checkedListFromModel("magnet") - def checkedListFromModel(self, model): + def checkedListFromModel(self, model_key): """ Returns list of checked parameters for given model """ def isChecked(row): + model = self.model_dict[model_key] return model.item(row, 0).checkState() == QtCore.Qt.Checked + model = self.model_dict[model_key] return [str(model.item(row_index, 0).text()) for row_index in range(model.rowCount()) if isChecked(row_index)] @@ -3226,6 +3445,9 @@ def addNameToPolyModel(self, i, param_name): str(npts), str(nsigs), "gaussian ",''] FittingUtilities.addCheckedListToModel(self._poly_model, checked_list) + all_items = self._poly_model.rowCount() + self._poly_model.item(all_items-1,0).setData(param_wname, role=QtCore.Qt.UserRole) + # All possible polydisp. functions as strings in combobox func = QtWidgets.QComboBox() func.addItems([str(name_disp) for name_disp in POLYDISPERSITY_MODELS.keys()]) @@ -3434,6 +3656,8 @@ def addCheckedMagneticListToModel(self, param, value): self.magnet_params[param.name] = value FittingUtilities.addCheckedListToModel(self._magnet_model, checked_list) + all_items = self._magnet_model.rowCount() + self._magnet_model.item(all_items-1,0).setData(param.name, role=QtCore.Qt.UserRole) def enableStructureFactorControl(self, structure_factor): """ @@ -4014,13 +4238,16 @@ def gatherParams(row): Create list of main parameters based on _model_model """ param_name = str(self._model_model.item(row, 0).text()) - + current_list = self.tabToList[self.tabFitting.currentIndex()] + model = self._model_model + if model.item(row, 0) is None: + return # Assure this is a parameter - must contain a checkbox - if not self._model_model.item(row, 0).isCheckable(): + if not model.item(row, 0).isCheckable(): # maybe it is a combobox item (multiplicity) try: - index = self._model_model.index(row, 1) - widget = self.lstParams.indexWidget(index) + index = model.index(row, 1) + widget = current_list.indexWidget(index) if widget is None: return if isinstance(widget, QtWidgets.QComboBox): @@ -4031,23 +4258,23 @@ def gatherParams(row): pass return - param_checked = str(self._model_model.item(row, 0).checkState() == QtCore.Qt.Checked) + param_checked = str(model.item(row, 0).checkState() == QtCore.Qt.Checked) # Value of the parameter. In some cases this is the text of the combobox choice. - param_value = str(self._model_model.item(row, 1).text()) + param_value = str(model.item(row, 1).text()) param_error = None param_min = None param_max = None column_offset = 0 if self.has_error_column: column_offset = 1 - param_error = str(self._model_model.item(row, 1+column_offset).text()) + param_error = str(model.item(row, 1+column_offset).text()) try: - param_min = str(self._model_model.item(row, 2+column_offset).text()) - param_max = str(self._model_model.item(row, 3+column_offset).text()) + param_min = str(model.item(row, 2+column_offset).text()) + param_max = str(model.item(row, 3+column_offset).text()) except: pass # Do we have any constraints on this parameter? - constraint = self.getConstraintForRow(row) + constraint = self.getConstraintForRow(row, model_key="standard") cons = () if constraint is not None: value = constraint.value @@ -4446,6 +4673,9 @@ def getSymbolDict(self): return sym_dict model_name = self.kernel_module.name for param in self.getParamNames(): + model_key = self.getModelKeyFromName(param) sym_dict[f"{model_name}.{param}"] = GuiUtils.toDouble( - self._model_model.item(self.getRowFromName(param), 1).text()) + self.model_dict[model_key].item(self.getRowFromName(param), 1).text()) return sym_dict + + diff --git a/src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py b/src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py index 229f49f290..e54c4b2d50 100644 --- a/src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py +++ b/src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py @@ -692,7 +692,7 @@ def removeData(self, data_list=None): self.nTermsSuggested)) self.regConstantSuggestionButton.setText("{:-3.2g}".format( REGULARIZATION)) - self.updateGuiValues() + # self.updateGuiValues() self.setupModel() else: self.dataList.setCurrentIndex(0)