source: sasview/fittingview/src/sans/perspectives/fitting/fitting.py @ fad6056

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 fad6056 was 2d9c7266, checked in by Jae Cho <jhjcho@…>, 12 years ago

change unnecessary error msg to warning msg on fit_complete

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