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

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 543d1bd was d2843a9, checked in by Jae Cho <jhjcho@…>, 13 years ago

ordered edit model menu

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