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

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 56acb5d was 19e614a, checked in by Jae Cho <jhjcho@…>, 13 years ago

let users be able to delete default custom models (WIN app only)

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