source: sasview/src/sans/perspectives/fitting/fitting.py @ eff93b8

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since eff93b8 was eff93b8, checked in by pkienzle, 10 years ago

change default fit engine to bumps:levenberg-marquardt

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