source: sasview/src/sas/perspectives/fitting/fitting.py @ 22ae2f7

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 22ae2f7 was fd5ac0d, checked in by krzywon, 10 years ago

I have completed the removal of all SANS references.
I will build, run, and run all unit tests before pushing.

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