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

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

mdi frames for main applications

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