source: sasview/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py @ f182f93

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

Fitting connected. Initial prototype

  • Property mode set to 100755
File size: 35.9 KB
Line 
1import sys
2import json
3import os
4import numpy
5from collections import defaultdict
6
7import logging
8import traceback
9from twisted.internet import threads
10
11from PyQt4 import QtGui
12from PyQt4 import QtCore
13
14from sasmodels import generate
15from sasmodels import modelinfo
16from sasmodels.sasview_model import load_standard_models
17from sas.sascalc.fit.BumpsFitting import BumpsFit as Fit
18from sas.sasgui.perspectives.fitting.fit_thread import FitThread
19
20from sas.sasgui.guiframe.CategoryInstaller import CategoryInstaller
21from sas.sasgui.guiframe.dataFitting import Data1D
22from sas.sasgui.guiframe.dataFitting import Data2D
23import sas.qtgui.GuiUtils as GuiUtils
24from sas.sasgui.perspectives.fitting.model_thread import Calc1D
25from sas.sasgui.perspectives.fitting.model_thread import Calc2D
26
27from UI.FittingWidgetUI import Ui_FittingWidgetUI
28from sas.qtgui.Perspectives.Fitting.FittingLogic import FittingLogic
29from sas.qtgui.Perspectives.Fitting import FittingUtilities
30
31TAB_MAGNETISM = 4
32TAB_POLY = 3
33CATEGORY_DEFAULT = "Choose category..."
34CATEGORY_STRUCTURE = "Structure Factor"
35STRUCTURE_DEFAULT = "None"
36QMIN_DEFAULT = 0.0005
37QMAX_DEFAULT = 0.5
38NPTS_DEFAULT = 50
39
40class FittingWidget(QtGui.QWidget, Ui_FittingWidgetUI):
41    """
42    Main widget for selecting form and structure factor models
43    """
44    def __init__(self, parent=None, data=None, id=1):
45
46        super(FittingWidget, self).__init__()
47
48        # Necessary globals
49        self.parent = parent
50        # SasModel is loaded
51        self.model_is_loaded = False
52        # Data[12]D passed and set
53        self.data_is_loaded = False
54        # Current SasModel in view
55        self.kernel_module = None
56        # Current SasModel view dimension
57        self.is2D = False
58        # Current SasModel is multishell
59        self.model_has_shells = False
60        # Utility variable to enable unselectable option in category combobox
61        self._previous_category_index = 0
62        # Utility variable for multishell display
63        self._last_model_row = 0
64        # Dictionary of {model name: model class} for the current category
65        self.models = {}
66        # Parameters to fit
67        self.parameters_to_fit = None
68
69        # Which tab is this widget displayed in?
70        self.tab_id = id
71
72        # Which shell is being currently displayed?
73        self.current_shell_displayed = 0
74        self.has_error_column = False
75
76        # Range parameters
77        self.q_range_min = QMIN_DEFAULT
78        self.q_range_max = QMAX_DEFAULT
79        self.npts = NPTS_DEFAULT
80
81        # Main Data[12]D holder
82        self.logic = FittingLogic(data=data)
83
84        # Main GUI setup up
85        self.setupUi(self)
86        self.setWindowTitle("Fitting")
87        self.communicate = self.parent.communicate
88
89        # Define bold font for use in various controls
90        self.boldFont=QtGui.QFont()
91        self.boldFont.setBold(True)
92
93        # Set data label
94        self.label.setFont(self.boldFont)
95        self.label.setText("No data loaded")
96        self.lblFilename.setText("")
97
98        # Set the main models
99        # We can't use a single model here, due to restrictions on flattening
100        # the model tree with subclassed QAbstractProxyModel...
101        self._model_model = QtGui.QStandardItemModel()
102        self._poly_model = QtGui.QStandardItemModel()
103        self._magnet_model = QtGui.QStandardItemModel()
104
105        # Param model displayed in param list
106        self.lstParams.setModel(self._model_model)
107        self.readCategoryInfo()
108        self.model_parameters = None
109        self.lstParams.setAlternatingRowColors(True)
110
111        # Poly model displayed in poly list
112        self.lstPoly.setModel(self._poly_model)
113        self.setPolyModel()
114        self.setTableProperties(self.lstPoly)
115
116        # Magnetism model displayed in magnetism list
117        self.lstMagnetic.setModel(self._magnet_model)
118        self.setMagneticModel()
119        self.setTableProperties(self.lstMagnetic)
120
121        # Defaults for the structure factors
122        self.setDefaultStructureCombo()
123
124        # Make structure factor and model CBs disabled
125        self.disableModelCombo()
126        self.disableStructureCombo()
127
128        # Generate the category list for display
129        category_list = sorted(self.master_category_dict.keys())
130        self.cbCategory.addItem(CATEGORY_DEFAULT)
131        self.cbCategory.addItems(category_list)
132        self.cbCategory.addItem(CATEGORY_STRUCTURE)
133        self.cbCategory.setCurrentIndex(0)
134
135        self._index = None
136        if data is not None:
137            self.data = data
138
139        # Connect signals to controls
140        self.initializeSignals()
141
142        # Initial control state
143        self.initializeControls()
144
145    @property
146    def data(self):
147        return self.logic.data
148
149    @data.setter
150    def data(self, value):
151        """ data setter """
152        assert isinstance(value[0], QtGui.QStandardItem)
153        # _index contains the QIndex with data
154        self._index = value[0]
155
156        # Update logics with data items
157        self.logic.data = GuiUtils.dataFromItem(value[0])
158
159        self.data_is_loaded = True
160        # Tag along functionality
161        self.label.setText("Data loaded from: ")
162        self.lblFilename.setText(self.logic.data.filename)
163        self.updateQRange()
164        self.cmdFit.setEnabled(True)
165
166    def acceptsData(self):
167        """ Tells the caller this widget can accept new dataset """
168        return not self.data_is_loaded
169
170    def disableModelCombo(self):
171        """ Disable the combobox """
172        self.cbModel.setEnabled(False)
173        self.lblModel.setEnabled(False)
174
175    def enableModelCombo(self):
176        """ Enable the combobox """
177        self.cbModel.setEnabled(True)
178        self.lblModel.setEnabled(True)
179
180    def disableStructureCombo(self):
181        """ Disable the combobox """
182        self.cbStructureFactor.setEnabled(False)
183        self.lblStructure.setEnabled(False)
184
185    def enableStructureCombo(self):
186        """ Enable the combobox """
187        self.cbStructureFactor.setEnabled(True)
188        self.lblStructure.setEnabled(True)
189
190    def togglePoly(self, isChecked):
191        """
192        Enable/disable the polydispersity tab
193        """
194        self.tabFitting.setTabEnabled(TAB_POLY, isChecked)
195
196    def toggleMagnetism(self, isChecked):
197        """
198        Enable/disable the magnetism tab
199        """
200        self.tabFitting.setTabEnabled(TAB_MAGNETISM, isChecked)
201
202    def toggle2D(self, isChecked):
203        """
204        Enable/disable the controls dependent on 1D/2D data instance
205        """
206        self.chkMagnetism.setEnabled(isChecked)
207        self.is2D = isChecked
208
209    def initializeControls(self):
210        """
211        Set initial control enablement
212        """
213        self.cmdFit.setEnabled(False)
214        self.cmdPlot.setEnabled(True)
215        self.chkPolydispersity.setEnabled(True)
216        self.chkPolydispersity.setCheckState(False)
217        self.chk2DView.setEnabled(True)
218        self.chk2DView.setCheckState(False)
219        self.chkMagnetism.setEnabled(False)
220        self.chkMagnetism.setCheckState(False)
221        # Tabs
222        self.tabFitting.setTabEnabled(TAB_POLY, False)
223        self.tabFitting.setTabEnabled(TAB_MAGNETISM, False)
224        self.lblChi2Value.setText("---")
225        # Update Q Ranges
226        self.updateQRange()
227
228    def initializeSignals(self):
229        """
230        Connect GUI element signals
231        """
232        # Comboboxes
233        self.cbStructureFactor.currentIndexChanged.connect(self.onSelectStructureFactor)
234        self.cbCategory.currentIndexChanged.connect(self.onSelectCategory)
235        self.cbModel.currentIndexChanged.connect(self.onSelectModel)
236        # Checkboxes
237        self.chk2DView.toggled.connect(self.toggle2D)
238        self.chkPolydispersity.toggled.connect(self.togglePoly)
239        self.chkMagnetism.toggled.connect(self.toggleMagnetism)
240        # Buttons
241        self.cmdFit.clicked.connect(self.onFit)
242        self.cmdPlot.clicked.connect(self.onPlot)
243        # Line edits
244        self.txtNpts.editingFinished.connect(self.onNpts)
245        self.txtMinRange.editingFinished.connect(self.onMinRange)
246        self.txtMaxRange.editingFinished.connect(self.onMaxRange)
247
248        # Respond to change in parameters from the UI
249        self._model_model.itemChanged.connect(self.updateParamsFromModel)
250        self._poly_model.itemChanged.connect(self.onPolyModelChange)
251        # TODO after the poly_model prototype accepted
252        #self._magnet_model.itemChanged.connect(self.onMagneticModelChange)
253
254    def onSelectModel(self):
255        """
256        Respond to select Model from list event
257        """
258        model = str(self.cbModel.currentText())
259
260        # Reset structure factor
261        self.cbStructureFactor.setCurrentIndex(0)
262
263        # Reset parameters to fit
264        self.parameters_to_fit = None
265
266        # SasModel -> QModel
267        self.SASModelToQModel(model)
268
269        if self.data_is_loaded:
270            self.calculateQGridForModel()
271        else:
272            # Create default datasets if no data passed
273            self.createDefaultDataset()
274
275    def onSelectStructureFactor(self):
276        """
277        Select Structure Factor from list
278        """
279        model = str(self.cbModel.currentText())
280        category = str(self.cbCategory.currentText())
281        structure = str(self.cbStructureFactor.currentText())
282        if category == CATEGORY_STRUCTURE:
283            model = None
284        self.SASModelToQModel(model, structure_factor=structure)
285
286    def onSelectCategory(self):
287        """
288        Select Category from list
289        """
290        category = str(self.cbCategory.currentText())
291        # Check if the user chose "Choose category entry"
292        if category == CATEGORY_DEFAULT:
293            # if the previous category was not the default, keep it.
294            # Otherwise, just return
295            if self._previous_category_index != 0:
296                # We need to block signals, or else state changes on perceived unchanged conditions
297                self.cbCategory.blockSignals(True)
298                self.cbCategory.setCurrentIndex(self._previous_category_index)
299                self.cbCategory.blockSignals(False)
300            return
301
302        if category == CATEGORY_STRUCTURE:
303            self.disableModelCombo()
304            self.enableStructureCombo()
305            self._model_model.clear()
306            return
307
308        # Safely clear and enable the model combo
309        self.cbModel.blockSignals(True)
310        self.cbModel.clear()
311        self.cbModel.blockSignals(False)
312        self.enableModelCombo()
313        self.disableStructureCombo()
314
315        self._previous_category_index = self.cbCategory.currentIndex()
316        # Retrieve the list of models
317        model_list = self.master_category_dict[category]
318        models = []
319        # Populate the models combobox
320        self.cbModel.addItems(sorted([model for (model, _) in model_list]))
321
322    def onPolyModelChange(self, item):
323        """
324        Callback method for updating the main model and sasmodel
325        parameters with the GUI values in the polydispersity view
326        """
327        model_column = item.column()
328        model_row = item.row()
329        name_index = self._poly_model.index(model_row, 0)
330        # Extract changed value. Assumes proper validation by QValidator/Delegate
331        # Checkbox in column 0
332        if model_column == 0:
333            value = item.checkState()
334        else:
335            try:
336                value = float(item.text())
337            except ValueError:
338                # Can't be converted properly, bring back the old value and exit
339                return
340
341        parameter_name = str(self._poly_model.data(name_index).toPyObject()) # "distribution of sld" etc.
342        if "Distribution of" in parameter_name:
343            parameter_name = parameter_name[16:]
344        property_name = str(self._poly_model.headerData(model_column, 1).toPyObject()) # Value, min, max, etc.
345        # print "%s(%s) => %d" % (parameter_name, property_name, value)
346
347        # Update the sasmodel
348        #self.kernel_module.params[parameter_name] = value
349
350        # Reload the main model - may not be required if no variable is shown in main view
351        #model = str(self.cbModel.currentText())
352        #self.SASModelToQModel(model)
353
354        pass # debug anchor
355
356    def onFit(self):
357        """
358        Perform fitting on the current data
359        """
360        fitter = Fit()
361
362        # Data going in
363        data = self.logic.data
364        model = self.kernel_module
365        qmin = self.q_range_min
366        qmax = self.q_range_max
367        params_to_fit = self.parameters_to_fit
368
369        # These should be updating somehow?
370        fit_id = 0
371        constraints = []
372        smearer = None
373        page_id = [210]
374        handler = None
375        batch_inputs = {}
376        batch_outputs = {}
377        list_page_id = [page_id]
378        #---------------------------------
379
380        # Parameterize the fitter
381        fitter.set_model(model, fit_id, params_to_fit, data=data,
382                         constraints=constraints)
383        fitter.set_data(data=data, id=fit_id, smearer=smearer, qmin=qmin,
384                        qmax=qmax)
385        fitter.select_problem_for_fit(id=fit_id, value=1)
386
387        fitter.fitter_id = page_id
388
389        # Create the fitting thread, based on the fitter
390        calc_fit = FitThread(handler=handler,
391                             fn=[fitter],
392                             batch_inputs=batch_inputs,
393                             batch_outputs=batch_outputs,
394                             page_id=list_page_id,
395                             updatefn=self.updateFit,
396                             completefn=None)
397
398        # start the trhrhread
399        calc_thread = threads.deferToThread(calc_fit.compute)
400        calc_thread.addCallback(self.fitComplete)
401
402        #disable the Fit button
403        self.cmdFit.setText("Calculating...")
404        self.cmdFit.setEnabled(False)
405
406    def updateFit(self):
407        """
408        """
409        print "UPDATE FIT"
410        pass
411
412    def fitComplete(self, result):
413        """
414        Receive and display fitting results
415
416        "result" is a tuple of actual result list and the fit time in seconds
417        """
418        #re-enable the Fit button
419        self.cmdFit.setText("Fit")
420        self.cmdFit.setEnabled(True)
421        res_list = result[0]
422        res = res_list[0]
423        if res.fitness is None or \
424            not numpy.isfinite(res.fitness) or \
425            numpy.any(res.pvec == None) or \
426            not numpy.all(numpy.isfinite(res.pvec)):
427            msg = "Fitting did not converge!!!"
428            logging.error(msg)
429            return
430
431        elapsed = result[1]
432        msg = "Fitting completed successfully in: %s s.\n" % GuiUtils.formatNumber(elapsed)
433
434        self.communicate.statusBarUpdateSignal.emit(msg)
435
436        fitness = res.fitness
437        param_list = res.param_list
438        param_values = res.pvec
439        param_stderr = res.stderr
440        from itertools import izip
441        # TODO: add errors to the dict so they can propagate to the view
442        params_and_errors = zip(param_values, param_stderr)
443        param_dict = dict(izip(param_list, params_and_errors))
444
445        # Dictionary of fitted parameter: value, error
446        # e.g. param_dic = {"sld":(1.703, 0.0034), "length":(33.455, -0.0983)}
447        self.updateModelFromList(param_dict)
448
449        # Read only value - we can get away by just printing it here
450        chi2_repr = GuiUtils.formatNumber(fitness, high=True)
451        self.lblChi2Value.setText(chi2_repr)
452
453        pass
454
455    def iterateOverModel(self, func):
456        """
457        Take func and throw it inside the model row loop
458        """
459        #assert isinstance(func, function)
460        for row_i in xrange(self._model_model.rowCount()):
461            func(row_i)
462
463    def updateModelFromList(self, param_dict):
464        """
465        Update the model with new parameters, create the errors column
466        """
467        assert isinstance(param_dict, dict)
468        if not dict:
469            return
470
471        def updateValues(row_i):
472            # Utility function for main model update
473            param_name = str(self._model_model.item(row_i, 0).text())
474            if param_name not in param_dict.keys():
475                return
476            # modify the param value
477            self._model_model.item(row_i, 1).setText(str(param_dict[param_name][0]))
478            if self.has_error_column:
479                self._model_model.item(row_i, 2).setText(str(param_dict[param_name][1]))
480
481        def createColumn(row_i):
482            # Utility function for error column update
483            item = QtGui.QStandardItem()
484            for param_name in param_dict.keys():
485                if str(self._model_model.item(row_i, 0).text()) != param_name:
486                    continue
487                error_repr = GuiUtils.formatNumber(param_dict[param_name][1], high=True)
488                item.setText(error_repr)
489            error_column.append(item)
490
491        self.iterateOverModel(updateValues)
492
493        if self.has_error_column:
494            return
495
496        error_column = []
497        self.iterateOverModel(createColumn)
498
499        self.has_error_column = True
500        self._model_model.insertColumn(2, error_column)
501        FittingUtilities.addErrorHeadersToModel(self._model_model)
502
503
504    def onPlot(self):
505        """
506        Plot the current set of data
507        """
508        if self.data is None :
509            self.createDefaultDataset()
510        self.calculateQGridForModel()
511
512    def onNpts(self):
513        """
514        Callback for number of points line edit update
515        """
516        # assumes type/value correctness achieved with QValidator
517        try:
518            self.npts = int(self.txtNpts.text())
519        except ValueError:
520            # TODO
521            # This will return the old value to model/view and return
522            # notifying the user about format available.
523            pass
524        # Force redisplay
525        if self.model_is_loaded:
526            self.onPlot()
527
528    def onMinRange(self):
529        """
530        Callback for minimum range of points line edit update
531        """
532        # assumes type/value correctness achieved with QValidator
533        try:
534            self.q_range_min = float(self.txtMinRange.text())
535        except ValueError:
536            # TODO
537            # This will return the old value to model/view and return
538            # notifying the user about format available.
539            return
540        # set Q range labels on the main tab
541        self.lblMinRangeDef.setText(str(self.q_range_min))
542        if self.model_is_loaded:
543            self.onPlot()
544
545    def onMaxRange(self):
546        """
547        Callback for maximum range of points line edit update
548        """
549        # assumes type/value correctness achieved with QValidator
550        try:
551            self.q_range_max = float(self.txtMaxRange.text())
552        except:
553            pass
554        # set Q range labels on the main tab
555        self.lblMaxRangeDef.setText(str(self.q_range_max))
556        if self.model_is_loaded:
557            self.onPlot()
558
559    def setDefaultStructureCombo(self):
560        """
561        Fill in the structure factors combo box with defaults
562        """
563        structure_factor_list = self.master_category_dict.pop(CATEGORY_STRUCTURE)
564        factors = [factor[0] for factor in structure_factor_list]
565        factors.insert(0, STRUCTURE_DEFAULT)
566        self.cbStructureFactor.clear()
567        self.cbStructureFactor.addItems(sorted(factors))
568
569    def createDefaultDataset(self):
570        """
571        Generate default Dataset 1D/2D for the given model
572        """
573        # Create default datasets if no data passed
574        if self.is2D:
575            qmax = self.q_range_max/numpy.sqrt(2)
576            qstep = self.npts
577            self.logic.createDefault2dData(qmax, qstep, self.tab_id)
578        else:
579            interval = numpy.linspace(start=self.q_range_min, stop=self.q_range_max,
580                        num=self.npts, endpoint=True)
581            self.logic.createDefault1dData(interval, self.tab_id)
582
583    def readCategoryInfo(self):
584        """
585        Reads the categories in from file
586        """
587        self.master_category_dict = defaultdict(list)
588        self.by_model_dict = defaultdict(list)
589        self.model_enabled_dict = defaultdict(bool)
590
591        categorization_file = CategoryInstaller.get_user_file()
592        if not os.path.isfile(categorization_file):
593            categorization_file = CategoryInstaller.get_default_file()
594        with open(categorization_file, 'rb') as cat_file:
595            self.master_category_dict = json.load(cat_file)
596            self.regenerateModelDict()
597
598        # Load the model dict
599        models = load_standard_models()
600        for model in models:
601            self.models[model.name] = model
602
603    def regenerateModelDict(self):
604        """
605        Regenerates self.by_model_dict which has each model name as the
606        key and the list of categories belonging to that model
607        along with the enabled mapping
608        """
609        self.by_model_dict = defaultdict(list)
610        for category in self.master_category_dict:
611            for (model, enabled) in self.master_category_dict[category]:
612                self.by_model_dict[model].append(category)
613                self.model_enabled_dict[model] = enabled
614
615    def addBackgroundToModel(self, model):
616        """
617        Adds background parameter with default values to the model
618        """
619        assert isinstance(model, QtGui.QStandardItemModel)
620        checked_list = ['background', '0.001', '-inf', 'inf', '1/cm']
621        FittingUtilities.addCheckedListToModel(model, checked_list)
622
623    def addScaleToModel(self, model):
624        """
625        Adds scale parameter with default values to the model
626        """
627        assert isinstance(model, QtGui.QStandardItemModel)
628        checked_list = ['scale', '1.0', '0.0', 'inf', '']
629        FittingUtilities.addCheckedListToModel(model, checked_list)
630
631    def updateQRange(self):
632        """
633        Updates Q Range display
634        """
635        if self.data_is_loaded:
636            self.q_range_min, self.q_range_max, self.npts = self.logic.computeDataRange()
637        # set Q range labels on the main tab
638        self.lblMinRangeDef.setText(str(self.q_range_min))
639        self.lblMaxRangeDef.setText(str(self.q_range_max))
640        # set Q range labels on the options tab
641        self.txtMaxRange.setText(str(self.q_range_max))
642        self.txtMinRange.setText(str(self.q_range_min))
643        self.txtNpts.setText(str(self.npts))
644
645    def SASModelToQModel(self, model_name, structure_factor=None):
646        """
647        Setting model parameters into table based on selected category
648        """
649        # TODO - modify for structure factor-only choice
650
651        # Crete/overwrite model items
652        self._model_model.clear()
653
654        kernel_module = generate.load_kernel_module(model_name)
655        self.model_parameters = modelinfo.make_parameter_table(getattr(kernel_module, 'parameters', []))
656
657        # Instantiate the current sasmodel
658        self.kernel_module = self.models[model_name]()
659
660        # Explicitly add scale and background with default values
661        self.addScaleToModel(self._model_model)
662        self.addBackgroundToModel(self._model_model)
663
664        # Update the QModel
665        FittingUtilities.addParametersToModel(self.model_parameters, self._model_model)
666        # Update the counter used for multishell display
667        self._last_model_row = self._model_model.rowCount()
668
669        FittingUtilities.addHeadersToModel(self._model_model)
670
671        # Add structure factor
672        if structure_factor is not None and structure_factor != "None":
673            structure_module = generate.load_kernel_module(structure_factor)
674            structure_parameters = modelinfo.make_parameter_table(getattr(structure_module, 'parameters', []))
675            FittingUtilities.addSimpleParametersToModel(structure_parameters, self._model_model)
676            # Set the error column width to 0
677            self.lstParams.setColumnWidth(2, 20)
678            # Update the counter used for multishell display
679            self._last_model_row = self._model_model.rowCount()
680        else:
681            self.addStructureFactor()
682
683        # Multishell models need additional treatment
684        self.addExtraShells()
685
686        # Add polydispersity to the model
687        self.setPolyModel()
688        # Add magnetic parameters to the model
689        self.setMagneticModel()
690
691        # Adjust the table cells width
692        self.lstParams.resizeColumnToContents(0)
693        self.lstParams.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
694
695        # Now we claim the model has been loaded
696        self.model_is_loaded = True
697
698        # Update Q Ranges
699        self.updateQRange()
700
701    def updateParamsFromModel(self, item):
702        """
703        Callback method for updating the sasmodel parameters with the GUI values
704        """
705        model_column = item.column()
706
707        if model_column == 0:
708            self.checkboxSelected(item)
709            return
710
711        model_row = item.row()
712        name_index = self._model_model.index(model_row, 0)
713
714        # Extract changed value. Assumes proper validation by QValidator/Delegate
715        value = float(item.text())
716        parameter_name = str(self._model_model.data(name_index).toPyObject()) # sld, background etc.
717        property_name = str(self._model_model.headerData(1, model_column).toPyObject()) # Value, min, max, etc.
718
719        self.kernel_module.params[parameter_name] = value
720
721        # min/max to be changed in self.kernel_module.details[parameter_name] = ['Ang', 0.0, inf]
722        # magnetic params in self.kernel_module.details['M0:parameter_name'] = value
723        # multishell params in self.kernel_module.details[??] = value
724
725        # Force the chart update
726        self.onPlot()
727
728    def checkboxSelected(self, item):
729        # Assure we're dealing with checkboxes
730        if not item.isCheckable():
731            return
732        status = item.checkState()
733
734        def isChecked(row):
735            return self._model_model.item(row, 0).checkState() == QtCore.Qt.Checked
736
737        def isCheckable(row):
738            return self._model_model.item(row, 0).isCheckable()
739
740        # If multiple rows selected - toggle all of them, filtering uncheckable
741        rows = [s.row() for s in self.lstParams.selectionModel().selectedRows() if isCheckable(s.row())]
742
743        # Switch off signaling from the model to avoid recursion
744        self._model_model.blockSignals(True)
745        # Convert to proper indices and set requested enablement
746        items = [self._model_model.item(row, 0).setCheckState(status) for row in rows]
747        self._model_model.blockSignals(False)
748
749        # update the list of parameters to fit
750        self.parameters_to_fit = [str(self._model_model.item(row_index, 0).text())
751                                  for row_index in xrange(self._model_model.rowCount())
752                                  if isChecked(row_index)]
753
754    def nameForFittedData(self, name):
755        """
756        Generate name for the current fit
757        """
758        if self.is2D:
759            name += "2d"
760        name = "M%i [%s]" % (self.tab_id, name)
761        return name
762
763    def createNewIndex(self, fitted_data):
764        """
765        Create a model or theory index with passed Data1D/Data2D
766        """
767        if self.data_is_loaded:
768            if not fitted_data.name:
769                name = self.nameForFittedData(self.data.filename)
770                fitted_data.title = name
771                fitted_data.name = name
772                fitted_data.filename = name
773                fitted_data.symbol = "Line"
774            self.updateModelIndex(fitted_data)
775        else:
776            name = self.nameForFittedData(self.kernel_module.name)
777            fitted_data.title = name
778            fitted_data.name = name
779            fitted_data.filename = name
780            fitted_data.symbol = "Line"
781            self.createTheoryIndex(fitted_data)
782
783    def updateModelIndex(self, fitted_data):
784        """
785        Update a QStandardModelIndex containing model data
786        """
787        if fitted_data.name is None:
788            name = self.nameForFittedData(self.logic.data.filename)
789            fitted_data.title = name
790            fitted_data.name = name
791        else:
792            name = fitted_data.name
793        # Make this a line if no other defined
794        if hasattr(fitted_data, 'symbol') and fitted_data.symbol is None:
795            fitted_data.symbol = 'Line'
796        # Notify the GUI manager so it can update the main model in DataExplorer
797        GuiUtils.updateModelItemWithPlot(self._index, QtCore.QVariant(fitted_data), name)
798
799    def createTheoryIndex(self, fitted_data):
800        """
801        Create a QStandardModelIndex containing model data
802        """
803        if fitted_data.name is None:
804            name = self.nameForFittedData(self.kernel_module.name)
805            fitted_data.title = name
806            fitted_data.name = name
807            fitted_data.filename = name
808        else:
809            name = fitted_data.name
810        # Notify the GUI manager so it can create the theory model in DataExplorer
811        new_item = GuiUtils.createModelItemWithPlot(QtCore.QVariant(fitted_data), name=name)
812        self.communicate.updateTheoryFromPerspectiveSignal.emit(new_item)
813
814    def methodCalculateForData(self):
815        '''return the method for data calculation'''
816        return Calc1D if isinstance(self.data, Data1D) else Calc2D
817
818    def methodCompleteForData(self):
819        '''return the method for result parsin on calc complete '''
820        return self.complete1D if isinstance(self.data, Data1D) else self.complete2D
821
822    def calculateQGridForModel(self):
823        """
824        Prepare the fitting data object, based on current ModelModel
825        """
826        # Awful API to a backend method.
827        method = self.methodCalculateForData()(data=self.data,
828                              model=self.kernel_module,
829                              page_id=0,
830                              qmin=self.q_range_min,
831                              qmax=self.q_range_max,
832                              smearer=None,
833                              state=None,
834                              weight=None,
835                              fid=None,
836                              toggle_mode_on=False,
837                              completefn=None,
838                              update_chisqr=True,
839                              exception_handler=self.calcException,
840                              source=None)
841
842        calc_thread = threads.deferToThread(method.compute)
843        calc_thread.addCallback(self.methodCompleteForData())
844
845    def complete1D(self, return_data):
846        """
847        Plot the current 1D data
848        """
849        fitted_plot = self.logic.new1DPlot(return_data, self.tab_id)
850        self.calculateResiduals(fitted_plot)
851
852    def complete2D(self, return_data):
853        """
854        Plot the current 2D data
855        """
856        fitted_data = self.logic.new2DPlot(return_data)
857        self.calculateResiduals(fitted_data)
858
859    def calculateResiduals(self, fitted_data):
860        """
861        Calculate and print Chi2 and display chart of residuals
862        """
863        # Create a new index for holding data
864        fitted_data.symbol = "Line"
865        self.createNewIndex(fitted_data)
866        # Calculate difference between return_data and logic.data
867        chi2 = FittingUtilities.calculateChi2(fitted_data, self.logic.data)
868        # Update the control
869        chi2_repr = "---" if chi2 is None else GuiUtils.formatNumber(chi2, high=True)
870        #self.lblChi2Value.setText(GuiUtils.formatNumber(chi2, high=True))
871        self.lblChi2Value.setText(chi2_repr)
872
873        # Plot residuals if actual data
874        if self.data_is_loaded:
875            residuals_plot = FittingUtilities.plotResiduals(self.data, fitted_data)
876            residuals_plot.id = "Residual " + residuals_plot.id
877            self.createNewIndex(residuals_plot)
878            self.communicate.plotUpdateSignal.emit([residuals_plot])
879
880        self.communicate.plotUpdateSignal.emit([fitted_data])
881
882    def calcException(self, etype, value, tb):
883        """
884        Something horrible happened in the deferred.
885        """
886        logging.error("".join(traceback.format_exception(etype, value, tb)))
887
888    def setTableProperties(self, table):
889        """
890        Setting table properties
891        """
892        # Table properties
893        table.verticalHeader().setVisible(False)
894        table.setAlternatingRowColors(True)
895        table.setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
896        table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
897        table.resizeColumnsToContents()
898
899        # Header
900        header = table.horizontalHeader()
901        header.setResizeMode(QtGui.QHeaderView.ResizeToContents)
902
903        header.ResizeMode(QtGui.QHeaderView.Interactive)
904        # Resize column 0 and 6 to content
905        header.setResizeMode(0, QtGui.QHeaderView.ResizeToContents)
906        header.setResizeMode(6, QtGui.QHeaderView.ResizeToContents)
907
908    def setPolyModel(self):
909        """
910        Set polydispersity values
911        """
912        if not self.model_parameters:
913            return
914        self._poly_model.clear()
915        for row, param in enumerate(self.model_parameters.form_volume_parameters):
916            # Counters should not be included
917            if not param.polydisperse:
918                continue
919
920            # Potential multishell params
921            checked_list = ["Distribution of "+param.name, str(param.default),
922                            str(param.limits[0]), str(param.limits[1]),
923                            "35", "3", ""]
924            FittingUtilities.addCheckedListToModel(self._poly_model, checked_list)
925
926            #TODO: Need to find cleaner way to input functions
927            func = QtGui.QComboBox()
928            func.addItems(['rectangle', 'array', 'lognormal', 'gaussian', 'schulz',])
929            func_index = self.lstPoly.model().index(row, 6)
930            self.lstPoly.setIndexWidget(func_index, func)
931
932        FittingUtilities.addPolyHeadersToModel(self._poly_model)
933
934    def setMagneticModel(self):
935        """
936        Set magnetism values on model
937        """
938        if not self.model_parameters:
939            return
940        self._magnet_model.clear()
941        for param in self.model_parameters.call_parameters:
942            if param.type != "magnetic":
943                continue
944            checked_list = [param.name,
945                            str(param.default),
946                            str(param.limits[0]),
947                            str(param.limits[1]),
948                            param.units]
949            FittingUtilities.addCheckedListToModel(self._magnet_model, checked_list)
950
951        FittingUtilities.addHeadersToModel(self._magnet_model)
952
953    def addStructureFactor(self):
954        """
955        Add structure factors to the list of parameters
956        """
957        if self.kernel_module.is_form_factor:
958            self.enableStructureCombo()
959        else:
960            self.disableStructureCombo()
961
962    def addExtraShells(self):
963        """
964        Add a combobox for multiple shell display
965        """
966        param_name, param_length = FittingUtilities.getMultiplicity(self.model_parameters)
967
968        if param_length == 0:
969            return
970
971        # cell 1: variable name
972        item1 = QtGui.QStandardItem(param_name)
973
974        func = QtGui.QComboBox()
975        # Available range of shells displayed in the combobox
976        func.addItems([str(i) for i in xrange(param_length+1)])
977
978        # Respond to index change
979        func.currentIndexChanged.connect(self.modifyShellsInList)
980
981        # cell 2: combobox
982        item2 = QtGui.QStandardItem()
983        self._model_model.appendRow([item1, item2])
984
985        # Beautify the row:  span columns 2-4
986        shell_row = self._model_model.rowCount()
987        shell_index = self._model_model.index(shell_row-1, 1)
988
989        self.lstParams.setIndexWidget(shell_index, func)
990        self._last_model_row = self._model_model.rowCount()
991
992        # Set the index to the state-kept value
993        func.setCurrentIndex(self.current_shell_displayed
994                             if self.current_shell_displayed < func.count() else 0)
995
996    def modifyShellsInList(self, index):
997        """
998        Add/remove additional multishell parameters
999        """
1000        # Find row location of the combobox
1001        last_row = self._last_model_row
1002        remove_rows = self._model_model.rowCount() - last_row
1003
1004        if remove_rows > 1:
1005            self._model_model.removeRows(last_row, remove_rows)
1006
1007        FittingUtilities.addShellsToModel(self.model_parameters, self._model_model, index)
1008        self.current_shell_displayed = index
1009
Note: See TracBrowser for help on using the repository browser.