source: sasview/sansview/perspectives/fitting/fitting.py @ 11a7e11

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 11a7e11 was 4d5fa8c, checked in by Gervaise Alina <gervyh@…>, 14 years ago

display theory error

  • Property mode set to 100644
File size: 52.7 KB
Line 
1"""
2This software was developed by the University of Tennessee as part of the
3Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
4project funded by the US National Science Foundation.
5
6See the license text in license.txt
7
8copyright 2009, University of Tennessee
9"""
10import  re
11import sys, wx, logging
12import string, numpy, math
13import time
14import thread
15from copy import deepcopy
16
17from danse.common.plottools.PlotPanel import PlotPanel
18
19from sans.guiframe.dataFitting import Data2D
20from sans.guiframe.dataFitting import Data1D
21from sans.guiframe.dataFitting import Theory1D
22
23from sans.guiframe.utils import format_number
24
25from sans.guicomm.events import NewPlotEvent, StatusEvent 
26from sans.guicomm.events import EVT_SLICER_PANEL,ERR_DATA,EVT_REMOVE_DATA
27from sans.guicomm.events import EVT_SLICER_PARS_UPDATE
28
29from sans.fit.AbstractFitEngine import Model
30from sans.fit.AbstractFitEngine import FitAbort
31from console import ConsoleUpdate
32
33from fitproblem import FitProblem
34from fitpanel import FitPanel
35from fit_thread import FitThread
36import models
37import fitpage
38
39DEFAULT_BEAM = 0.005
40DEFAULT_QMIN = 0.001
41DEFAULT_QMAX = 0.13
42DEFAULT_NPTS = 50
43
44(PageInfoEvent, EVT_PAGE_INFO)   = wx.lib.newevent.NewEvent()
45
46from fitpage import Chi2UpdateEvent
47class PlotInfo:
48    """
49        store some plotting field
50    """
51    _xunit = 'A^{-1}'
52    _xaxis= "\\rm{Q}"
53    _yunit = "cm^{-1}"
54    _yaxis= "\\rm{Intensity} "
55    id = "Model"
56    group_id = "Model"
57    title= None
58    info= None
59   
60   
61class Plugin:
62    """
63        Fitting plugin is used to perform fit
64    """
65    def __init__(self):
66        ## Plug-in name
67        self.sub_menu = "Fitting"
68       
69        ## Reference to the parent window
70        self.parent = None
71        #Provide list of models existing in the application
72        self.menu_mng = models.ModelManager()
73        ## List of panels for the simulation perspective (names)
74        self.perspective = []
75        #list of panel to send to guiframe
76        self.mypanels=[]
77        # reference to the current running thread
78        self.calc_2D= None
79        self.calc_1D= None
80        self.calc_fit= None
81       
82        # Start with a good default
83        self.elapsed = 0.022
84        # the type of optimizer selected, park or scipy
85        self.fitter  = None
86        #let fit ready
87        self.fitproblem_count = None
88        #Flag to let the plug-in know that it is running stand alone
89        self.standalone=True
90        ## dictionary of page closed and id
91        self.closed_page_dict ={}
92        ## Fit engine
93        self._fit_engine = 'scipy'
94        #List of selected data
95        self.selected_data_list=[]
96        ## list of slicer panel created to display slicer parameters and results
97        self.slicer_panels=[]
98        # Log startup
99        logging.info("Fitting plug-in started")   
100        # model 2D view
101        self.model2D_id=None
102        #keep reference of the simultaneous fit page
103        self.sim_page=None
104        #dictionary containing data name and error on dy of that data
105        self.err_dy = {}
106        self.theory_data = None     
107   
108       
109    def populate_menu(self, id, owner):
110        """
111            Create a menu for the Fitting plug-in
112            @param id: id to create a menu
113            @param owner: owner of menu
114            @ return : list of information to populate the main menu
115        """
116        #Menu for fitting
117        self.menu1 = wx.Menu()
118       
119        #Set park engine
120        id3 = wx.NewId()
121        scipy_help= "Scipy Engine: Perform Simple fit. More in Help window...."
122        self.menu1.AppendCheckItem(id3, "Simple Fit  [Scipy]",scipy_help) 
123        wx.EVT_MENU(owner, id3,  self._onset_engine_scipy)
124       
125        id3 = wx.NewId()
126        park_help = "Park Engine: Perform Complex fit. More in Help window...."
127        self.menu1.AppendCheckItem(id3, "Complex Fit  [Park]",park_help) 
128        wx.EVT_MENU(owner, id3,  self._onset_engine_park)
129       
130        self.menu1.FindItemByPosition(0).Check(True)
131        self.menu1.FindItemByPosition(1).Check(False)
132           
133        self.menu1.AppendSeparator()
134        id1 = wx.NewId()
135        simul_help = "Allow to edit fit engine with multiple model and data"
136        self.menu1.Append(id1, '&Simultaneous Page',simul_help)
137        wx.EVT_MENU(owner, id1, self.on_add_sim_page)
138       
139        #menu for model
140        menu2 = wx.Menu()
141        self.menu_mng.populate_menu(menu2, owner)
142        id2 = wx.NewId()
143        owner.Bind(models.EVT_MODEL,self._on_model_menu)
144     
145        self.fit_panel.set_owner(owner)
146        self.fit_panel.set_model_list(self.menu_mng.get_model_list())
147        owner.Bind(fitpage.EVT_MODEL_BOX,self._on_model_panel)
148
149        #create  menubar items
150        return [(id, self.menu1, "Fitting")]
151               
152    def on_add_sim_page(self, event):
153        """
154            Create a page to access simultaneous fit option
155        """
156        Plugin.on_perspective(self,event=event)
157        if self.sim_page !=None:
158            msg= "Simultaneous Fit page already opened"
159            wx.PostEvent(self.parent, StatusEvent(status= msg))
160            return 
161       
162        self.sim_page= self.fit_panel.add_sim_page()
163       
164    def help(self, evt):
165        """
166            Show a general help dialog.
167            TODO: replace the text with a nice image
168        """
169       
170        from help_panel import  HelpWindow
171        frame = HelpWindow(None, -1, 'HelpWindow')   
172        frame.Show(True)
173       
174       
175    def get_context_menu(self, graph=None):
176        """
177            Get the context menu items available for P(r).them allow fitting option
178            for Data2D and Data1D only.
179           
180            @param graph: the Graph object to which we attach the context menu
181            @return: a list of menu items with call-back function
182            @note: if Data1D was generated from Theory1D 
183                    the fitting option is not allowed
184        """
185        self.graph = graph
186        fit_option = "Select data for fitting"
187        fit_hint =  "Dialog with fitting parameters "
188       
189        for item in graph.plottables:
190            if item.__class__.__name__ is "Data2D":
191               
192                if hasattr(item,"is_data"):
193                    if item.is_data:
194                        return [[fit_option, fit_hint, self._onSelect]]
195                    else:
196                        return [] 
197                return [[fit_option, fit_hint, self._onSelect]]
198            else:
199                if item.name == graph.selected_plottable :
200                    if item.name in ["$I_{obs}(q)$","$I_{fit}(q)$","$P_{fit}(r)$"]:
201                        return [] 
202                    if hasattr(item, "group_id"):
203                        # if is_data is true , this in an actual data loaded
204                        #else it is a data created from a theory model
205                        if hasattr(item,"is_data"):
206                            if item.is_data:
207                                return [[fit_option, fit_hint,
208                                          self._onSelect]]
209                            else:
210                                return [] 
211                        else:
212                           return [[fit_option, fit_hint, self._onSelect]]
213        return []   
214
215
216    def get_panels(self, parent):
217        """
218            Create and return a list of panel objects
219        """
220        self.parent = parent
221        # Creation of the fit panel
222        self.fit_panel = FitPanel(self.parent, -1)
223        #Set the manager for the main panel
224        self.fit_panel.set_manager(self)
225        # List of windows used for the perspective
226        self.perspective = []
227        self.perspective.append(self.fit_panel.window_name)
228        # take care of saving  data, model and page associated with each other
229        self.page_finder = {}
230        #index number to create random model name
231        self.index_model = 0
232        self.index_theory= 0
233        self.parent.Bind(EVT_SLICER_PANEL, self._on_slicer_event)
234        self.parent.Bind(ERR_DATA, self._on_data_error)
235        self.parent.Bind(EVT_REMOVE_DATA, self._closed_fitpage)
236        self.parent.Bind(EVT_SLICER_PARS_UPDATE, self._onEVT_SLICER_PANEL)
237        self.parent._mgr.Bind(wx.aui.EVT_AUI_PANE_CLOSE,self._onclearslicer)   
238
239       
240        #Send the fitting panel to guiframe
241        self.mypanels.append(self.fit_panel)
242       
243        return self.mypanels
244
245       
246     
247    def get_perspective(self):
248        """
249            Get the list of panel names for this perspective
250        """
251        return self.perspective
252   
253   
254    def on_perspective(self, event):
255        """
256            Call back function for the perspective menu item.
257            We notify the parent window that the perspective
258            has changed.
259        """
260        self.parent.set_perspective(self.perspective)
261   
262    def set_default_perspective(self):
263        """
264           Call back method that True to notify the parent that the current plug-in
265           can be set as default  perspective.
266           when returning False, the plug-in is not candidate for an automatic
267           default perspective setting
268        """
269        return True
270   
271    def post_init(self):
272        """
273            Post initialization call back to close the loose ends
274        """
275        # Do not show the fitting perspective immediately and
276        # let the application show the Welcome Page.
277        #self.parent.set_perspective(self.perspective)
278        pass
279
280    def copy_data(self, item, dy=None):
281        """
282            receive a data 1D and the list of errors on dy
283            and create a new data1D data
284            @param return
285        """
286        id = None
287        if hasattr(item,"id"):
288            id = item.id
289
290        data= Data1D(x=item.x, y=item.y,dx=None, dy=None)
291        data.copy_from_datainfo(item)
292        item.clone_without_data(clone=data)   
293        data.dy = deepcopy(dy)
294        data.name = item.name
295        ## allow to highlight data when plotted
296        data.interactive = deepcopy(item.interactive)
297        ## when 2 data have the same id override the 1 st plotted
298        data.id = id
299        data.group_id = item.group_id
300        return data
301   
302    def set_fit_range(self, page, qmin, qmax):
303        """
304            Set the fitting range of a given page
305        """
306        if page in self.page_finder.iterkeys():
307            fitproblem= self.page_finder[page]
308            fitproblem.set_range(qmin= qmin, qmax= qmax)
309                   
310    def schedule_for_fit(self,value=0,page=None,fitproblem =None): 
311        """
312            Set the fit problem field to 0 or 1 to schedule that problem to fit.
313            Schedule the specified fitproblem or get the fit problem related to
314            the current page and set value.
315            @param value : integer 0 or 1
316            @param fitproblem: fitproblem to schedule or not to fit
317        """   
318        if fitproblem !=None:
319            fitproblem.schedule_tofit(value)
320        else:
321            if page in self.page_finder.iterkeys():
322                fitproblem= self.page_finder[page]
323                fitproblem.schedule_tofit(value)
324         
325    def get_page_finder(self):
326        """ @return self.page_finder used also by simfitpage.py""" 
327        return self.page_finder
328   
329   
330    def set_page_finder(self,modelname,names,values):
331        """
332             Used by simfitpage.py to reset a parameter given the string constrainst.
333             @param modelname: the name ot the model for with the parameter has to reset
334             @param value: can be a string in this case.
335             @param names: the paramter name
336             @note: expecting park used for fit.
337        """ 
338        sim_page= self.sim_page
339        for page, value in self.page_finder.iteritems():
340            if page != sim_page:
341                list=value.get_model()
342                model = list[0]
343                if model.name== modelname:
344                    value.set_model_param(names,values)
345                    break
346         
347    def split_string(self,item): 
348        """
349            receive a word containing dot and split it. used to split parameterset
350            name into model name and parameter name example:
351            paramaterset (item) = M1.A
352            @return model_name =M1 , parameter name =A
353        """
354        if string.find(item,".")!=-1:
355            param_names= re.split("\.",item)
356            model_name=param_names[0]           
357            ##Assume max len is 3; eg., M0.radius.width
358            if len(param_names) == 3:
359                param_name=param_names[1]+"."+param_names[2]
360            else:
361                param_name=param_names[1]                   
362            return model_name,param_name
363       
364    def stop_fit(self):
365        """
366            Stop the fit engine
367        """
368        if self.calc_fit!= None and self.calc_fit.isrunning():
369            self.calc_fit.stop()
370            wx.PostEvent(self.parent, StatusEvent(status="Fitting  \
371                is cancelled" , type="stop"))
372           
373    def set_smearer_nodraw(self,smearer, qmin=None, qmax=None):
374        """
375            Get a smear object and store it to a fit problem
376            @param smearer: smear object to allow smearing data
377        """ 
378        self.current_pg=self.fit_panel.get_current_page()
379        self.page_finder[self.current_pg].set_smearer(smearer)
380        ## draw model 1D with smeared data
381        data =  self.page_finder[self.current_pg].get_fit_data()
382        model = self.page_finder[self.current_pg].get_model()
383        ## if user has already selected a model to plot
384        ## redraw the model with data smeared
385       
386        smear =self.page_finder[self.current_pg].get_smearer()   
387           
388    def set_smearer(self,smearer, qmin=None, qmax=None):
389        """
390            Get a smear object and store it to a fit problem
391            @param smearer: smear object to allow smearing data
392        """   
393        self.current_pg=self.fit_panel.get_current_page()
394        self.page_finder[self.current_pg].set_smearer(smearer)
395        ## draw model 1D with smeared data
396        data =  self.page_finder[self.current_pg].get_fit_data()
397        model = self.page_finder[self.current_pg].get_model()
398        ## if user has already selected a model to plot
399        ## redraw the model with data smeared
400
401        smear =self.page_finder[self.current_pg].get_smearer()
402        if model!= None:
403            self.draw_model( model=model, data= data, smearer= smear,
404                qmin= qmin, qmax= qmax)
405
406    def draw_model(self, model, data= None,smearer= None,
407                   enable1D= True, enable2D= False,
408                   qmin= DEFAULT_QMIN, qmax= DEFAULT_QMAX, qstep= DEFAULT_NPTS):
409        """
410             Draw model.
411             @param model: the model to draw
412             @param name: the name of the model to draw
413             @param data: the data on which the model is based to be drawn
414             @param description: model's description
415             @param enable1D: if true enable drawing model 1D
416             @param enable2D: if true enable drawing model 2D
417             @param qmin:  Range's minimum value to draw model
418             @param qmax:  Range's maximum value to draw model
419             @param qstep: number of step to divide the x and y-axis
420             
421        """
422
423        if data.__class__.__name__ !="Data2D":   
424            ## draw model 1D with no loaded data
425            self._draw_model1D( model= model, data= data,
426                                                    enable1D=enable1D, 
427                                                    smearer= smearer,
428                                                    qmin= qmin, qmax= qmax, qstep= qstep )
429        else:     
430            ## draw model 2D with no initial data
431             self._draw_model2D(model=model,
432                                      data = data,
433                                      enable2D= enable2D,
434                                      smearer= smearer,
435                                      qmin=qmin,
436                                      qmax=qmax,
437                                      qstep=qstep)
438           
439    def onFit(self):
440        """
441            perform fit
442        """
443        ##  count the number of fitproblem schedule to fit
444        fitproblem_count= 0
445        for value in self.page_finder.itervalues():
446            if value.get_scheduled()==1:
447                fitproblem_count += 1
448               
449        ## if simultaneous fit change automatically the engine to park
450        if fitproblem_count >1:
451            self._on_change_engine(engine='park')
452           
453        self.fitproblem_count = fitproblem_count 
454         
455        from sans.fit.Fitting import Fit
456        self.fitter= Fit(self._fit_engine)
457       
458        if self._fit_engine=="park":
459            engineType="Simultaneous Fit"
460        else:
461            engineType="Single Fit"
462           
463        fproblemId = 0
464        self.current_pg=None
465        for page, value in self.page_finder.iteritems():
466            try:
467                if value.get_scheduled()==1:
468                    #Get list of parameters name to fit
469                    pars = []
470                    templist = []
471                    templist = page.get_param_list()
472                    for element in templist:
473                        name = str(element[1])
474                        pars.append(name)
475                    #Set Engine  (model , data) related to the page on
476                    self._fit_helper( value=value,pars=pars,
477                                      id=fproblemId, title= engineType ) 
478                    fproblemId += 1 
479                    self.current_pg= page
480            except:
481                msg= "%s error: %s" % (engineType,sys.exc_value)
482                wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
483                                                      type="stop"))
484                return 
485        ## If a thread is already started, stop it
486        #if self.calc_fit!= None and self.calc_fit.isrunning():
487        #    self.calc_fit.stop()
488         #Handler used for park engine displayed message
489        handler = ConsoleUpdate(parent=self.parent,improvement_delta=0.1)
490        ## perform single fit
491        if fitproblem_count == 1:
492            calc_fit=FitThread(parent =self.parent,
493                                    handler = handler,
494                                    fn= self.fitter,
495                                   cpage=self.current_pg,
496                                   pars= pars,
497                                   updatefn=handler.update_fit,
498                                   completefn= self._single_fit_completed)
499        else:
500            ## Perform more than 1 fit at the time
501            calc_fit=FitThread(parent=self.parent,
502                                handler=handler,
503                                    fn= self.fitter,
504                                   completefn= self._simul_fit_completed,
505                                  updatefn=handler.update_fit)
506       
507        calc_fit.queue()
508        self.ready_fit(calc_fit=calc_fit)
509     
510    def ready_fit(self, calc_fit):
511        """
512        Ready for another fit
513        """
514        if self.fitproblem_count != None and self.fitproblem_count > 1:
515            calc_fit.ready(2.5)
516           
517        else:
518            time.sleep(0.4)
519           
520    def remove_plot(self, page, theory=False):
521        """
522            remove model plot when a fit page is closed
523        """
524        fitproblem = self.page_finder[page]
525        data = fitproblem.get_fit_data()
526        model = fitproblem.get_model()
527        if model is not None:
528            name = model.name
529            new_plot = Theory1D(x=[], y=[], dy=None)
530            new_plot.name = name
531            new_plot.xaxis(data._xaxis, data._xunit)
532            new_plot.yaxis(data._yaxis, data._yunit)
533            new_plot.group_id = data.group_id
534            new_plot.id = data.id + name
535            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=data.name))
536        if theory:
537            new_plot_data = Data1D(x=[], y=[], dx=None, dy=None)
538            new_plot_data.name = data.name
539            new_plot_data.xaxis(data._xaxis, data._xunit)
540            new_plot_data.yaxis(data._yaxis, data._yunit)
541            new_plot_data.group_id = data.group_id
542            new_plot_data.id = data.id
543            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot_data,
544                                                    title=data.name))
545    def create_fittable_data2D(self, data):
546        """
547            check if the current data is a data 2d and add dy to that data
548            @return Data2D
549        """
550        if data.__class__.__name__ != "Data2D":
551            raise ValueError, " create_fittable_data2D expects a Data2D"
552        ## Data2D case
553        new_data = deepcopy(data)
554        if not hasattr(data, "is_data"):
555            new_data.group_id += "data2D"
556            new_data.id +="data2D"
557            new_data.is_data = False
558            title = new_data.name
559            title += " Fit"
560            wx.PostEvent(self.parent, NewPlotEvent(plot=new_data,
561                                                    title=str(title)))
562        else:
563            new_data.is_data = True
564        return new_data
565       
566    def create_fittable_data1D(self, data):
567        """
568            check if the current data is a theory 1d and add dy to that data
569            @return Data1D
570        """
571        class_name = data.__class__.__name__
572        if not class_name in ["Data1D", "Theory1D"]:
573            raise ValueError, "create_fittable_data1D expects Data1D"
574     
575        #get the appropriate dy
576        dy = deepcopy(data.dy)
577        if len(self.err_dy) > 0:
578            if data.name in  self.err_dy.iterkeys():
579                dy = self.err_dy[data.name]   
580        if data.__class__.__name__ == "Theory1D":
581            new_data = self.copy_data(data, dy)
582            new_data.group_id = str(new_data.group_id)+"data1D"
583            new_data.id = str(new_data.id)+"data1D"
584            new_data.is_data = False
585            title = new_data.name
586            title = 'Data created from Theory'
587            wx.PostEvent(self.parent, NewPlotEvent(plot=new_data,
588                                                    title=str(title),
589                                                   reset=True))
590        else:
591            new_data = self.copy_data(data, dy) 
592            new_data.id = data.id
593            new_data.is_data = True
594        return new_data
595           
596    def add_fit_page(self, data):
597        """
598            given a data, ask to the fitting panel to create a new fitting page,
599            get this page and store it into the page_finder of this plug-in
600        """
601        try:
602            page = self.fit_panel.add_fit_page(data)
603            # add data associated to the page created
604            if page != None: 
605                page.set_data(data) 
606                #create a fitproblem storing all link to data,model,page creation
607                if not page in self.page_finder.keys():
608                    self.page_finder[page]= FitProblem()
609                ## item is almost the same as data but contains
610                ## axis info for plotting
611                #self.page_finder[page].add_plotted_data(item)
612                self.page_finder[page].add_fit_data(data)
613
614                wx.PostEvent(self.parent, StatusEvent(status="Page Created",
615                                                                info="info"))
616            else:
617                msg = "Page was already Created"
618                wx.PostEvent(self.parent, StatusEvent(status=msg, info="warning"))
619        except:
620            msg = "Creating Fit page: %s"%sys.exc_value
621            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
622            return
623
624       
625    def _onEVT_SLICER_PANEL(self, event):
626        """
627            receive and event telling to update a panel with a name starting with
628            event.panel_name. this method update slicer panel for a given interactor.
629            @param event: contains type of slicer , paramaters for updating the panel
630            and panel_name to find the slicer 's panel concerned.
631        """
632        for item in self.parent.panels:
633            if self.parent.panels[item].window_caption.startswith(event.panel_name):
634                self.parent.panels[item].set_slicer(event.type, event.params)
635               
636        self.parent._mgr.Update()
637   
638             
639    def _closed_fitpage(self, event):   
640        """
641            request fitpanel to close a given page when its unique data is removed
642            from the plot. close fitpage only when the a loaded data is removed
643        """   
644        if event is None or event.data is None:
645            return
646       
647        if hasattr(event.data,"is_data"):
648            if not event.data.is_data or event.data.__class__.__name__=="Data1D":
649                self.fit_panel.close_page_with_data(event.data) 
650       
651    def _add_page_onmenu(self, name,fitproblem=None):
652        """
653            Add name of a closed page of fitpanel in a menu
654        """
655        list = self.menu1.GetMenuItems()
656        for item in list:
657            if name == item.GetItemLabel():
658                self.closed_page_dict[name][1] = fitproblem
659               
660        if not name in self.closed_page_dict.keys():   
661            # Post paramters
662            event_id = wx.NewId()
663            self.menu1.Append(event_id, name, "Show %s fit panel" % name)
664            self.closed_page_dict[name]= [event_id, fitproblem]
665            wx.EVT_MENU(self.parent,event_id,  self._open_closed_page)
666       
667       
668    def _open_closed_page(self, event):   
669        """
670            reopen a closed page
671        """
672        for name, value in self.closed_page_dict.iteritems():
673            if event.GetId() in value:
674                id,fitproblem = value
675                if name !="Model":
676                    data= fitproblem.get_fit_data()
677                    page = self.fit_panel.add_fit_page(data= data,reset=True)
678                    if fitproblem != None:
679                        self.page_finder[page]=fitproblem
680                        if self.sim_page != None:
681                            self.sim_page.draw_page()
682                           
683                else:
684                    model = fitproblem
685                    self.fit_panel.add_model_page(model=model, topmenu=True,
686                                                  reset= True)
687                    break
688       
689       
690    def _reset_schedule_problem(self, value=0):
691        """
692             unschedule or schedule all fitproblem to be fit
693        """
694        for page, fitproblem in self.page_finder.iteritems():
695            fitproblem.schedule_tofit(value)
696           
697    def _fit_helper(self,pars,value, id, title="Single Fit " ):
698        """
699            helper for fitting
700        """
701        metadata = value.get_fit_data()
702        model = value.get_model()
703        smearer = value.get_smearer()
704        qmin , qmax = value.get_range()
705        self.fit_id =id
706        #Create list of parameters for fitting used
707        templist=[]
708       
709        try:
710            #Extra list of parameters and their constraints
711            listOfConstraint= []
712           
713            param = value.get_model_param()
714            if len(param)>0:
715                for item in param:
716                    ## check if constraint
717                    if item[0] !=None and item[1] != None:
718                        listOfConstraint.append((item[0],item[1]))
719                   
720            #Do the single fit
721            self.fitter.set_model(model, self.fit_id,
722                                   pars,constraints = listOfConstraint)
723           
724            self.fitter.set_data(data=metadata,Uid=self.fit_id,
725                                 smearer=smearer,qmin= qmin,qmax=qmax )
726           
727            self.fitter.select_problem_for_fit(Uid= self.fit_id,
728                                               value= value.get_scheduled())
729            value.clear_model_param()
730        except:
731            msg= title +" error: %s" % sys.exc_value
732            wx.PostEvent(self.parent, StatusEvent(status= msg, type="stop"))
733            return
734       
735    def _onSelect(self,event):
736        """
737            when Select data to fit a new page is created .Its reference is
738            added to self.page_finder
739        """
740        self.panel = event.GetEventObject()
741        Plugin.on_perspective(self,event=event)
742        for plottable in self.panel.graph.plottables:
743            if plottable.__class__.__name__ in ["Data1D", "Theory1D"]:
744                if plottable.name == self.panel.graph.selected_plottable:
745                    data = self.create_fittable_data1D(data=plottable)
746                    self.add_fit_page(data=data)
747                    return
748            else:
749                data = self.create_fittable_data2D(data=plottable)
750                self.add_fit_page(data=data)
751           
752    def _single_fit_completed(self,result,pars,cpage, elapsed=None):
753        """
754            Display fit result on one page of the notebook.
755            @param result: result of fit
756            @param pars: list of names of parameters fitted
757            @param current_pg: the page where information will be displayed
758            @param qmin: the minimum value of x to replot the model
759            @param qmax: the maximum value of x to replot model
760         
761        """     
762        try:
763            if result ==None:
764                msg= "Simple Fitting Stop !!!"
765                wx.PostEvent(self.parent, StatusEvent(status=msg,info="warning",
766                                                      type="stop"))
767                return
768            if not numpy.isfinite(result.fitness) or numpy.any(result.pvec ==None )or not numpy.all(numpy.isfinite(result.pvec) ):
769                msg= "Single Fitting did not converge!!!"
770                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
771                return
772            for page, value in self.page_finder.iteritems():
773                if page==cpage :
774                    model= value.get_model()
775                    break
776            param_name = []
777            i = 0
778            for name in pars:
779                param_name.append(name)
780
781            cpage.onsetValues(result.fitness,param_name, result.pvec,result.stderr)
782           
783        except:
784            msg= "Single Fit completed but Following error occurred:%s"% sys.exc_value
785            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
786                                                  type="stop"))
787            return
788       
789       
790    def _simul_fit_completed(self,result,pars=None,cpage=None, elapsed=None):
791        """
792            Parameter estimation completed,
793            display the results to the user
794            @param alpha: estimated best alpha
795            @param elapsed: computation time
796        """
797        ## fit more than 1 model at the same time
798        try:
799            msg = "" 
800            if result ==None:
801                msg= "Complex Fitting Stop !!!"
802                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
803                return
804            if not numpy.isfinite(result.fitness) or numpy.any(result.pvec ==None )or not numpy.all(numpy.isfinite(result.pvec) ):
805                msg= "Single Fitting did not converge!!!"
806                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
807                return
808             
809            for page, value in self.page_finder.iteritems():
810                """
811                if format_number(result.fitness) == page.get_chi2():
812                    #ToDo: Compare parameter inputs with outputs too.
813                    wx.PostEvent(self.parent, StatusEvent(status="%s " % msg))
814                    break     
815                """             
816                if value.get_scheduled()==1:
817                    model = value.get_model()
818                    data =  value.get_fit_data()
819                    small_param_name = []
820                    small_out = []
821                    small_cov = []
822                    i = 0
823                    #Separate result in to data corresponding to each page
824                    for p in result.parameters:
825                        model_name,param_name = self.split_string(p.name) 
826                        if model.name == model_name:
827                            p_name= model.name+"."+param_name
828                            if p.name == p_name:     
829                                if p.value != None and numpy.isfinite(p.value):
830                                    small_out.append(p.value )
831                                    small_param_name.append(param_name)
832                                    small_cov.append(p.stderr)
833
834                    # Display result on each page
835                    page.onsetValues(result.fitness, small_param_name,small_out,small_cov)
836        except:
837             msg= "Simultaneous Fit completed"
838             msg +=" but Following error occurred:%s"%sys.exc_value
839             wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
840             return 
841             
842                           
843       
844    def _on_show_panel(self, event):
845        print "_on_show_panel: fitting"
846     
847     
848    def _onset_engine_park(self,event):
849        """
850            set engine to park
851        """
852        Plugin.on_perspective(self,event=event)
853        self._on_change_engine('park')
854       
855       
856    def _onset_engine_scipy(self,event):
857        """
858            set engine to scipy
859        """
860        self._on_change_engine('scipy')
861       
862    def _on_slicer_event(self, event):
863        """
864            Receive a panel as event and send it to guiframe
865            @param event: event containing a panel
866        """
867       
868        if event.panel!=None:
869            new_panel = event.panel
870            self.slicer_panels.append(event.panel)
871            # Set group ID if available
872            event_id = self.parent.popup_panel(new_panel)
873            #self.menu3.Append(event_id, new_panel.window_caption,
874            #                 "Show %s plot panel" % new_panel.window_caption)
875            # Set UID to allow us to reference the panel later
876         
877            new_panel.uid = event_id
878            self.mypanels.append(new_panel) 
879        return 
880   
881    def _onclearslicer(self, event):
882        """
883            Clear the boxslicer when close the panel associate with this slicer
884        """
885        name =event.GetPane().caption
886   
887        for panel in self.slicer_panels:
888            if panel.window_caption==name:
889               
890                for item in self.parent.panels:
891                    if hasattr(self.parent.panels[item],"uid"):
892                        if self.parent.panels[item].uid ==panel.base.uid:
893                            self.parent.panels[item].onClearSlicer(event)
894                            self.parent._mgr.Update()
895                            break 
896                break
897       
898       
899       
900       
901    def _return_engine_type(self):
902        """
903            return the current type of engine
904        """
905        return self._fit_engine
906     
907     
908    def _on_change_engine(self, engine='park'):
909        """
910            Allow to select the type of engine to perform fit
911            @param engine: the key work of the engine
912        """
913       
914        ## saving fit engine name
915        self._fit_engine = engine
916        ## change menu item state
917        if engine=="park":
918            self.menu1.FindItemByPosition(0).Check(False)
919            self.menu1.FindItemByPosition(1).Check(True)
920        else:
921            self.menu1.FindItemByPosition(0).Check(True)
922            self.menu1.FindItemByPosition(1).Check(False)
923           
924        ## post a message to status bar
925        wx.PostEvent(self.parent, StatusEvent(status="Engine set to: %s" % self._fit_engine))
926   
927        ## Bind every open fit page with a newevent to know the current fitting engine
928        import fitpage
929        event= fitpage.FitterTypeEvent()
930        event.type = self._fit_engine
931        for key in self.page_finder.keys():
932            wx.PostEvent(key, event)
933       
934   
935    def _on_model_panel(self, evt):
936        """
937            react to model selection on any combo box or model menu.plot the model 
938            @param evt: wx.combobox event
939        """
940        model = evt.model
941        if model == None:
942            return
943        smearer = None
944        qmin = None
945        qmax = None
946        if hasattr(evt, "qmin"):
947            qmin = evt.qmin
948        if hasattr(evt, "qmax"):
949            qmax = evt.qmax
950        if hasattr(evt, "smearer"):
951            smearer = evt.smearer
952        model.origin_name = model.name
953        self.current_pg = self.fit_panel.get_current_page() 
954        ## make sure nothing is done on self.sim_page
955        ## example trying to call set_panel on self.sim_page
956        if self.current_pg != self.sim_page :
957            if self.page_finder[self.current_pg].get_model()== None :
958               
959                model.name = "M"+str(self.index_model)
960                self.index_model += 1 
961            else:
962                model.name= self.page_finder[self.current_pg].get_model().name
963           
964            data = self.page_finder[self.current_pg].get_fit_data()
965           
966            # save the name containing the data name with the appropriate model
967            self.page_finder[self.current_pg].set_model(model)
968            qmin, qmax= self.current_pg.get_range()
969            self.page_finder[self.current_pg].set_range(qmin=qmin, qmax=qmax)
970            smearer=  self.page_finder[self.current_pg].get_smearer()
971            # save model name
972            self.set_smearer(smearer=smearer, qmin=qmin, qmax=qmax)
973           
974            if self.sim_page!=None:
975                self.sim_page.draw_page()
976       
977    def _on_model_menu(self, evt):
978        """
979            Plot a theory from a model selected from the menu
980            @param evt: wx.menu event
981        """
982        model = evt.model
983        Plugin.on_perspective(self,event=evt)
984        # Create a model page. If a new page is created, the model
985        # will be plotted automatically. If a page already exists,
986        # the content will be updated and the plot refreshed
987        self.fit_panel.add_model_page(model,topmenu=True)
988   
989   
990   
991   
992    def _update1D(self,x, output):
993        """
994            Update the output of plotting model 1D
995        """
996        wx.PostEvent(self.parent, StatusEvent(status="Plot \
997        #updating ... ",type="update"))
998        self.ready_fit()
999        #self.calc_thread.ready(0.01)
1000   
1001   
1002    def _fill_default_model2D(self, theory, qmax,qstep, qmin=None):
1003        """
1004            fill Data2D with default value
1005            @param theory: Data2D to fill
1006        """
1007        from DataLoader.data_info import Detector, Source
1008       
1009        detector = Detector()
1010        theory.detector.append(detector)         
1011        theory.source= Source()
1012       
1013        ## Default values   
1014        theory.detector[0].distance= 8000   # mm       
1015        theory.source.wavelength= 6         # A     
1016        theory.detector[0].pixel_size.x= 5  # mm
1017        theory.detector[0].pixel_size.y= 5  # mm
1018       
1019        theory.detector[0].beam_center.x= qmax
1020        theory.detector[0].beam_center.y= qmax
1021       
1022       
1023        ## create x_bins and y_bins of the model 2D
1024        pixel_width_x = theory.detector[0].pixel_size.x
1025        pixel_width_y = theory.detector[0].pixel_size.y
1026        center_x      = theory.detector[0].beam_center.x/pixel_width_x
1027        center_y      = theory.detector[0].beam_center.y/pixel_width_y
1028
1029        # theory default: assume the beam center is located at the center of sqr detector
1030        xmax = qmax
1031        xmin = -qmax
1032        ymax = qmax
1033        ymin = -qmax
1034       
1035        x=  numpy.linspace(start= -1*qmax,
1036                               stop= qmax,
1037                               num= qstep,
1038                               endpoint=True ) 
1039        y = numpy.linspace(start= -1*qmax,
1040                               stop= qmax,
1041                               num= qstep,
1042                               endpoint=True )
1043         
1044        ## use data info instead
1045        new_x = numpy.tile(x, (len(y),1))
1046        new_y = numpy.tile(y, (len(x),1))
1047        new_y = new_y.swapaxes(0,1)
1048       
1049        # all data reuire now in 1d array
1050        qx_data = new_x.flatten()
1051        qy_data = new_y.flatten()
1052       
1053        q_data = numpy.sqrt(qx_data*qx_data+qy_data*qy_data)
1054        # set all True (standing for unmasked) as default
1055        mask    = numpy.ones(len(qx_data), dtype = bool)
1056       
1057        # calculate the range of qx and qy: this way, it is a little more independent
1058        x_size = xmax- xmin
1059        y_size = ymax -ymin
1060       
1061        # store x and y bin centers in q space
1062        x_bins  = x
1063        y_bins  = y
1064        # bin size: x- & y-directions
1065        xstep = x_size/len(x_bins-1)
1066        ystep = y_size/len(y_bins-1)
1067       
1068        #theory.data = numpy.zeros(len(mask))
1069        theory.err_data = numpy.ones(len(mask))
1070        theory.qx_data = qx_data
1071        theory.qy_data = qy_data 
1072        theory.q_data = q_data
1073        theory.mask = mask           
1074        theory.x_bins = x_bins 
1075        theory.y_bins = y_bins   
1076       
1077        # max and min taking account of the bin sizes
1078        theory.xmin= xmin
1079        theory.xmax= xmax
1080        theory.ymin= ymin
1081        theory.ymax= ymax
1082        theory.group_id ="Model"
1083        theory.id ="Model"
1084       
1085       
1086    def _get_plotting_info(self, data=None):
1087        """
1088            get plotting info from data if data !=None
1089            else use some default
1090        """
1091        my_info = PlotInfo()
1092        if data !=None:
1093            if hasattr(data,"info"):
1094                x_name, x_units = data.get_xaxis() 
1095                y_name, y_units = data.get_yaxis() 
1096               
1097                my_info._xunit = x_units
1098                my_info._xaxis = x_name
1099                my_info._yunit = y_units
1100                my_info._yaxis = y_name
1101               
1102            my_info.title= data.name
1103            if hasattr(data, "info"):
1104                my_info.info= data.info
1105            if hasattr(data, "group_id"):
1106                my_info.group_id= data.group_id
1107       
1108        return my_info
1109               
1110               
1111    def _complete1D(self, x,y, elapsed,index,model,data=None):
1112        """
1113            Complete plotting 1D data
1114        """ 
1115        try:
1116            new_plot = Theory1D(x=x, y=y)
1117            my_info = self._get_plotting_info( data)
1118            new_plot.name = model.name
1119            new_plot.id = my_info.id
1120            new_plot.group_id = my_info.group_id
1121           
1122            new_plot.xaxis( my_info._xaxis,  my_info._xunit)
1123            new_plot.yaxis( my_info._yaxis, my_info._yunit)
1124            if data!=None:
1125                if new_plot.id == data.id:
1126                    new_plot.id += "Model"
1127                new_plot.is_data =False 
1128           
1129            title= new_plot.name
1130            # x, y are only in range of index
1131            self.theory_data = new_plot
1132            #new_plot.perspective = self.get_perspective()
1133            # Pass the reset flag to let the plotting event handler
1134            # know that we are replacing the whole plot
1135            if title== None:
1136                title = "Analytical model 1D "
1137            if data ==None:
1138                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1139                             title=str(title), reset=True))
1140            else:
1141                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,title= str(title)))
1142            # Chisqr in fitpage
1143            current_pg=self.fit_panel.get_current_page()
1144            wx.PostEvent(current_pg,Chi2UpdateEvent(output=self._cal_chisqr(data=data,index=index)))
1145            msg = "Plot 1D  complete !"
1146            wx.PostEvent( self.parent, StatusEvent(status=msg , type="stop" ))
1147        except:
1148            msg= " Error occurred when drawing %s Model 1D: "%new_plot.name
1149            msg+= " %s"%sys.exc_value
1150            wx.PostEvent( self.parent, StatusEvent(status= msg, type="stop"))
1151            return 
1152                 
1153    def _update2D(self, output,time=None):
1154        """
1155            Update the output of plotting model
1156        """
1157        wx.PostEvent(self.parent, StatusEvent(status="Plot \
1158        #updating ... ",type="update"))
1159        self.ready_fit()
1160        #self.calc_thread.ready(0.01)
1161       
1162       
1163    def _complete2D(self, image,data, model,  elapsed,index,qmin, qmax,qstep=DEFAULT_NPTS):
1164        """
1165            Complete get the result of modelthread and create model 2D
1166            that can be plot.
1167        """
1168        err_image = numpy.zeros(numpy.shape(image))
1169       
1170        theory= Data2D(image= image , err_image= err_image)
1171        theory.name= model.name
1172       
1173        if data ==None:
1174            self._fill_default_model2D(theory= theory, qmax=qmax,qstep=qstep, qmin= qmin)
1175       
1176        else:
1177            theory.id= data.id+"Model"
1178            theory.group_id= data.name+"Model"
1179            theory.x_bins= data.x_bins
1180            theory.y_bins= data.y_bins
1181            theory.detector= data.detector
1182            theory.source= data.source
1183            theory.is_data =False 
1184            theory.qx_data = data.qx_data
1185            theory.qy_data = data.qy_data
1186            theory.q_data = data.q_data
1187            theory.err_data = data.err_data
1188            theory.mask = data.mask
1189            ## plot boundaries
1190            theory.ymin= data.ymin
1191            theory.ymax= data.ymax
1192            theory.xmin= data.xmin
1193            theory.xmax= data.xmax
1194           
1195        self.theory_data = theory
1196        ## plot
1197        wx.PostEvent(self.parent, NewPlotEvent(plot=theory,
1198                         title="Analytical model 2D ", reset=True ))
1199        # Chisqr in fitpage
1200        current_pg=self.fit_panel.get_current_page()
1201        wx.PostEvent(current_pg,Chi2UpdateEvent(output=self._cal_chisqr(data=data,index=index)))
1202        msg = "Plot 2D complete !"
1203        wx.PostEvent( self.parent, StatusEvent( status= msg , type="stop" ))
1204     
1205    def _on_data_error(self, event):
1206        """
1207            receives and event from plotting plu-gins to store the data name and
1208            their errors of y coordinates for 1Data hide and show error
1209        """
1210        self.err_dy = event.err_dy
1211         
1212    def _draw_model2D(self,model,data=None, smearer= None,description=None, enable2D=False,
1213                      qmin=DEFAULT_QMIN, qmax=DEFAULT_QMAX, qstep=DEFAULT_NPTS):
1214        """
1215            draw model in 2D
1216            @param model: instance of the model to draw
1217            @param description: the description of the model
1218            @param enable2D: when True allows to draw model 2D
1219            @param qmin: the minimum value to  draw model 2D
1220            @param qmax: the maximum value to draw model 2D
1221            @param qstep: the number of division of Qx and Qy of the model to draw
1222           
1223        """
1224        x=  numpy.linspace(start= -1*qmax,
1225                               stop= qmax,
1226                               num= qstep,
1227                               endpoint=True ) 
1228        y = numpy.linspace(start= -1*qmax,
1229                               stop= qmax,
1230                               num= qstep,
1231                               endpoint=True )
1232        ## use data info instead
1233        if data !=None:
1234            ## check if data2D to plot
1235            if hasattr(data, "x_bins"):
1236                enable2D = True
1237                x= data.x_bins
1238                y= data.y_bins
1239               
1240        if not enable2D:
1241            return None,None
1242        try:
1243            from model_thread import Calc2D
1244            ## If a thread is already started, stop it
1245            if self.calc_2D != None and self.calc_2D.isrunning():
1246                self.calc_2D.stop()
1247
1248            self.calc_2D = Calc2D(  x= x,
1249                                    y= y,
1250                                    model= model, 
1251                                    data = data,
1252                                    smearer = smearer,
1253                                    qmin= qmin,
1254                                    qmax= qmax,
1255                                    qstep= qstep,
1256                                    completefn= self._complete2D,
1257                                    updatefn= self._update2D )
1258            self.calc_2D.queue()
1259
1260        except:
1261            msg= " Error occurred when drawing %s Model 2D: "%model.name
1262            msg+= " %s"%sys.exc_value
1263            wx.PostEvent( self.parent, StatusEvent(status= msg ))
1264
1265   
1266    def _draw_model1D(self, model, data=None, smearer= None,
1267                qmin=DEFAULT_QMIN, qmax=DEFAULT_QMAX, qstep= DEFAULT_NPTS,enable1D= True):
1268        """
1269            Draw model 1D from loaded data1D
1270            @param data: loaded data
1271            @param model: the model to plot
1272        """
1273        x=  numpy.linspace(start= qmin,
1274                           stop= qmax,
1275                           num= qstep,
1276                           endpoint=True
1277                           )
1278        if data!=None:
1279            ## check for data2D
1280            if hasattr(data,"x_bins"):
1281                return
1282            x = data.x
1283            if qmin == DEFAULT_QMIN :
1284                qmin = min(data.x)
1285            if qmax == DEFAULT_QMAX:
1286                qmax = max(data.x) 
1287           
1288       
1289        if not enable1D:
1290            return 
1291   
1292        try:
1293            from model_thread import Calc1D
1294            ## If a thread is already started, stop it
1295            if self.calc_1D!= None and self.calc_1D.isrunning():
1296                self.calc_1D.stop()
1297            self.calc_1D= Calc1D( x= x,
1298                                  data = data,
1299                                  model= model, 
1300                                  qmin = qmin,
1301                                  qmax = qmax,
1302                                  smearer = smearer,
1303                                  completefn = self._complete1D,
1304                                  updatefn = self._update1D  )
1305            self.calc_1D.queue()
1306
1307        except:
1308            msg= " Error occurred when drawing %s Model 1D: "%model.name
1309            msg+= " %s"%sys.exc_value
1310            wx.PostEvent( self.parent, StatusEvent(status= msg ))
1311
1312    def _cal_chisqr(self, data=None, index=None): 
1313        """
1314            Get handy Chisqr using the output from draw1D and 2D,
1315            instead of calling expansive CalcChisqr in guithread
1316        """
1317        # default chisqr
1318        chisqr = None
1319       
1320        # return None if data == None
1321        if data == None: return chisqr
1322       
1323        # Get data: data I, theory I, and data dI in order
1324        if data.__class__.__name__ =="Data2D":
1325            if index == None: index = numpy.ones(len(data.data),ntype=bool)
1326            index = index & (data.err_data !=0 )   # get rid of zero error points
1327            fn = data.data[index] 
1328            gn = self.theory_data.data[index]
1329            en = data.err_data[index]
1330        else:
1331            # 1 d theory from model_thread is only in the range of index
1332            if index == None: index = numpy.ones(len(data.y),ntype=bool)
1333            if data.dy== None or data.dy ==[]:
1334                dy = numpy.ones(len(data.y))
1335            else:
1336                ## Set consitently w/AbstractFitengine: But this should be corrected later.
1337                dy = data.dy
1338                dy[dy==0] = 1 
1339            fn = data.y[index] 
1340            gn = self.theory_data.y
1341            en = dy[index]
1342
1343        # residual
1344        res = (fn - gn)/en
1345        # get chisqr only w/finite
1346        chisqr = numpy.average(res[numpy.isfinite(res)]*res[numpy.isfinite(res)])
1347
1348        return chisqr
1349   
1350   
1351def profile(fn, *args, **kw):
1352    import cProfile, pstats, os
1353    global call_result
1354    def call():
1355        global call_result
1356        call_result = fn(*args, **kw)
1357    cProfile.runctx('call()', dict(call=call), {}, 'profile.out')
1358    stats = pstats.Stats('profile.out')
1359    #stats.sort_stats('time')
1360    stats.sort_stats('calls')
1361    stats.print_stats()
1362    os.unlink('profile.out')
1363    return call_result
1364if __name__ == "__main__":
1365    i = Plugin()
1366   
1367   
1368   
1369   
Note: See TracBrowser for help on using the repository browser.