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

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

Merge 2.1.1 into trunk

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