source: sasview/src/sas/sasgui/perspectives/fitting/fitting.py @ c155a16

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since c155a16 was c155a16, checked in by Ricardo Ferraz Leal <ricleal@…>, 7 years ago

Logging is now logger

  • Property mode set to 100755
File size: 88.2 KB
Line 
1"""
2    Fitting perspective
3"""
4################################################################################
5#This software was developed by the University of Tennessee as part of the
6#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
7#project funded by the US National Science Foundation.
8#
9#See the license text in license.txt
10#
11#copyright 2009, University of Tennessee
12################################################################################
13import re
14import sys
15import os
16import wx
17import logging
18import numpy
19import time
20from copy import deepcopy
21import traceback
22
23from sas.sascalc.dataloader.loader import Loader
24from sas.sasgui.guiframe.dataFitting import Data2D
25from sas.sasgui.guiframe.dataFitting import Data1D
26from sas.sasgui.guiframe.dataFitting import check_data_validity
27from sas.sasgui.guiframe.events import NewPlotEvent
28from sas.sasgui.guiframe.events import StatusEvent
29from sas.sasgui.guiframe.events import EVT_SLICER_PANEL
30from sas.sasgui.guiframe.events import EVT_SLICER_PARS_UPDATE
31from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
32from sas.sasgui.guiframe.plugin_base import PluginBase
33from sas.sasgui.guiframe.data_processor import BatchCell
34from sas.sascalc.fit.BumpsFitting import BumpsFit as Fit
35from sas.sasgui.perspectives.fitting.console import ConsoleUpdate
36from sas.sasgui.perspectives.fitting.fitproblem import FitProblemDictionary
37from sas.sasgui.perspectives.fitting.fitpanel import FitPanel
38from sas.sasgui.perspectives.fitting.resultpanel import ResultPanel, PlotResultEvent
39
40from sas.sasgui.perspectives.fitting.fit_thread import FitThread
41from sas.sasgui.perspectives.fitting.pagestate import Reader
42from sas.sasgui.perspectives.fitting.fitpage import Chi2UpdateEvent
43from sas.sasgui.perspectives.calculator.model_editor import TextDialog
44from sas.sasgui.perspectives.calculator.model_editor import EditorWindow
45from sas.sasgui.guiframe.gui_manager import MDIFrame
46from sas.sasgui.guiframe.documentation_window import DocumentationWindow
47from sas.sasgui.perspectives.fitting.gpu_options import GpuOptions
48
49from . import models
50
51logger = logging.getLogger()
52
53MAX_NBR_DATA = 4
54
55(PageInfoEvent, EVT_PAGE_INFO) = wx.lib.newevent.NewEvent()
56
57
58if sys.platform == "win32":
59    ON_MAC = False
60else:
61    ON_MAC = True
62
63import bumps.options
64from bumps.gui.fit_dialog import show_fit_config
65try:
66    from bumps.gui.fit_dialog import EVT_FITTER_CHANGED
67except ImportError:
68    # CRUFT: bumps 0.7.5.8 and below
69    EVT_FITTER_CHANGED = None  # type: wx.PyCommandEvent
70
71class Plugin(PluginBase):
72    """
73    Fitting plugin is used to perform fit
74    """
75    def __init__(self):
76        PluginBase.__init__(self, name="Fitting")
77
78        #list of panel to send to guiframe
79        self.mypanels = []
80        # reference to the current running thread
81        self.calc_2D = None
82        self.calc_1D = None
83
84        self.color_dict = {}
85
86        self.fit_thread_list = {}
87        self.residuals = None
88        self.weight = None
89        self.fit_panel = None
90        self.plot_panel = None
91        # Start with a good default
92        self.elapsed = 0.022
93        self.fit_panel = None
94        ## dictionary of page closed and id
95        self.closed_page_dict = {}
96        ## Relative error desired in the sum of squares (float)
97        self.batch_reset_flag = True
98        #List of selected data
99        self.selected_data_list = []
100        ## list of slicer panel created to display slicer parameters and results
101        self.slicer_panels = []
102        # model 2D view
103        self.model2D_id = None
104        #keep reference of the simultaneous fit page
105        self.sim_page = None
106        self.sim_menu = None
107        self.batch_page = None
108        self.batch_menu = None
109        self.index_model = 0
110        self.test_model_color = None
111        #Create a reader for fit page's state
112        self.state_reader = None
113        self._extensions = '.fitv'
114        self.menu1 = None
115        self.new_model_frame = None
116
117        self.temp_state = []
118        self.state_index = 0
119        self.sfile_ext = None
120        # take care of saving  data, model and page associated with each other
121        self.page_finder = {}
122        # Log startup
123        logger.info("Fitting plug-in started")
124        self.batch_capable = self.get_batch_capable()
125
126    def get_batch_capable(self):
127        """
128        Check if the plugin has a batch capability
129        """
130        return True
131
132    def create_fit_problem(self, page_id):
133        """
134        Given an ID create a fitproblem container
135        """
136        self.page_finder[page_id] = FitProblemDictionary()
137
138    def delete_fit_problem(self, page_id):
139        """
140        Given an ID create a fitproblem container
141        """
142        if page_id in self.page_finder.iterkeys():
143            del self.page_finder[page_id]
144
145    def add_color(self, color, id):
146        """
147        adds a color as a key with a plot id as its value to a dictionary
148        """
149        self.color_dict[id] = color
150
151    def on_batch_selection(self, flag):
152        """
153        switch the the notebook of batch mode or not
154        """
155        self.batch_on = flag
156        if self.fit_panel is not None:
157            self.fit_panel.batch_on = self.batch_on
158
159    def populate_menu(self, owner):
160        """
161        Create a menu for the Fitting plug-in
162
163        :param id: id to create a menu
164        :param owner: owner of menu
165
166        :return: list of information to populate the main menu
167
168        """
169        #Menu for fitting
170        self.menu1 = wx.Menu()
171        id1 = wx.NewId()
172        simul_help = "Add new fit panel"
173        self.menu1.Append(id1, '&New Fit Page', simul_help)
174        wx.EVT_MENU(owner, id1, self.on_add_new_page)
175        self.menu1.AppendSeparator()
176        self.id_simfit = wx.NewId()
177        simul_help = "Constrained or Simultaneous Fit"
178        self.menu1.Append(self.id_simfit, '&Constrained or Simultaneous Fit', simul_help)
179        wx.EVT_MENU(owner, self.id_simfit, self.on_add_sim_page)
180        self.sim_menu = self.menu1.FindItemById(self.id_simfit)
181        self.sim_menu.Enable(False)
182        #combined Batch
183        self.id_batchfit = wx.NewId()
184        batch_help = "Combined Batch"
185        self.menu1.Append(self.id_batchfit, '&Combine Batch Fit', batch_help)
186        wx.EVT_MENU(owner, self.id_batchfit, self.on_add_sim_page)
187        self.batch_menu = self.menu1.FindItemById(self.id_batchfit)
188        self.batch_menu.Enable(False)
189
190        self.menu1.AppendSeparator()
191        self.id_bumps_options = wx.NewId()
192        bopts_help = "Fitting options"
193        self.menu1.Append(self.id_bumps_options, 'Fit &Options', bopts_help)
194        wx.EVT_MENU(owner, self.id_bumps_options, self.on_bumps_options)
195        self.bumps_options_menu = self.menu1.FindItemById(self.id_bumps_options)
196        self.bumps_options_menu.Enable(True)
197
198        self.id_gpu_options_panel = wx.NewId()
199        self.menu1.Append(self.id_gpu_options_panel, "OpenCL Options", "Choose OpenCL driver or turn it off")
200        wx.EVT_MENU(owner, self.id_gpu_options_panel, self.on_gpu_options)
201
202        self.id_result_panel = wx.NewId()
203        self.menu1.Append(self.id_result_panel, "Fit Results", "Show fit results panel")
204        wx.EVT_MENU(owner, self.id_result_panel, self.on_fit_results)
205        self.menu1.AppendSeparator()
206
207        self.id_reset_flag = wx.NewId()
208        resetf_help = "BatchFit: If checked, the initial param values will be "
209        resetf_help += "propagated from the previous results. "
210        resetf_help += "Otherwise, the same initial param values will be used "
211        resetf_help += "for all fittings."
212        self.menu1.AppendCheckItem(self.id_reset_flag,
213                                   "Chain Fitting [BatchFit Only]",
214                                   resetf_help)
215        wx.EVT_MENU(owner, self.id_reset_flag, self.on_reset_batch_flag)
216        chain_menu = self.menu1.FindItemById(self.id_reset_flag)
217        chain_menu.Check(not self.batch_reset_flag)
218        chain_menu.Enable(self.batch_on)
219
220        self.menu1.AppendSeparator()
221        self.edit_model_menu = wx.Menu()
222        # Find and put files name in menu
223        try:
224            self.set_edit_menu(owner=owner)
225        except:
226            raise
227
228        self.id_edit = wx.NewId()
229        self.menu1.AppendMenu(self.id_edit, "Plugin Model Operations",
230                              self.edit_model_menu)
231        #create  menubar items
232        return [(self.menu1, self.sub_menu)]
233
234    def edit_custom_model(self, event):
235        """
236        Get the python editor panel
237        """
238        event_id = event.GetId()
239        label = self.edit_menu.GetLabel(event_id)
240        from sas.sasgui.perspectives.calculator.pyconsole import PyConsole
241        filename = os.path.join(models.find_plugins_dir(), label)
242        frame = PyConsole(parent=self.parent, manager=self,
243                          panel=self.fit_panel,
244                          title='Advanced Plugin Model Editor',
245                          filename=filename)
246        self.put_icon(frame)
247        frame.Show(True)
248
249    def delete_custom_model(self, event):
250        """
251        Delete custom model file
252        """
253        event_id = event.GetId()
254        label = self.delete_menu.GetLabel(event_id)
255        toks = os.path.splitext(label)
256        path = os.path.join(models.find_plugins_dir(), toks[0])
257        try:
258            for ext in ['.py', '.pyc']:
259                p_path = path + ext
260                os.remove(p_path)
261            self.update_custom_combo()
262            if os.path.isfile(p_path):
263                msg = "Sorry! unable to delete the default "
264                msg += "plugin model... \n"
265                msg += "Please manually remove the files (.py, .pyc) "
266                msg += "in the 'plugin_models' folder \n"
267                msg += "inside of the SasView application, "
268                msg += "and try it again."
269                wx.MessageBox(msg, 'Info')
270                #evt = StatusEvent(status=msg, type='stop', info='warning')
271                #wx.PostEvent(self.parent, evt)
272            else:
273                self.delete_menu.Delete(event_id)
274                for item in self.edit_menu.GetMenuItems():
275                    if item.GetLabel() == label:
276                        self.edit_menu.DeleteItem(item)
277                        msg = "The plugin model, %s, has been deleted." % label
278                        evt = StatusEvent(status=msg, type='stop', info='info')
279                        wx.PostEvent(self.parent, evt)
280                        break
281        except Exception:
282            import traceback; traceback.print_exc()
283            msg = 'Delete Error: \nCould not delete the file; Check if in use.'
284            wx.MessageBox(msg, 'Error')
285
286    def make_sum_model(self, event):
287        """
288        Edit summodel template and make one
289        """
290        event_id = event.GetId()
291        model_manager = models.ModelManager()
292        model_list = model_manager.get_model_name_list()
293        plug_dir = models.find_plugins_dir()
294        textdial = TextDialog(None, self, wx.ID_ANY, 'Easy Sum/Multi(p1, p2) Editor',
295                              model_list, plug_dir)
296        self.put_icon(textdial)
297        textdial.ShowModal()
298        textdial.Destroy()
299
300    def make_new_model(self, event):
301        """
302        Make new model
303        """
304        if self.new_model_frame != None:
305            self.new_model_frame.Show(False)
306            self.new_model_frame.Show(True)
307        else:
308            event_id = event.GetId()
309            dir_path = models.find_plugins_dir()
310            title = "New Plugin Model Function"
311            self.new_model_frame = EditorWindow(parent=self, base=self,
312                                                path=dir_path, title=title)
313            self.put_icon(self.new_model_frame)
314        self.new_model_frame.Show(True)
315
316    def load_plugin_models(self, event):
317        """
318        Update of models in plugin_models folder
319        """
320        event_id = event.GetId()
321        self.update_custom_combo()
322
323    def update_custom_combo(self):
324        """
325        Update custom model list in the fitpage combo box
326        """
327        custom_model = 'Plugin Models'
328        try:
329            # Update edit menus
330            self.set_edit_menu_helper(self.parent, self.edit_custom_model)
331            self.set_edit_menu_helper(self.parent, self.delete_custom_model)
332            temp = self.fit_panel.reset_pmodel_list()
333            if temp:
334                # Set the new plugin model list for all fit pages
335                for uid, page in self.fit_panel.opened_pages.iteritems():
336                    if hasattr(page, "formfactorbox"):
337                        page.model_list_box = temp
338                        current_val = page.formfactorbox.GetLabel()
339                        #if page.plugin_rbutton.GetValue():
340                        mod_cat = page.categorybox.GetStringSelection()
341                        if mod_cat == custom_model:
342                            #pos = page.formfactorbox.GetSelection()
343                            page._show_combox_helper()
344                            new_val = page.formfactorbox.GetLabel()
345                            if current_val != new_val and new_val != '':
346                                page.formfactorbox.SetLabel(new_val)
347                            else:
348                                page.formfactorbox.SetLabel(current_val)
349        except:
350            logger.error("update_custom_combo: %s", sys.exc_value)
351
352    def set_edit_menu(self, owner):
353        """
354        Set list of the edit model menu labels
355        """
356        wx_id = wx.NewId()
357        #new_model_menu = wx.Menu()
358        self.edit_model_menu.Append(wx_id, 'New Plugin Model',
359                                   'Add a new model function')
360        wx.EVT_MENU(owner, wx_id, self.make_new_model)
361       
362        wx_id = wx.NewId()
363        self.edit_model_menu.Append(wx_id, 'Sum|Multi(p1, p2)',
364                                    'Sum of two model functions')
365        wx.EVT_MENU(owner, wx_id, self.make_sum_model)
366
367        e_id = wx.NewId()
368        self.edit_menu = wx.Menu()
369        self.edit_model_menu.AppendMenu(e_id,
370                                    'Advanced Plugin Editor', self.edit_menu)
371        self.set_edit_menu_helper(owner, self.edit_custom_model)
372
373        d_id = wx.NewId()
374        self.delete_menu = wx.Menu()
375        self.edit_model_menu.AppendMenu(d_id,
376                                        'Delete Plugin Models', self.delete_menu)
377        self.set_edit_menu_helper(owner, self.delete_custom_model)
378
379        wx_id = wx.NewId()
380        self.edit_model_menu.Append(wx_id, 'Load Plugin Models',
381          '(Re)Load all models present in user plugin_models folder')
382        wx.EVT_MENU(owner, wx_id, self.load_plugin_models)
383               
384    def set_edit_menu_helper(self, owner=None, menu=None):
385        """
386        help for setting list of the edit model menu labels
387        """
388        if menu == None:
389            menu = self.edit_custom_model
390        list_fnames = os.listdir(models.find_plugins_dir())
391        list_fnames.sort()
392        for f_item in list_fnames:
393            name = os.path.basename(f_item)
394            toks = os.path.splitext(name)
395            if toks[-1] == '.py' and not toks[0] == '__init__':
396                if menu == self.edit_custom_model:
397                    if toks[0] == 'easy_sum_of_p1_p2':
398                        continue
399                    submenu = self.edit_menu
400                else:
401                    submenu = self.delete_menu
402                has_file = False
403                for item in submenu.GetMenuItems():
404                    if name == submenu.GetLabel(item.GetId()):
405                        has_file = True
406                if not has_file:
407                    wx_id = wx.NewId()
408                    submenu.Append(wx_id, name)
409                    wx.EVT_MENU(owner, wx_id, menu)
410                    has_file = False
411
412    def put_icon(self, frame):
413        """
414        Put icon in the frame title bar
415        """
416        if hasattr(frame, "IsIconized"):
417            if not frame.IsIconized():
418                try:
419                    icon = self.parent.GetIcon()
420                    frame.SetIcon(icon)
421                except:
422                    pass
423
424    def on_add_sim_page(self, event):
425        """
426        Create a page to access simultaneous fit option
427        """
428        event_id = event.GetId()
429        caption = "Const & Simul Fit"
430        page = self.sim_page
431        if event_id == self.id_batchfit:
432            caption = "Combined Batch"
433            page = self.batch_page
434
435        def set_focus_page(page):
436            page.Show(True)
437            page.Refresh()
438            page.SetFocus()
439            #self.parent._mgr.Update()
440            msg = "%s already opened\n" % str(page.window_caption)
441            wx.PostEvent(self.parent, StatusEvent(status=msg))
442
443        if page != None:
444            return set_focus_page(page)
445        if caption == "Const & Simul Fit":
446            self.sim_page = self.fit_panel.add_sim_page(caption=caption)
447        else:
448            self.batch_page = self.fit_panel.add_sim_page(caption=caption)
449
450    def get_context_menu(self, plotpanel=None):
451        """
452        Get the context menu items available for P(r).them allow fitting option
453        for Data2D and Data1D only.
454
455        :param graph: the Graph object to which we attach the context menu
456
457        :return: a list of menu items with call-back function
458
459        :note: if Data1D was generated from Theory1D
460                the fitting option is not allowed
461
462        """
463        self.plot_panel = plotpanel
464        graph = plotpanel.graph
465        fit_option = "Select data for fitting"
466        fit_hint = "Dialog with fitting parameters "
467
468        if graph.selected_plottable not in plotpanel.plots:
469            return []
470        item = plotpanel.plots[graph.selected_plottable]
471        if item.__class__.__name__ is "Data2D":
472            if hasattr(item, "is_data"):
473                if item.is_data:
474                    return [[fit_option, fit_hint, self._onSelect]]
475                else:
476                    return []
477            return [[fit_option, fit_hint, self._onSelect]]
478        else:
479
480            # if is_data is true , this in an actual data loaded
481            #else it is a data created from a theory model
482            if hasattr(item, "is_data"):
483                if item.is_data:
484                    return [[fit_option, fit_hint, self._onSelect]]
485                else:
486                    return []
487            return [[fit_option, fit_hint, self._onSelect]]
488        return []
489
490    def get_panels(self, parent):
491        """
492        Create and return a list of panel objects
493        """
494        self.parent = parent
495        #self.parent.Bind(EVT_FITSTATE_UPDATE, self.on_set_state_helper)
496        # Creation of the fit panel
497        self.frame = MDIFrame(self.parent, None, 'None', (100, 200))
498        self.fit_panel = FitPanel(parent=self.frame, manager=self)
499        self.frame.set_panel(self.fit_panel)
500        self._frame_set_helper()
501        self.on_add_new_page(event=None)
502        #Set the manager for the main panel
503        self.fit_panel.set_manager(self)
504        # List of windows used for the perspective
505        self.perspective = []
506        self.perspective.append(self.fit_panel.window_name)
507
508        self.result_frame = MDIFrame(self.parent, None, ResultPanel.window_caption, (220, 200))
509        self.result_panel = ResultPanel(parent=self.result_frame, manager=self)
510        self.perspective.append(self.result_panel.window_name)
511
512        #index number to create random model name
513        self.index_model = 0
514        self.index_theory = 0
515        self.parent.Bind(EVT_SLICER_PANEL, self._on_slicer_event)
516        self.parent.Bind(EVT_SLICER_PARS_UPDATE, self._onEVT_SLICER_PANEL)
517
518        # CRUFT: EVT_FITTER_CHANGED is not None for bumps 0.7.5.9 and above
519        if EVT_FITTER_CHANGED is not None:
520            self.parent.Bind(EVT_FITTER_CHANGED, self.on_fitter_changed)
521        self._set_fitter_label(bumps.options.FIT_CONFIG)
522
523        #self.parent._mgr.Bind(wx.aui.EVT_AUI_PANE_CLOSE,self._onclearslicer)
524        #Create reader when fitting panel are created
525        self.state_reader = Reader(self.set_state)
526        #append that reader to list of available reader
527        loader = Loader()
528        loader.associate_file_reader(".fitv", self.state_reader)
529        #Send the fitting panel to guiframe
530        self.mypanels.append(self.fit_panel)
531        self.mypanels.append(self.result_panel)
532        return self.mypanels
533
534    def clear_panel(self):
535        """
536        """
537        self.fit_panel.clear_panel()
538
539    def delete_data(self, data):
540        """
541        delete  the given data from panel
542        """
543        self.fit_panel.delete_data(data)
544
545    def set_data(self, data_list=None):
546        """
547        receive a list of data to fit
548        """
549        if data_list is None:
550            data_list = []
551        selected_data_list = []
552        if self.batch_on:
553            self.add_fit_page(data=data_list)
554        else:
555            if len(data_list) > MAX_NBR_DATA:
556                from fitting_widgets import DataDialog
557                dlg = DataDialog(data_list=data_list, nb_data=MAX_NBR_DATA)
558                if dlg.ShowModal() == wx.ID_OK:
559                    selected_data_list = dlg.get_data()
560                dlg.Destroy()
561
562            else:
563                selected_data_list = data_list
564            try:
565                group_id = wx.NewId()
566                for data in selected_data_list:
567                    if data is not None:
568                        # 2D has no same group_id
569                        if data.__class__.__name__ == 'Data2D':
570                            group_id = wx.NewId()
571                        data.group_id = group_id
572                        if group_id not in data.list_group_id:
573                            data.list_group_id.append(group_id)
574                        self.add_fit_page(data=[data])
575            except:
576                msg = "Fitting set_data: " + str(sys.exc_value)
577                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
578
579    def set_theory(self, theory_list=None):
580        """
581        """
582        #set the model state for a given theory_state:
583        for item in theory_list:
584            try:
585                _, theory_state = item
586                self.fit_panel.set_model_state(theory_state)
587            except Exception:
588                msg = "Fitting: cannot deal with the theory received"
589                evt = StatusEvent(status=msg, info="error")
590                logger.error("set_theory " + msg + "\n" + str(sys.exc_value))
591                wx.PostEvent(self.parent, evt)
592
593    def set_state(self, state=None, datainfo=None, format=None):
594        """
595        Call-back method for the fit page state reader.
596        This method is called when a .fitv/.svs file is loaded.
597
598        : param state: PageState object
599        : param datainfo: data
600        """
601        from pagestate import PageState
602        from simfitpage import SimFitPageState
603        if isinstance(state, PageState):
604            state = state.clone()
605            self.temp_state.append(state)
606        elif isinstance(state, SimFitPageState):
607            state.load_from_save_state(self)
608        else:
609            self.temp_state = []
610        # index to start with for a new set_state
611        self.state_index = 0
612        # state file format
613        self.sfile_ext = format
614
615        self.on_set_state_helper(event=None)
616
617    def  on_set_state_helper(self, event=None):
618        """
619        Set_state_helper. This actually sets state
620        after plotting data from state file.
621
622        : event: FitStateUpdateEvent called
623            by dataloader.plot_data from guiframe
624        """
625        if len(self.temp_state) == 0:
626            if self.state_index == 0 and len(self.mypanels) <= 0 \
627            and self.sfile_ext == '.svs':
628                self.temp_state = []
629                self.state_index = 0
630            return
631
632        try:
633            # Load fitting state
634            state = self.temp_state[self.state_index]
635            #panel state should have model selection to set_state
636            if state.formfactorcombobox != None:
637                #set state
638                data = self.parent.create_gui_data(state.data)
639                data.group_id = state.data.group_id
640                self.parent.add_data(data_list={data.id: data})
641                wx.PostEvent(self.parent, NewPlotEvent(plot=data,
642                                        title=data.title))
643                #need to be fix later make sure we are sendind guiframe.data
644                #to panel
645                state.data = data
646                page = self.fit_panel.set_state(state)
647            else:
648                #just set data because set_state won't work
649                data = self.parent.create_gui_data(state.data)
650                data.group_id = state.data.group_id
651                self.parent.add_data(data_list={data.id: data})
652                wx.PostEvent(self.parent, NewPlotEvent(plot=data,
653                                        title=data.title))
654                page = self.add_fit_page([data])
655                caption = page.window_caption
656                self.store_data(uid=page.uid, data_list=page.get_data_list(),
657                        caption=caption)
658                self.mypanels.append(page)
659
660            # get ready for the next set_state
661            self.state_index += 1
662
663            #reset state variables to default when all set_state is finished.
664            if len(self.temp_state) == self.state_index:
665
666                self.temp_state = []
667                #self.state_index = 0
668                # Make sure the user sees the fitting panel after loading
669                #self.parent.set_perspective(self.perspective)
670                self.on_perspective(event=None)
671        except:
672            self.state_index = 0
673            self.temp_state = []
674            raise
675
676    def set_param2fit(self, uid, param2fit):
677        """
678        Set the list of param names to fit for fitprobelm
679        """
680        self.page_finder[uid].set_param2fit(param2fit)
681
682    def set_graph_id(self, uid, graph_id):
683        """
684        Set graph_id for fitprobelm
685        """
686        self.page_finder[uid].set_graph_id(graph_id)
687
688    def get_graph_id(self, uid):
689        """
690        Set graph_id for fitprobelm
691        """
692        return self.page_finder[uid].get_graph_id()
693
694    def save_fit_state(self, filepath, fitstate):
695        """
696        save fit page state into file
697        """
698        self.state_reader.write(filename=filepath, fitstate=fitstate)
699
700    def set_fit_weight(self, uid, flag, is2d=False, fid=None):
701        """
702        Set the fit weights of a given page for all
703        its data by default. If fid is provide then set the range
704        only for the data with fid as id
705        :param uid: id corresponding to a fit page
706        :param fid: id corresponding to a fit problem (data, model)
707        :param weight: current dy data
708        """
709        # If we are not dealing with a specific fit problem, then
710        # there is no point setting the weights.
711        if fid is None:
712            return
713        if uid in self.page_finder.keys():
714            self.page_finder[uid].set_weight(flag=flag, is2d=is2d)
715
716    def set_fit_range(self, uid, qmin, qmax, fid=None):
717        """
718        Set the fitting range of a given page for all
719        its data by default. If fid is provide then set the range
720        only for the data with fid as id
721        :param uid: id corresponding to a fit page
722        :param fid: id corresponding to a fit problem (data, model)
723        :param qmin: minimum  value of the fit range
724        :param qmax: maximum  value of the fit range
725        """
726        if uid in self.page_finder.keys():
727            self.page_finder[uid].set_range(qmin=qmin, qmax=qmax, fid=fid)
728
729    def schedule_for_fit(self, value=0, uid=None):
730        """
731        Set the fit problem field to 0 or 1 to schedule that problem to fit.
732        Schedule the specified fitproblem or get the fit problem related to
733        the current page and set value.
734        :param value: integer 0 or 1
735        :param uid: the id related to a page contaning fitting information
736        """
737        if uid in self.page_finder.keys():
738            self.page_finder[uid].schedule_tofit(value)
739
740    def get_page_finder(self):
741        """
742        return self.page_finder used also by simfitpage.py
743        """
744        return self.page_finder
745
746    def set_page_finder(self, modelname, names, values):
747        """
748        Used by simfitpage.py to reset a parameter given the string constrainst.
749
750        :param modelname: the name ot the model for with the parameter
751                            has to reset
752        :param value: can be a string in this case.
753        :param names: the paramter name
754        """
755        sim_page_id = self.sim_page.uid
756        for uid, value in self.page_finder.iteritems():
757            if uid != sim_page_id and uid != self.batch_page.uid:
758                model_list = value.get_model()
759                model = model_list[0]
760                if model.name == modelname:
761                    value.set_model_param(names, values)
762                    break
763
764    def split_string(self, item):
765        """
766        receive a word containing dot and split it. used to split parameterset
767        name into model name and parameter name example: ::
768
769            paramaterset (item) = M1.A
770            Will return model_name = M1 , parameter name = A
771
772        """
773        if item.find(".") >= 0:
774            param_names = re.split("\.", item)
775            model_name = param_names[0]
776            ##Assume max len is 3; eg., M0.radius.width
777            if len(param_names) == 3:
778                param_name = param_names[1] + "." + param_names[2]
779            else:
780                param_name = param_names[1]
781            return model_name, param_name
782
783    def on_bumps_options(self, event=None):
784        """
785        Open the bumps options panel.
786        """
787        show_fit_config(self.parent, help=self.on_help)
788
789    def on_fitter_changed(self, event):
790        self._set_fitter_label(event.config)
791
792    def _set_fitter_label(self, config):
793        self.fit_panel.parent.SetTitle(self.fit_panel.window_name
794                                       + " - Active Fitting Optimizer: "
795                                       + config.selected_name)
796
797    def on_help(self, algorithm_id):
798        _TreeLocation = "user/sasgui/perspectives/fitting/optimizer.html"
799        _anchor = "#fit-"+algorithm_id
800        DocumentationWindow(self.parent, wx.ID_ANY, _TreeLocation, _anchor, "Optimizer Help")
801
802
803    def on_fit_results(self, event=None):
804        """
805        Make the Fit Results panel visible.
806        """
807        self.result_frame.Show()
808        self.result_frame.Raise()
809
810    def on_gpu_options(self, event=None):
811        """
812        Make the Fit Results panel visible.
813        """
814        dialog = GpuOptions(None, wx.ID_ANY, "")
815        dialog.Show()
816
817    def stop_fit(self, uid):
818        """
819        Stop the fit
820        """
821        if uid in self.fit_thread_list.keys():
822            calc_fit = self.fit_thread_list[uid]
823            if calc_fit is not  None and calc_fit.isrunning():
824                calc_fit.stop()
825                msg = "Fit stop!"
826                wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
827            del self.fit_thread_list[uid]
828        #set the fit button label of page when fit stop is trigger from
829        #simultaneous fit pane
830        sim_flag = self.sim_page is not None and uid == self.sim_page.uid
831        batch_flag = self.batch_page is not None and uid == self.batch_page.uid
832        if sim_flag or batch_flag:
833            for uid, value in self.page_finder.iteritems():
834                if value.get_scheduled() == 1:
835                    if uid in self.fit_panel.opened_pages.keys():
836                        panel = self.fit_panel.opened_pages[uid]
837                        panel._on_fit_complete()
838
839    def set_smearer(self, uid, smearer, fid, qmin=None, qmax=None, draw=True,
840                    enable_smearer=False):
841        """
842        Get a smear object and store it to a fit problem of fid as id. If proper
843        flag is enable , will plot the theory with smearing information.
844
845        :param smearer: smear object to allow smearing data of id fid
846        :param enable_smearer: Define whether or not all (data, model) contained
847            in the structure of id uid will be smeared before fitting.
848        :param qmin: the maximum value of the theory plotting range
849        :param qmax: the maximum value of the theory plotting range
850        :param draw: Determine if the theory needs to be plot
851        """
852        if uid not in self.page_finder.keys():
853            return
854        self.page_finder[uid].enable_smearing(flag=enable_smearer)
855        self.page_finder[uid].set_smearer(smearer, fid=fid)
856        if draw:
857            ## draw model 1D with smeared data
858            data = self.page_finder[uid].get_fit_data(fid=fid)
859            if data is None:
860                msg = "set_mearer requires at least data.\n"
861                msg += "Got data = %s .\n" % str(data)
862                return
863                #raise ValueError, msg
864            model = self.page_finder[uid].get_model(fid=fid)
865            if model is None:
866                return
867            enable1D = issubclass(data.__class__, Data1D)
868            enable2D = issubclass(data.__class__, Data2D)
869            ## if user has already selected a model to plot
870            ## redraw the model with data smeared
871            smear = self.page_finder[uid].get_smearer(fid=fid)
872
873            # compute weight for the current data
874            weight = self.page_finder[uid].get_weight(fid=fid)
875
876            self.draw_model(model=model, data=data, page_id=uid, smearer=smear,
877                enable1D=enable1D, enable2D=enable2D,
878                qmin=qmin, qmax=qmax, weight=weight)
879
880    def _mac_sleep(self, sec=0.2):
881        """
882        Give sleep to MAC
883        """
884        if ON_MAC:
885            time.sleep(sec)
886
887    def draw_model(self, model, page_id, data=None, smearer=None,
888                   enable1D=True, enable2D=False,
889                   state=None,
890                   fid=None,
891                   toggle_mode_on=False,
892                   qmin=None, qmax=None,
893                   update_chisqr=True, weight=None, source='model'):
894        """
895        Draw model.
896
897        :param model: the model to draw
898        :param name: the name of the model to draw
899        :param data: the data on which the model is based to be drawn
900        :param description: model's description
901        :param enable1D: if true enable drawing model 1D
902        :param enable2D: if true enable drawing model 2D
903        :param qmin:  Range's minimum value to draw model
904        :param qmax:  Range's maximum value to draw model
905        :param qstep: number of step to divide the x and y-axis
906        :param update_chisqr: update chisqr [bool]
907
908        """
909        #self.weight = weight
910        if issubclass(data.__class__, Data1D) or not enable2D:
911            ## draw model 1D with no loaded data
912            self._draw_model1D(model=model,
913                               data=data,
914                               page_id=page_id,
915                               enable1D=enable1D,
916                               smearer=smearer,
917                               qmin=qmin,
918                               qmax=qmax,
919                               fid=fid,
920                               weight=weight,
921                               toggle_mode_on=toggle_mode_on,
922                               state=state,
923                               update_chisqr=update_chisqr,
924                               source=source)
925        else:
926            ## draw model 2D with no initial data
927            self._draw_model2D(model=model,
928                                page_id=page_id,
929                                data=data,
930                                enable2D=enable2D,
931                                smearer=smearer,
932                                qmin=qmin,
933                                qmax=qmax,
934                                fid=fid,
935                                weight=weight,
936                                state=state,
937                                toggle_mode_on=toggle_mode_on,
938                                update_chisqr=update_chisqr,
939                                source=source)
940
941    def onFit(self, uid):
942        """
943        Get series of data, model, associates parameters and range and send then
944        to  series of fitters. Fit data and model, display result to
945        corresponding panels.
946        :param uid: id related to the panel currently calling this fit function.
947        """
948        if uid is None: raise RuntimeError("no page to fit") # Should never happen
949
950        sim_page_uid = getattr(self.sim_page, 'uid', None)
951        batch_page_uid = getattr(self.batch_page, 'uid', None)
952
953        if uid == sim_page_uid:
954            fit_type = 'simultaneous'
955        elif uid == batch_page_uid:
956            fit_type = 'combined_batch'
957        else:
958            fit_type = 'single'
959
960        fitter_list = []
961        sim_fitter = None
962        if fit_type == 'simultaneous':
963            # for simultaneous fitting only one fitter is needed
964            sim_fitter = Fit()
965            sim_fitter.fitter_id = self.sim_page.uid
966            fitter_list.append(sim_fitter)
967
968        self.current_pg = None
969        list_page_id = []
970        fit_id = 0
971        for page_id, page_info in self.page_finder.iteritems():
972            # For simulfit (uid give with None), do for-loop
973            # if uid is specified (singlefit), do it only on the page.
974            if page_id in (sim_page_uid, batch_page_uid): continue
975            if fit_type == "single" and page_id != uid: continue
976
977            try:
978                if page_info.get_scheduled() == 1:
979                    page_info.nbr_residuals_computed = 0
980                    page = self.fit_panel.get_page_by_id(page_id)
981                    self.set_fit_weight(uid=page.uid,
982                                     flag=page.get_weight_flag(),
983                                     is2d=page._is_2D())
984                    if not page.param_toFit:
985                        msg = "No fitting parameters for %s" % page.window_caption
986                        evt = StatusEvent(status=msg, info="error", type="stop")
987                        wx.PostEvent(page.parent.parent, evt)
988                        return False
989                    if not page._update_paramv_on_fit():
990                        msg = "Fitting range or parameter values are"
991                        msg += " invalid in %s" % \
992                                    page.window_caption
993                        evt = StatusEvent(status=msg, info="error", type="stop")
994                        wx.PostEvent(page.parent.parent, evt)
995                        return False
996
997                    pars = [str(element[1]) for element in page.param_toFit]
998                    fitproblem_list = page_info.values()
999                    for fitproblem in  fitproblem_list:
1000                        if sim_fitter is None:
1001                            fitter = Fit()
1002                            fitter.fitter_id = page_id
1003                            fitter_list.append(fitter)
1004                        else:
1005                            fitter = sim_fitter
1006                        self._add_problem_to_fit(fitproblem=fitproblem,
1007                                             pars=pars,
1008                                             fitter=fitter,
1009                                             fit_id=fit_id)
1010                        fit_id += 1
1011                    list_page_id.append(page_id)
1012                    page_info.clear_model_param()
1013            except KeyboardInterrupt:
1014                msg = "Fitting terminated"
1015                evt = StatusEvent(status=msg, info="info", type="stop")
1016                wx.PostEvent(self.parent, evt)
1017                return True
1018            except:
1019                raise
1020                msg = "Fitting error: %s" % str(sys.exc_value)
1021                evt = StatusEvent(status=msg, info="error", type="stop")
1022                wx.PostEvent(self.parent, evt)
1023                return False
1024        ## If a thread is already started, stop it
1025        #if self.calc_fit!= None and self.calc_fit.isrunning():
1026        #    self.calc_fit.stop()
1027        msg = "Fitting is in progress..."
1028        wx.PostEvent(self.parent, StatusEvent(status=msg, type="progress"))
1029
1030        #Handler used to display fit message
1031        handler = ConsoleUpdate(parent=self.parent,
1032                                manager=self,
1033                                improvement_delta=0.1)
1034        self._mac_sleep(0.2)
1035
1036        # batch fit
1037        batch_inputs = {}
1038        batch_outputs = {}
1039        if fit_type == "simultaneous":
1040            page = self.sim_page
1041        elif fit_type == "combined_batch":
1042            page = self.batch_page
1043        else:
1044            page = self.fit_panel.get_page_by_id(uid)
1045        if page.batch_on:
1046            calc_fit = FitThread(handler=handler,
1047                                 fn=fitter_list,
1048                                 pars=pars,
1049                                 batch_inputs=batch_inputs,
1050                                 batch_outputs=batch_outputs,
1051                                 page_id=list_page_id,
1052                                 completefn=self._batch_fit_complete,
1053                                 reset_flag=self.batch_reset_flag)
1054        else:
1055            ## Perform more than 1 fit at the time
1056            calc_fit = FitThread(handler=handler,
1057                                    fn=fitter_list,
1058                                    batch_inputs=batch_inputs,
1059                                    batch_outputs=batch_outputs,
1060                                    page_id=list_page_id,
1061                                    updatefn=handler.update_fit,
1062                                    completefn=self._fit_completed)
1063        #self.fit_thread_list[current_page_id] = calc_fit
1064        self.fit_thread_list[uid] = calc_fit
1065        calc_fit.queue()
1066        calc_fit.ready(2.5)
1067        msg = "Fitting is in progress..."
1068        wx.PostEvent(self.parent, StatusEvent(status=msg, type="progress"))
1069
1070        return True
1071
1072    def remove_plot(self, uid, fid=None, theory=False):
1073        """
1074        remove model plot when a fit page is closed
1075        :param uid: the id related to the fitpage to close
1076        :param fid: the id of the fitproblem(data, model, range,etc)
1077        """
1078        if uid not in self.page_finder.keys():
1079            return
1080        fitproblemList = self.page_finder[uid].get_fit_problem(fid)
1081        for fitproblem in fitproblemList:
1082            data = fitproblem.get_fit_data()
1083            model = fitproblem.get_model()
1084            plot_id = None
1085            if model is not None:
1086                plot_id = data.id + model.name
1087            if theory:
1088                plot_id = data.id + model.name
1089            group_id = data.group_id
1090            wx.PostEvent(self.parent, NewPlotEvent(id=plot_id,
1091                                                   group_id=group_id,
1092                                                   action='remove'))
1093
1094    def store_data(self, uid, data_list=None, caption=None):
1095        """
1096        Recieve a list of data and store them ans well as a caption of
1097        the fit page where they come from.
1098        :param uid: if related to a fit page
1099        :param data_list: list of data to fit
1100        :param caption: caption of the window related to these data
1101        """
1102        if data_list is None:
1103            data_list = []
1104
1105        self.page_finder[uid].set_fit_data(data=data_list)
1106        if caption is not None:
1107            self.page_finder[uid].set_fit_tab_caption(caption=caption)
1108
1109    def on_add_new_page(self, event=None):
1110        """
1111        ask fit panel to create a new empty page
1112        """
1113        try:
1114            page = self.fit_panel.add_empty_page()
1115            # add data associated to the page created
1116            if page != None:
1117                evt = StatusEvent(status="Page Created", info="info")
1118                wx.PostEvent(self.parent, evt)
1119            else:
1120                msg = "Page was already Created"
1121                evt = StatusEvent(status=msg, info="warning")
1122                wx.PostEvent(self.parent, evt)
1123        except:
1124            msg = "Creating Fit page: %s" % sys.exc_value
1125            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
1126
1127    def add_fit_page(self, data):
1128        """
1129        given a data, ask to the fitting panel to create a new fitting page,
1130        get this page and store it into the page_finder of this plug-in
1131        :param data: is a list of data
1132        """
1133        page = self.fit_panel.set_data(data)
1134        # page could be None when loading state files
1135        if page == None:
1136            return page
1137        #append Data1D to the panel containing its theory
1138        #if theory already plotted
1139        if page.uid in self.page_finder:
1140            data = page.get_data()
1141            theory_data = self.page_finder[page.uid].get_theory_data(data.id)
1142            if issubclass(data.__class__, Data2D):
1143                data.group_id = wx.NewId()
1144                if theory_data is not None:
1145                    group_id = str(page.uid) + " Model1D"
1146                    wx.PostEvent(self.parent,
1147                             NewPlotEvent(group_id=group_id,
1148                                               action="delete"))
1149                    self.parent.update_data(prev_data=theory_data,
1150                                             new_data=data)
1151            else:
1152                if theory_data is not None:
1153                    group_id = str(page.uid) + " Model2D"
1154                    data.group_id = theory_data.group_id
1155                    wx.PostEvent(self.parent,
1156                             NewPlotEvent(group_id=group_id,
1157                                               action="delete"))
1158                    self.parent.update_data(prev_data=theory_data,
1159                                             new_data=data)
1160        self.store_data(uid=page.uid, data_list=page.get_data_list(),
1161                        caption=page.window_caption)
1162        if self.sim_page is not None and not self.batch_on:
1163            self.sim_page.draw_page()
1164        if self.batch_page is not None and self.batch_on:
1165            self.batch_page.draw_page()
1166
1167        return page
1168
1169    def _onEVT_SLICER_PANEL(self, event):
1170        """
1171        receive and event telling to update a panel with a name starting with
1172        event.panel_name. this method update slicer panel
1173        for a given interactor.
1174
1175        :param event: contains type of slicer , paramaters for updating
1176            the panel and panel_name to find the slicer 's panel concerned.
1177        """
1178        event.panel_name
1179        for item in self.parent.panels:
1180            name = event.panel_name
1181            if self.parent.panels[item].window_caption.startswith(name):
1182                self.parent.panels[item].set_slicer(event.type, event.params)
1183
1184        #self.parent._mgr.Update()
1185
1186    def _closed_fitpage(self, event):
1187        """
1188        request fitpanel to close a given page when its unique data is removed
1189        from the plot. close fitpage only when the a loaded data is removed
1190        """
1191        if event is None or event.data is None:
1192            return
1193        if hasattr(event.data, "is_data"):
1194            if not event.data.is_data or \
1195                event.data.__class__.__name__ == "Data1D":
1196                self.fit_panel.close_page_with_data(event.data)
1197
1198    def _reset_schedule_problem(self, value=0, uid=None):
1199        """
1200        unschedule or schedule all fitproblem to be fit
1201        """
1202        # case that uid is not specified
1203        if uid == None:
1204            for page_id in self.page_finder.keys():
1205                self.page_finder[page_id].schedule_tofit(value)
1206        # when uid is given
1207        else:
1208            if uid in self.page_finder.keys():
1209                self.page_finder[uid].schedule_tofit(value)
1210
1211    def _add_problem_to_fit(self, fitproblem, pars, fitter, fit_id):
1212        """
1213        Create and set fitter with series of data and model
1214        """
1215        data = fitproblem.get_fit_data()
1216        model = fitproblem.get_model()
1217        smearer = fitproblem.get_smearer()
1218        qmin, qmax = fitproblem.get_range()
1219
1220        #Extra list of parameters and their constraints
1221        listOfConstraint = []
1222        param = fitproblem.get_model_param()
1223        if len(param) > 0:
1224            for item in param:
1225                ## check if constraint
1226                if item[0] != None and item[1] != None:
1227                    listOfConstraint.append((item[0], item[1]))
1228        new_model = model
1229        fitter.set_model(new_model, fit_id, pars, data=data,
1230                         constraints=listOfConstraint)
1231        fitter.set_data(data=data, id=fit_id, smearer=smearer, qmin=qmin,
1232                        qmax=qmax)
1233        fitter.select_problem_for_fit(id=fit_id, value=1)
1234
1235    def _onSelect(self, event):
1236        """
1237        when Select data to fit a new page is created .Its reference is
1238        added to self.page_finder
1239        """
1240        panel = self.plot_panel
1241        if panel == None:
1242            raise ValueError, "Fitting:_onSelect: NonType panel"
1243        Plugin.on_perspective(self, event=event)
1244        self.select_data(panel)
1245
1246    def select_data(self, panel):
1247        """
1248        """
1249        for plottable in panel.graph.plottables:
1250            if plottable.__class__.__name__ in ["Data1D", "Theory1D"]:
1251                data_id = panel.graph.selected_plottable
1252                if plottable == panel.plots[data_id]:
1253                    data = plottable
1254                    self.add_fit_page(data=[data])
1255                    return
1256            else:
1257                data = plottable
1258                self.add_fit_page(data=[data])
1259
1260    def update_fit(self, result=None, msg=""):
1261        """
1262        """
1263        print "update_fit result", result
1264
1265    def _batch_fit_complete(self, result, pars, page_id,
1266                            batch_outputs, batch_inputs, elapsed=None):
1267        """
1268        Display fit result in batch
1269        :param result: list of objects received from fitters
1270        :param pars: list of  fitted parameters names
1271        :param page_id: list of page ids which called fit function
1272        :param elapsed: time spent at the fitting level
1273        """
1274        self._mac_sleep(0.2)
1275        uid = page_id[0]
1276        if uid in self.fit_thread_list.keys():
1277            del self.fit_thread_list[uid]
1278
1279        wx.CallAfter(self._update_fit_button, page_id)
1280        t1 = time.time()
1281        str_time = time.strftime("%a, %d %b %Y %H:%M:%S ", time.localtime(t1))
1282        msg = "Fit completed on %s \n" % str_time
1283        msg += "Duration time: %s s.\n" % str(elapsed)
1284        evt = StatusEvent(status=msg, info="info", type="stop")
1285        wx.PostEvent(self.parent, evt)
1286
1287        if batch_outputs is None:
1288            batch_outputs = {}
1289
1290        # format batch_outputs
1291        batch_outputs["Chi2"] = []
1292        #Don't like these loops
1293        # Need to create dictionary of all fitted parameters
1294        # since the number of parameters can differ between each fit result
1295        for list_res in result:
1296            for res in list_res:
1297                model, data = res.inputs[0]
1298                if model is not None and hasattr(model, "model"):
1299                    model = model.model
1300                #get all fittable parameters of the current model
1301                for param in  model.getParamList():
1302                    if param  not in batch_outputs.keys():
1303                        batch_outputs[param] = []
1304                for param in model.getDispParamList():
1305                    if not model.is_fittable(param) and \
1306                        param in batch_outputs.keys():
1307                        del batch_outputs[param]
1308                # Add fitted parameters and their error
1309                for param in res.param_list:
1310                    if param not in batch_outputs.keys():
1311                        batch_outputs[param] = []
1312                    err_param = "error on %s" % str(param)
1313                    if err_param not in batch_inputs.keys():
1314                        batch_inputs[err_param] = []
1315        msg = ""
1316        for list_res in result:
1317            for res in list_res:
1318                pid = res.fitter_id
1319                model, data = res.inputs[0]
1320                correct_result = False
1321                if model is not None and hasattr(model, "model"):
1322                    model = model.model
1323                if data is not None and hasattr(data, "sas_data"):
1324                    data = data.sas_data
1325
1326                is_data2d = issubclass(data.__class__, Data2D)
1327                #check consistency of arrays
1328                if not is_data2d:
1329                    if len(res.theory) == len(res.index[res.index]) and \
1330                        len(res.index) == len(data.y):
1331                        correct_result = True
1332                else:
1333                    copy_data = deepcopy(data)
1334                    new_theory = copy_data.data
1335                    new_theory[res.index] = res.theory
1336                    new_theory[res.index == False] = numpy.nan
1337                    correct_result = True
1338                #get all fittable parameters of the current model
1339                param_list = model.getParamList()
1340                for param in model.getDispParamList():
1341                    if not model.is_fittable(param) and \
1342                        param in param_list:
1343                        param_list.remove(param)
1344                if not correct_result or res.fitness is None or \
1345                    not numpy.isfinite(res.fitness) or \
1346                    numpy.any(res.pvec == None) or not \
1347                    numpy.all(numpy.isfinite(res.pvec)):
1348                    data_name = str(None)
1349                    if data is not None:
1350                        data_name = str(data.name)
1351                    model_name = str(None)
1352                    if model is not None:
1353                        model_name = str(model.name)
1354                    msg += "Data %s and Model %s did not fit.\n" % (data_name,
1355                                                                    model_name)
1356                    ERROR = numpy.NAN
1357                    cell = BatchCell()
1358                    cell.label = res.fitness
1359                    cell.value = res.fitness
1360                    batch_outputs["Chi2"].append(ERROR)
1361                    for param in param_list:
1362                        # save value of  fixed parameters
1363                        if param not in res.param_list:
1364                            batch_outputs[str(param)].append(ERROR)
1365                        else:
1366                            #save only fitted values
1367                            batch_outputs[param].append(ERROR)
1368                            batch_inputs["error on %s" % str(param)].append(ERROR)
1369                else:
1370                    # TODO: Why sometimes res.pvec comes with numpy.float64?
1371                    # probably from scipy lmfit
1372                    if res.pvec.__class__ == numpy.float64:
1373                        res.pvec = [res.pvec]
1374
1375                    cell = BatchCell()
1376                    cell.label = res.fitness
1377                    cell.value = res.fitness
1378                    batch_outputs["Chi2"].append(cell)
1379                    # add parameters to batch_results
1380                    for param in param_list:
1381                        # save value of  fixed parameters
1382                        if param not in res.param_list:
1383                            batch_outputs[str(param)].append(model.getParam(param))
1384                        else:
1385                            index = res.param_list.index(param)
1386                            #save only fitted values
1387                            batch_outputs[param].append(res.pvec[index])
1388                            if res.stderr is not None and \
1389                                len(res.stderr) == len(res.param_list):
1390                                item = res.stderr[index]
1391                                batch_inputs["error on %s" % param].append(item)
1392                            else:
1393                                batch_inputs["error on %s" % param].append('-')
1394                            model.setParam(param, res.pvec[index])
1395                #fill the batch result with emtpy value if not in the current
1396                #model
1397                EMPTY = "-"
1398                for key in batch_outputs.keys():
1399                    if key not in param_list and key not in ["Chi2", "Data"]:
1400                        batch_outputs[key].append(EMPTY)
1401
1402                self.page_finder[pid].set_batch_result(batch_inputs=batch_inputs,
1403                                                       batch_outputs=batch_outputs)
1404
1405                cpage = self.fit_panel.get_page_by_id(pid)
1406                cpage._on_fit_complete()
1407                self.page_finder[pid][data.id].set_result(res)
1408                fitproblem = self.page_finder[pid][data.id]
1409                qmin, qmax = fitproblem.get_range()
1410                plot_result = False
1411                if correct_result:
1412                    if not is_data2d:
1413                        self._complete1D(x=data.x[res.index], y=res.theory, page_id=pid,
1414                                         elapsed=None,
1415                                         index=res.index, model=model,
1416                                         weight=None, fid=data.id,
1417                                         toggle_mode_on=False, state=None,
1418                                         data=data, update_chisqr=False,
1419                                         source='fit', plot_result=plot_result)
1420                    else:
1421                        self._complete2D(image=new_theory, data=data,
1422                                         model=model,
1423                                         page_id=pid, elapsed=None,
1424                                         index=res.index,
1425                                         qmin=qmin,
1426                                         qmax=qmax, fid=data.id, weight=None,
1427                                         toggle_mode_on=False, state=None,
1428                                         update_chisqr=False,
1429                                         source='fit', plot_result=plot_result)
1430                self.on_set_batch_result(page_id=pid,
1431                                         fid=data.id,
1432                                         batch_outputs=batch_outputs,
1433                                         batch_inputs=batch_inputs)
1434
1435        evt = StatusEvent(status=msg, error="info", type="stop")
1436        wx.PostEvent(self.parent, evt)
1437        # Remove parameters that are not shown
1438        cpage = self.fit_panel.get_page_by_id(uid)
1439        tbatch_outputs = {}
1440        shownkeystr = cpage.get_copy_params()
1441        for key in batch_outputs.keys():
1442            if key in ["Chi2", "Data"] or shownkeystr.count(key) > 0:
1443                tbatch_outputs[key] = batch_outputs[key]
1444
1445        wx.CallAfter(self.parent.on_set_batch_result, tbatch_outputs,
1446                     batch_inputs, self.sub_menu)
1447
1448    def on_set_batch_result(self, page_id, fid, batch_outputs, batch_inputs):
1449        """
1450        """
1451        pid = page_id
1452        if fid not in self.page_finder[pid]:
1453            return
1454        fitproblem = self.page_finder[pid][fid]
1455        index = self.page_finder[pid].nbr_residuals_computed - 1
1456        residuals = fitproblem.get_residuals()
1457        theory_data = fitproblem.get_theory_data()
1458        data = fitproblem.get_fit_data()
1459        model = fitproblem.get_model()
1460        #fill batch result information
1461        if "Data" not in batch_outputs.keys():
1462            batch_outputs["Data"] = []
1463        from sas.sasgui.guiframe.data_processor import BatchCell
1464        cell = BatchCell()
1465        cell.label = data.name
1466        cell.value = index
1467
1468        if theory_data != None:
1469            #Suucessful fit
1470            theory_data.id = wx.NewId()
1471            theory_data.name = model.name + "[%s]" % str(data.name)
1472            if issubclass(theory_data.__class__, Data2D):
1473                group_id = wx.NewId()
1474                theory_data.group_id = group_id
1475                if group_id not in theory_data.list_group_id:
1476                    theory_data.list_group_id.append(group_id)
1477
1478            try:
1479                # associate residuals plot
1480                if issubclass(residuals.__class__, Data2D):
1481                    group_id = wx.NewId()
1482                    residuals.group_id = group_id
1483                    if group_id not in residuals.list_group_id:
1484                        residuals.list_group_id.append(group_id)
1485                batch_outputs["Chi2"][index].object = [residuals]
1486            except:
1487                pass
1488
1489        cell.object = [data, theory_data]
1490        batch_outputs["Data"].append(cell)
1491        for key, value in data.meta_data.iteritems():
1492            if key not in batch_inputs.keys():
1493                batch_inputs[key] = []
1494            #if key.lower().strip() != "loader":
1495            batch_inputs[key].append(value)
1496        param = "temperature"
1497        if hasattr(data.sample, param):
1498            if param not in  batch_inputs.keys():
1499                batch_inputs[param] = []
1500            batch_inputs[param].append(data.sample.temperature)
1501
1502    def _fit_completed(self, result, page_id, batch_outputs,
1503                       batch_inputs=None, pars=None, elapsed=None):
1504        """
1505        Display result of the fit on related panel(s).
1506        :param result: list of object generated when fit ends
1507        :param pars: list of names of parameters fitted
1508        :param page_id: list of page ids which called fit function
1509        :param elapsed: time spent at the fitting level
1510        """
1511        t1 = time.time()
1512        str_time = time.strftime("%a, %d %b %Y %H:%M:%S ", time.localtime(t1))
1513        msg = "Fit completed on %s \n" % str_time
1514        msg += "Duration time: %s s.\n" % str(elapsed)
1515        evt = StatusEvent(status=msg, info="info", type="stop")
1516        wx.PostEvent(self.parent, evt)
1517        wx.PostEvent(self.result_panel, PlotResultEvent(result=result))
1518        wx.CallAfter(self._update_fit_button, page_id)
1519        result = result[0]
1520        self.fit_thread_list = {}
1521        if page_id is None:
1522            page_id = []
1523        ## fit more than 1 model at the same time
1524        self._mac_sleep(0.2)
1525        try:
1526            index = 0
1527            # Update potential simfit page(s)
1528            if self.sim_page is not None:
1529                self.sim_page._on_fit_complete()
1530            if self.batch_page:
1531                self.batch_page._on_fit_complete()
1532            # Update all fit pages
1533            for uid in page_id:
1534                res = result[index]
1535                fit_msg = res.mesg
1536                if res.fitness is None or \
1537                    not numpy.isfinite(res.fitness) or \
1538                    numpy.any(res.pvec == None) or \
1539                    not numpy.all(numpy.isfinite(res.pvec)):
1540                    fit_msg += "\nFitting did not converge!!!"
1541                    wx.CallAfter(self._update_fit_button, page_id)
1542                else:
1543                    #set the panel when fit result are float not list
1544                    if res.pvec.__class__ == numpy.float64:
1545                        pvec = [res.pvec]
1546                    else:
1547                        pvec = res.pvec
1548                    if res.stderr.__class__ == numpy.float64:
1549                        stderr = [res.stderr]
1550                    else:
1551                        stderr = res.stderr
1552                    cpage = self.fit_panel.get_page_by_id(uid)
1553                    # Make sure we got all results
1554                    #(CallAfter is important to MAC)
1555                    try:
1556                        #if res != None:
1557                        wx.CallAfter(cpage.onsetValues, res.fitness,
1558                                     res.param_list,
1559                                     pvec, stderr)
1560                        index += 1
1561                        wx.CallAfter(cpage._on_fit_complete)
1562                    except KeyboardInterrupt:
1563                        fit_msg += "\nSingular point: Fitting stopped."
1564                    except:
1565                        fit_msg += "\nSingular point: Fitting error occurred."
1566                if fit_msg:
1567                   evt = StatusEvent(status=fit_msg, info="warning", type="stop")
1568                   wx.PostEvent(self.parent, evt)
1569
1570        except:
1571            msg = ("Fit completed but the following error occurred: %s"
1572                   % sys.exc_value)
1573            #import traceback; msg = "\n".join((traceback.format_exc(), msg))
1574            evt = StatusEvent(status=msg, info="warning", type="stop")
1575            wx.PostEvent(self.parent, evt)
1576
1577    def _update_fit_button(self, page_id):
1578        """
1579        Update Fit button when fit stopped
1580
1581        : parameter page_id: fitpage where the button is
1582        """
1583        if page_id.__class__.__name__ != 'list':
1584            page_id = [page_id]
1585        for uid in page_id:
1586            page = self.fit_panel.get_page_by_id(uid)
1587            page._on_fit_complete()
1588
1589    def _on_show_panel(self, event):
1590        """
1591        """
1592        pass
1593
1594    def on_reset_batch_flag(self, event):
1595        """
1596        Set batch_reset_flag
1597        """
1598        event.Skip()
1599        if self.menu1 == None:
1600            return
1601        menu_item = self.menu1.FindItemById(self.id_reset_flag)
1602        flag = menu_item.IsChecked()
1603        if not flag:
1604            menu_item.Check(False)
1605            self.batch_reset_flag = True
1606        else:
1607            menu_item.Check(True)
1608            self.batch_reset_flag = False
1609
1610        ## post a message to status bar
1611        msg = "Set Chain Fitting: %s" % str(not self.batch_reset_flag)
1612        wx.PostEvent(self.parent, StatusEvent(status=msg))
1613
1614
1615    def _on_slicer_event(self, event):
1616        """
1617        Receive a panel as event and send it to guiframe
1618
1619        :param event: event containing a panel
1620
1621        """
1622        if event.panel is not None:
1623            self.slicer_panels.append(event.panel)
1624            # Set group ID if available
1625            event_id = self.parent.popup_panel(event.panel)
1626            event.panel.uid = event_id
1627            self.mypanels.append(event.panel)
1628
1629    def _onclearslicer(self, event):
1630        """
1631        Clear the boxslicer when close the panel associate with this slicer
1632        """
1633        name = event.GetEventObject().frame.GetTitle()
1634        for panel in self.slicer_panels:
1635            if panel.window_caption == name:
1636
1637                for item in self.parent.panels:
1638                    if hasattr(self.parent.panels[item], "uid"):
1639                        if self.parent.panels[item].uid == panel.base.uid:
1640                            self.parent.panels[item].onClearSlicer(event)
1641                            #self.parent._mgr.Update()
1642                            break
1643                break
1644
1645    def _on_model_panel(self, evt):
1646        """
1647        react to model selection on any combo box or model menu.plot the model
1648
1649        :param evt: wx.combobox event
1650
1651        """
1652        model = evt.model
1653        uid = evt.uid
1654        qmin = evt.qmin
1655        qmax = evt.qmax
1656        caption = evt.caption
1657        enable_smearer = evt.enable_smearer
1658        if model == None:
1659            return
1660        if uid not in self.page_finder.keys():
1661            return
1662        # save the name containing the data name with the appropriate model
1663        self.page_finder[uid].set_model(model)
1664        self.page_finder[uid].enable_smearing(enable_smearer)
1665        self.page_finder[uid].set_range(qmin=qmin, qmax=qmax)
1666        self.page_finder[uid].set_fit_tab_caption(caption=caption)
1667        if self.sim_page is not None and not self.batch_on:
1668            self.sim_page.draw_page()
1669        if self.batch_page is not None and self.batch_on:
1670            self.batch_page.draw_page()
1671
1672    def _update1D(self, x, output):
1673        """
1674        Update the output of plotting model 1D
1675        """
1676        msg = "Plot updating ... "
1677        wx.PostEvent(self.parent, StatusEvent(status=msg, type="update"))
1678
1679    def create_theory_1D(self, x, y, page_id, model, data, state,
1680                         data_description, data_id, dy=None):
1681        """
1682            Create a theory object associate with an existing Data1D
1683            and add it to the data manager.
1684            @param x: x-values of the data
1685            @param y: y_values of the data
1686            @param page_id: fit page ID
1687            @param model: model used for fitting
1688            @param data: Data1D object to create the theory for
1689            @param state: model state
1690            @param data_description: title to use in the data manager
1691            @param data_id: unique data ID
1692        """
1693        new_plot = Data1D(x=x, y=y)
1694        if dy is None:
1695            new_plot.is_data = False
1696            new_plot.dy = numpy.zeros(len(y))
1697            # If this is a theory curve, pick the proper symbol to make it a curve
1698            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
1699        else:
1700            new_plot.is_data = True
1701            new_plot.dy = dy
1702        new_plot.interactive = True
1703        new_plot.dx = None
1704        new_plot.dxl = None
1705        new_plot.dxw = None
1706        _yaxis, _yunit = data.get_yaxis()
1707        _xaxis, _xunit = data.get_xaxis()
1708        new_plot.title = data.name
1709        new_plot.group_id = data.group_id
1710        if new_plot.group_id == None:
1711            new_plot.group_id = data.group_id
1712        new_plot.id = data_id
1713        # Find if this theory was already plotted and replace that plot given
1714        # the same id
1715        self.page_finder[page_id].get_theory_data(fid=data.id)
1716
1717        if data.is_data:
1718            data_name = str(data.name)
1719        else:
1720            data_name = str(model.__class__.__name__)
1721
1722        new_plot.name = data_description + " [" + data_name + "]"
1723        new_plot.xaxis(_xaxis, _xunit)
1724        new_plot.yaxis(_yaxis, _yunit)
1725        self.page_finder[page_id].set_theory_data(data=new_plot,
1726                                                  fid=data.id)
1727        self.parent.update_theory(data_id=data.id, theory=new_plot,
1728                                   state=state)
1729        return new_plot
1730
1731    def _complete1D(self, x, y, page_id, elapsed, index, model,
1732                    weight=None, fid=None,
1733                    toggle_mode_on=False, state=None,
1734                    data=None, update_chisqr=True,
1735                    source='model', plot_result=True,
1736                    unsmeared_model=None, unsmeared_data=None,
1737                    unsmeared_error=None, sq_model=None, pq_model=None):
1738        """
1739            Complete plotting 1D data
1740            @param unsmeared_model: fit model, without smearing
1741            @param unsmeared_data: data, rescaled to unsmeared model
1742            @param unsmeared_error: data error, rescaled to unsmeared model
1743        """
1744        try:
1745            numpy.nan_to_num(y)
1746            new_plot = self.create_theory_1D(x, y, page_id, model, data, state,
1747                                             data_description=model.name,
1748                                             data_id=str(page_id) + " " + data.name)
1749            if unsmeared_model is not None:
1750                self.create_theory_1D(x, unsmeared_model, page_id, model, data, state,
1751                                      data_description=model.name + " unsmeared",
1752                                      data_id=str(page_id) + " " + data.name + " unsmeared")
1753
1754                if unsmeared_data is not None and unsmeared_error is not None:
1755                    self.create_theory_1D(x, unsmeared_data, page_id, model, data, state,
1756                                          data_description="Data unsmeared",
1757                                          data_id="Data  " + data.name + " unsmeared",
1758                                          dy=unsmeared_error)
1759            # Comment this out until we can get P*S models with correctly populated parameters
1760            #if sq_model is not None and pq_model is not None:
1761            #    self.create_theory_1D(x, sq_model, page_id, model, data, state,
1762            #                          data_description=model.name + " S(q)",
1763            #                          data_id=str(page_id) + " " + data.name + " S(q)")
1764            #    self.create_theory_1D(x, pq_model, page_id, model, data, state,
1765            #                          data_description=model.name + " P(q)",
1766            #                          data_id=str(page_id) + " " + data.name + " P(q)")
1767
1768            current_pg = self.fit_panel.get_page_by_id(page_id)
1769            title = new_plot.title
1770            batch_on = self.fit_panel.get_page_by_id(page_id).batch_on
1771            if not batch_on:
1772                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1773                                            title=str(title)))
1774            elif plot_result:
1775                top_data_id = self.fit_panel.get_page_by_id(page_id).data.id
1776                if data.id == top_data_id:
1777                    wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1778                                            title=str(title)))
1779            caption = current_pg.window_caption
1780            self.page_finder[page_id].set_fit_tab_caption(caption=caption)
1781
1782            self.page_finder[page_id].set_theory_data(data=new_plot,
1783                                                      fid=data.id)
1784            if toggle_mode_on:
1785                wx.PostEvent(self.parent,
1786                             NewPlotEvent(group_id=str(page_id) + " Model2D",
1787                                          action="Hide"))
1788            else:
1789                if update_chisqr:
1790                    wx.PostEvent(current_pg,
1791                                 Chi2UpdateEvent(output=self._cal_chisqr(
1792                                                                data=data,
1793                                                                fid=fid,
1794                                                                weight=weight,
1795                                                            page_id=page_id,
1796                                                            index=index)))
1797                else:
1798                    self._plot_residuals(page_id=page_id, data=data, fid=fid,
1799                                         index=index, weight=weight)
1800
1801            msg = "Computation  completed!"
1802            wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1803        except:
1804            raise
1805
1806    def _calc_exception(self, etype, value, tb):
1807        """
1808        Handle exception from calculator by posting it as an error.
1809        """
1810        logger.error("".join(traceback.format_exception(etype, value, tb)))
1811        msg = traceback.format_exception(etype, value, tb, limit=1)
1812        evt = StatusEvent(status="".join(msg), type="stop", info="error")
1813        wx.PostEvent(self.parent, evt)
1814
1815    def _update2D(self, output, time=None):
1816        """
1817        Update the output of plotting model
1818        """
1819        msg = "Plot updating ... "
1820        wx.PostEvent(self.parent, StatusEvent(msg, type="update"))
1821
1822    def _complete2D(self, image, data, model, page_id, elapsed, index, qmin,
1823                qmax, fid=None, weight=None, toggle_mode_on=False, state=None,
1824                     update_chisqr=True, source='model', plot_result=True):
1825        """
1826        Complete get the result of modelthread and create model 2D
1827        that can be plot.
1828        """
1829        numpy.nan_to_num(image)
1830        new_plot = Data2D(image=image, err_image=data.err_data)
1831        new_plot.name = model.name + '2d'
1832        new_plot.title = "Analytical model 2D "
1833        new_plot.id = str(page_id) + " " + data.name
1834        new_plot.group_id = str(page_id) + " Model2D"
1835        new_plot.detector = data.detector
1836        new_plot.source = data.source
1837        new_plot.is_data = False
1838        new_plot.qx_data = data.qx_data
1839        new_plot.qy_data = data.qy_data
1840        new_plot.q_data = data.q_data
1841        new_plot.mask = data.mask
1842        ## plot boundaries
1843        new_plot.ymin = data.ymin
1844        new_plot.ymax = data.ymax
1845        new_plot.xmin = data.xmin
1846        new_plot.xmax = data.xmax
1847        title = data.title
1848
1849        new_plot.is_data = False
1850        if data.is_data:
1851            data_name = str(data.name)
1852        else:
1853            data_name = str(model.__class__.__name__) + '2d'
1854
1855        if len(title) > 1:
1856            new_plot.title = "Model2D for %s " % model.name + data_name
1857        new_plot.name = model.name + " [" + \
1858                                    data_name + "]"
1859        theory_data = deepcopy(new_plot)
1860
1861        self.page_finder[page_id].set_theory_data(data=theory_data,
1862                                                  fid=data.id)
1863        self.parent.update_theory(data_id=data.id,
1864                                       theory=new_plot,
1865                                       state=state)
1866        current_pg = self.fit_panel.get_page_by_id(page_id)
1867        title = new_plot.title
1868        if not source == 'fit' and plot_result:
1869            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1870                                               title=title))
1871        if toggle_mode_on:
1872            wx.PostEvent(self.parent,
1873                             NewPlotEvent(group_id=str(page_id) + " Model1D",
1874                                               action="Hide"))
1875        else:
1876            # Chisqr in fitpage
1877            if update_chisqr:
1878                wx.PostEvent(current_pg,
1879                             Chi2UpdateEvent(output=self._cal_chisqr(data=data,
1880                                                                    weight=weight,
1881                                                                    fid=fid,
1882                                                         page_id=page_id,
1883                                                         index=index)))
1884            else:
1885                self._plot_residuals(page_id=page_id, data=data, fid=fid,
1886                                      index=index, weight=weight)
1887        msg = "Computation  completed!"
1888        wx.PostEvent(self.parent, StatusEvent(status=msg, type="stop"))
1889
1890    def _draw_model2D(self, model, page_id, qmin,
1891                      qmax,
1892                      data=None, smearer=None,
1893                      description=None, enable2D=False,
1894                      state=None,
1895                      fid=None,
1896                      weight=None,
1897                      toggle_mode_on=False,
1898                       update_chisqr=True, source='model'):
1899        """
1900        draw model in 2D
1901
1902        :param model: instance of the model to draw
1903        :param description: the description of the model
1904        :param enable2D: when True allows to draw model 2D
1905        :param qmin: the minimum value to  draw model 2D
1906        :param qmax: the maximum value to draw model 2D
1907        :param qstep: the number of division of Qx and Qy of the model to draw
1908
1909        """
1910        if not enable2D:
1911            return None
1912        try:
1913            from model_thread import Calc2D
1914            ## If a thread is already started, stop it
1915            if (self.calc_2D is not None) and self.calc_2D.isrunning():
1916                self.calc_2D.stop()
1917                ## stop just raises a flag to tell the thread to kill
1918                ## itself -- see the fix in Calc1D implemented to fix
1919                ## an actual problem.  Seems the fix should also go here
1920                ## and may be the cause of other noted instabilities
1921                ##
1922                ##    -PDB August 12, 2014
1923                while self.calc_2D.isrunning():
1924                    time.sleep(0.1)
1925            self.calc_2D = Calc2D(model=model,
1926                                  data=data,
1927                                  page_id=page_id,
1928                                  smearer=smearer,
1929                                  qmin=qmin,
1930                                  qmax=qmax,
1931                                  weight=weight,
1932                                  fid=fid,
1933                                  toggle_mode_on=toggle_mode_on,
1934                                  state=state,
1935                                  completefn=self._complete2D,
1936                                  update_chisqr=update_chisqr,
1937                                  exception_handler=self._calc_exception,
1938                                  source=source)
1939            self.calc_2D.queue()
1940        except:
1941            raise
1942
1943    def _draw_model1D(self, model, page_id, data,
1944                      qmin, qmax, smearer=None,
1945                state=None,
1946                weight=None,
1947                fid=None,
1948                toggle_mode_on=False, update_chisqr=True, source='model',
1949                enable1D=True):
1950        """
1951        Draw model 1D from loaded data1D
1952
1953        :param data: loaded data
1954        :param model: the model to plot
1955
1956        """
1957        if not enable1D:
1958            return
1959        try:
1960            from model_thread import Calc1D
1961            ## If a thread is already started, stop it
1962            if (self.calc_1D is not None) and self.calc_1D.isrunning():
1963                self.calc_1D.stop()
1964                ## stop just raises the flag -- the thread is supposed to
1965                ## then kill itself but cannot.  Paul Kienzle came up with
1966                ## this fix to prevent threads from stepping on each other
1967                ## which was causing a simple custom plugin model to crash
1968                ##Sasview.
1969                ## We still don't know why the fit sometimes lauched a second
1970                ## thread -- something which should also be investigated.
1971                ## The thread approach was implemented in order to be able
1972                ## to lauch a computation in a separate thread from the GUI so
1973                ## that the GUI can still respond to user input including
1974                ## a request to stop the computation.
1975                ## It seems thus that the whole thread approach used here
1976                ## May need rethinking 
1977                ##
1978                ##    -PDB August 12, 2014
1979                while self.calc_1D.isrunning():
1980                    time.sleep(0.1)
1981            self.calc_1D = Calc1D(data=data,
1982                                  model=model,
1983                                  page_id=page_id,
1984                                  qmin=qmin,
1985                                  qmax=qmax,
1986                                  smearer=smearer,
1987                                  state=state,
1988                                  weight=weight,
1989                                  fid=fid,
1990                                  toggle_mode_on=toggle_mode_on,
1991                                  completefn=self._complete1D,
1992                                  #updatefn = self._update1D,
1993                                  update_chisqr=update_chisqr,
1994                                  exception_handler=self._calc_exception,
1995                                  source=source)
1996            self.calc_1D.queue()
1997        except:
1998            msg = " Error occurred when drawing %s Model 1D: " % model.name
1999            msg += " %s" % sys.exc_value
2000            wx.PostEvent(self.parent, StatusEvent(status=msg))
2001
2002    def _cal_chisqr(self, page_id, data, weight, fid=None, index=None):
2003        """
2004        Get handy Chisqr using the output from draw1D and 2D,
2005        instead of calling expansive CalcChisqr in guithread
2006        """
2007        try:
2008            data_copy = deepcopy(data)
2009        except:
2010            return
2011        # default chisqr
2012        chisqr = None
2013        #to compute chisq make sure data has valid data
2014        # return None if data == None
2015        if not check_data_validity(data_copy) or data_copy == None:
2016            return chisqr
2017
2018        # Get data: data I, theory I, and data dI in order
2019        if data_copy.__class__.__name__ == "Data2D":
2020            if index == None:
2021                index = numpy.ones(len(data_copy.data), dtype=bool)
2022            if weight != None:
2023                data_copy.err_data = weight
2024            # get rid of zero error points
2025            index = index & (data_copy.err_data != 0)
2026            index = index & (numpy.isfinite(data_copy.data))
2027            fn = data_copy.data[index]
2028            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2029            if theory_data == None:
2030                return chisqr
2031            gn = theory_data.data[index]
2032            en = data_copy.err_data[index]
2033        else:
2034            # 1 d theory from model_thread is only in the range of index
2035            if index == None:
2036                index = numpy.ones(len(data_copy.y), dtype=bool)
2037            if weight != None:
2038                data_copy.dy = weight
2039            if data_copy.dy == None or data_copy.dy == []:
2040                dy = numpy.ones(len(data_copy.y))
2041            else:
2042                ## Set consistently w/AbstractFitengine:
2043                # But this should be corrected later.
2044                dy = deepcopy(data_copy.dy)
2045                dy[dy == 0] = 1
2046            fn = data_copy.y[index]
2047
2048            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2049            if theory_data == None:
2050                return chisqr
2051            gn = theory_data.y
2052            en = dy[index]
2053
2054        # residual
2055        try:
2056            res = (fn - gn) / en
2057        except ValueError:
2058            print "Unmatch lengths %s, %s, %s" % (len(fn), len(gn), len(en))
2059            return
2060
2061        residuals = res[numpy.isfinite(res)]
2062        # get chisqr only w/finite
2063        chisqr = numpy.average(residuals * residuals)
2064
2065        self._plot_residuals(page_id=page_id, data=data_copy,
2066                             fid=fid,
2067                             weight=weight, index=index)
2068
2069        return chisqr
2070
2071    def _plot_residuals(self, page_id, weight, fid=None,
2072                        data=None, index=None):
2073        """
2074        Plot the residuals
2075
2076        :param data: data
2077        :param index: index array (bool)
2078        : Note: this is different from the residuals in cal_chisqr()
2079        """
2080        data_copy = deepcopy(data)
2081        # Get data: data I, theory I, and data dI in order
2082        if data_copy.__class__.__name__ == "Data2D":
2083            # build residuals
2084            residuals = Data2D()
2085            #residuals.copy_from_datainfo(data)
2086            # Not for trunk the line below, instead use the line above
2087            data_copy.clone_without_data(len(data_copy.data), residuals)
2088            residuals.data = None
2089            fn = data_copy.data
2090            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2091            gn = theory_data.data
2092            if weight == None:
2093                en = data_copy.err_data
2094            else:
2095                en = weight
2096            residuals.data = (fn - gn) / en
2097            residuals.qx_data = data_copy.qx_data
2098            residuals.qy_data = data_copy.qy_data
2099            residuals.q_data = data_copy.q_data
2100            residuals.err_data = numpy.ones(len(residuals.data))
2101            residuals.xmin = min(residuals.qx_data)
2102            residuals.xmax = max(residuals.qx_data)
2103            residuals.ymin = min(residuals.qy_data)
2104            residuals.ymax = max(residuals.qy_data)
2105            residuals.q_data = data_copy.q_data
2106            residuals.mask = data_copy.mask
2107            residuals.scale = 'linear'
2108            # check the lengths
2109            if len(residuals.data) != len(residuals.q_data):
2110                return
2111        else:
2112            # 1 d theory from model_thread is only in the range of index
2113            if data_copy.dy == None or data_copy.dy == []:
2114                dy = numpy.ones(len(data_copy.y))
2115            else:
2116                if weight == None:
2117                    dy = numpy.ones(len(data_copy.y))
2118                ## Set consitently w/AbstractFitengine:
2119                ## But this should be corrected later.
2120                else:
2121                    dy = weight
2122                dy[dy == 0] = 1
2123            fn = data_copy.y[index]
2124            theory_data = self.page_finder[page_id].get_theory_data(fid=data_copy.id)
2125            gn = theory_data.y
2126            en = dy[index]
2127            # build residuals
2128            residuals = Data1D()
2129            try:
2130                residuals.y = (fn - gn) / en
2131            except:
2132                msg = "ResidualPlot Error: different # of data points in theory"
2133                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
2134                residuals.y = (fn - gn[index]) / en
2135            residuals.x = data_copy.x[index]
2136            residuals.dy = numpy.ones(len(residuals.y))
2137            residuals.dx = None
2138            residuals.dxl = None
2139            residuals.dxw = None
2140            residuals.ytransform = 'y'
2141            # For latter scale changes
2142            residuals.xaxis('\\rm{Q} ', 'A^{-1}')
2143            residuals.yaxis('\\rm{Residuals} ', 'normalized')
2144        theory_name = str(theory_data.name.split()[0])
2145        new_plot = residuals
2146        new_plot.name = "Residuals for " + str(theory_name) + "[" + \
2147                        str(data.name) + "]"
2148        ## allow to highlight data when plotted
2149        new_plot.interactive = True
2150        ## when 2 data have the same id override the 1 st plotted
2151        new_plot.id = "res" + str(data_copy.id) + str(theory_name)
2152        ##group_id specify on which panel to plot this data
2153        group_id = self.page_finder[page_id].get_graph_id()
2154        if group_id == None:
2155            group_id = data.group_id
2156        new_plot.group_id = "res" + str(group_id)
2157        #new_plot.is_data = True
2158        ##post data to plot
2159        title = new_plot.name
2160        self.page_finder[page_id].set_residuals(residuals=new_plot,
2161                                                fid=data.id)
2162        self.parent.update_theory(data_id=data.id, theory=new_plot)
2163        batch_on = self.fit_panel.get_page_by_id(page_id).batch_on
2164        if not batch_on:
2165            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
Note: See TracBrowser for help on using the repository browser.