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

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

working on guiframe loading data for fitting

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