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

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

sum model editot can handle multi model too.

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