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

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 df7a7e3 was df7a7e3, checked in by Mathieu Doucet <doucetm@…>, 12 years ago

merging category branch

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