source: sasview/sansview/perspectives/fitting/fitting.py @ 5536035

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

working on save option

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