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

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 2ada16c was 7360816, checked in by Jae Cho <jhjcho@…>, 13 years ago

active radio buttons for single/batch modes

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