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

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

working on save state

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