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

Last change on this file since e4fe091 was 33844bf, checked in by wojciech, 8 years ago

Merged with master

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