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

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 f4167637 was 9afebe1, checked in by butler, 10 years ago

patch to Calc1D and Calc2D to deal with problem of threads not handling a stop thread properly causing 2 threads to trample on each other. Thread issues in general may be at the heart of many of our instabilities and should be investigated further.

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