source: sasview/sansview/perspectives/fitting/fitting.py @ 1b43306

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

working on documentation

  • Property mode set to 100644
File size: 53.2 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        owner.Bind(fitpage.EVT_MODEL_BOX,self._on_model_panel)
164
165        #create  menubar items
166        return [(id, self.menu1, "Fitting")]
167               
168    def on_add_sim_page(self, event):
169        """
170        Create a page to access simultaneous fit option
171        """
172        Plugin.on_perspective(self,event=event)
173        if self.sim_page !=None:
174            msg= "Simultaneous Fit page already opened"
175            wx.PostEvent(self.parent, StatusEvent(status= msg))
176            return 
177       
178        self.sim_page= self.fit_panel.add_sim_page()
179       
180    def help(self, evt):
181        """
182        Show a general help dialog.
183        """
184       
185        from help_panel import  HelpWindow
186        frame = HelpWindow(None, -1, 'HelpWindow')   
187        frame.Show(True)
188       
189    def get_context_menu(self, graph=None):
190        """
191        Get the context menu items available for P(r).them allow fitting option
192        for Data2D and Data1D only.
193       
194        :param graph: the Graph object to which we attach the context menu
195       
196        :return: a list of menu items with call-back function
197       
198        :note: if Data1D was generated from Theory1D 
199                the fitting option is not allowed
200               
201        """
202        self.graph = graph
203        fit_option = "Select data for fitting"
204        fit_hint =  "Dialog with fitting parameters "
205       
206        for item in graph.plottables:
207            if item.__class__.__name__ is "Data2D":
208               
209                if hasattr(item,"is_data"):
210                    if item.is_data:
211                        return [[fit_option, fit_hint, self._onSelect]]
212                    else:
213                        return [] 
214                return [[fit_option, fit_hint, self._onSelect]]
215            else:
216                if item.name == graph.selected_plottable :
217                    if item.name in ["$I_{obs}(q)$","$I_{fit}(q)$","$P_{fit}(r)$"]:
218                        return [] 
219                    if hasattr(item, "group_id"):
220                        # if is_data is true , this in an actual data loaded
221                        #else it is a data created from a theory model
222                        if hasattr(item,"is_data"):
223                            if item.is_data:
224                                return [[fit_option, fit_hint,
225                                          self._onSelect]]
226                            else:
227                                return [] 
228                        else:
229                           return [[fit_option, fit_hint, self._onSelect]]
230        return []   
231
232
233    def get_panels(self, parent):
234        """
235        Create and return a list of panel objects
236        """
237        self.parent = parent
238        # Creation of the fit panel
239        self.fit_panel = FitPanel(self.parent, -1)
240        #Set the manager for the main panel
241        self.fit_panel.set_manager(self)
242        # List of windows used for the perspective
243        self.perspective = []
244        self.perspective.append(self.fit_panel.window_name)
245        # take care of saving  data, model and page associated with each other
246        self.page_finder = {}
247        #index number to create random model name
248        self.index_model = 0
249        self.index_theory= 0
250        self.parent.Bind(EVT_SLICER_PANEL, self._on_slicer_event)
251        self.parent.Bind(ERR_DATA, self._on_data_error)
252        self.parent.Bind(EVT_REMOVE_DATA, self._closed_fitpage)
253        self.parent.Bind(EVT_SLICER_PARS_UPDATE, self._onEVT_SLICER_PANEL)
254        self.parent._mgr.Bind(wx.aui.EVT_AUI_PANE_CLOSE,self._onclearslicer)   
255        #Create reader when fitting panel are created
256        self.state_reader = Reader(self.set_state)   
257        #append that reader to list of available reader
258        loader = Loader()
259        loader.associate_file_reader(".fitv", self.state_reader)
260        #Send the fitting panel to guiframe
261        self.mypanels.append(self.fit_panel)
262        return self.mypanels
263   
264    def get_perspective(self):
265        """
266        Get the list of panel names for this perspective
267        """
268        return self.perspective
269   
270    def on_perspective(self, event):
271        """
272        Call back function for the perspective menu item.
273        We notify the parent window that the perspective
274        has changed.
275        """
276        self.parent.set_perspective(self.perspective)
277   
278    def set_default_perspective(self):
279        """
280        Call back method that True to notify the parent that the current plug-in
281        can be set as default  perspective.
282        when returning False, the plug-in is not candidate for an automatic
283        default perspective setting
284        """
285        return True
286   
287    def post_init(self):
288        """
289        Post initialization call back to close the loose ends
290        """
291        pass
292   
293    def set_state(self, state, datainfo=None):
294        """
295        Call-back method for the fit page state reader.
296        This method is called when a .fitv file is loaded.
297       
298        :param state: PageState object
299       
300        """
301        #working on reading state
302        return 
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            # add data associated to the page created
670            if page != None: 
671                self.store_page(page=page, data=data)
672                wx.PostEvent(self.parent, StatusEvent(status="Page Created",
673                                               info="info"))
674            else:
675                msg = "Page was already Created"
676                wx.PostEvent(self.parent, StatusEvent(status=msg, info="warning"))
677        except:
678            msg = "Creating Fit page: %s"%sys.exc_value
679            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
680       
681    def _onEVT_SLICER_PANEL(self, event):
682        """
683        receive and event telling to update a panel with a name starting with
684        event.panel_name. this method update slicer panel for a given interactor.
685       
686        :param event: contains type of slicer , paramaters for updating the panel
687            and panel_name to find the slicer 's panel concerned.
688        """
689        for item in self.parent.panels:
690            if self.parent.panels[item].window_caption.startswith(event.panel_name):
691                self.parent.panels[item].set_slicer(event.type, event.params)
692               
693        self.parent._mgr.Update()
694   
695    def _closed_fitpage(self, event):   
696        """
697        request fitpanel to close a given page when its unique data is removed
698        from the plot. close fitpage only when the a loaded data is removed
699        """   
700        if event is None or event.data is None:
701            return
702       
703        if hasattr(event.data,"is_data"):
704            if not event.data.is_data or event.data.__class__.__name__=="Data1D":
705                self.fit_panel.close_page_with_data(event.data) 
706       
707    def _add_page_onmenu(self, name,fitproblem=None):
708        """
709        Add name of a closed page of fitpanel in a menu
710        """
711        list = self.menu1.GetMenuItems()
712        for item in list:
713            if name == item.GetItemLabel():
714                self.closed_page_dict[name][1] = fitproblem
715               
716        if not name in self.closed_page_dict.keys():   
717            # Post paramters
718            event_id = wx.NewId()
719            self.menu1.Append(event_id, name, "Show %s fit panel" % name)
720            self.closed_page_dict[name]= [event_id, fitproblem]
721            wx.EVT_MENU(self.parent,event_id,  self._open_closed_page)
722       
723    def _open_closed_page(self, event):   
724        """
725        reopen a closed page
726        """
727        for name, value in self.closed_page_dict.iteritems():
728            if event.GetId() in value:
729                id,fitproblem = value
730                if name !="Model":
731                    data= fitproblem.get_fit_data()
732                    page = self.fit_panel.add_fit_page(data= data,reset=True)
733                    if fitproblem != None:
734                        self.page_finder[page]=fitproblem
735                        if self.sim_page != None:
736                            self.sim_page.draw_page()
737                           
738                else:
739                    model = fitproblem
740                    self.fit_panel.add_model_page(model=model, topmenu=True,
741                                                  reset= True)
742                    break
743   
744    def _reset_schedule_problem(self, value=0):
745        """
746        unschedule or schedule all fitproblem to be fit
747        """
748        for page, fitproblem in self.page_finder.iteritems():
749            fitproblem.schedule_tofit(value)
750           
751    def _fit_helper(self,pars,value, id, title="Single Fit " ):
752        """
753        helper for fitting
754        """
755        metadata = value.get_fit_data()
756        model = value.get_model()
757        smearer = value.get_smearer()
758        qmin , qmax = value.get_range()
759        self.fit_id =id
760        #Create list of parameters for fitting used
761        templist=[]
762       
763        try:
764            #Extra list of parameters and their constraints
765            listOfConstraint= []
766           
767            param = value.get_model_param()
768            if len(param)>0:
769                for item in param:
770                    ## check if constraint
771                    if item[0] !=None and item[1] != None:
772                        listOfConstraint.append((item[0],item[1]))
773                   
774            #Do the single fit
775            self.fitter.set_model(model, self.fit_id,
776                                   pars,constraints = listOfConstraint)
777           
778            self.fitter.set_data(data=metadata,Uid=self.fit_id,
779                                 smearer=smearer,qmin= qmin,qmax=qmax )
780           
781            self.fitter.select_problem_for_fit(Uid= self.fit_id,
782                                               value= value.get_scheduled())
783            value.clear_model_param()
784        except:
785            msg= title +" error: %s" % sys.exc_value
786            wx.PostEvent(self.parent, StatusEvent(status= msg, type="stop"))
787            return
788       
789    def _onSelect(self,event):
790        """
791        when Select data to fit a new page is created .Its reference is
792        added to self.page_finder
793        """
794        self.panel = event.GetEventObject()
795        Plugin.on_perspective(self,event=event)
796        for plottable in self.panel.graph.plottables:
797            if plottable.__class__.__name__ in ["Data1D", "Theory1D"]:
798                if plottable.name == self.panel.graph.selected_plottable:
799                    data = self.create_fittable_data1D(data=plottable)
800                    self.add_fit_page(data=data)
801                    return
802            else:
803                data = self.create_fittable_data2D(data=plottable)
804                self.add_fit_page(data=data)
805           
806    def _single_fit_completed(self,result,pars,cpage, elapsed=None):
807        """
808        Display fit result on one page of the notebook.
809       
810        :param result: result of fit
811        :param pars: list of names of parameters fitted
812        :param current_pg: the page where information will be displayed
813        :param qmin: the minimum value of x to replot the model
814        :param qmax: the maximum value of x to replot model
815         
816        """     
817        try:
818            if result ==None:
819                msg= "Simple Fitting Stop !!!"
820                wx.PostEvent(self.parent, StatusEvent(status=msg,info="warning",
821                                                      type="stop"))
822                return
823            if not numpy.isfinite(result.fitness) or numpy.any(result.pvec ==None )or not numpy.all(numpy.isfinite(result.pvec) ):
824                msg= "Single Fitting did not converge!!!"
825                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
826                return
827            for page, value in self.page_finder.iteritems():
828                if page==cpage :
829                    model= value.get_model()
830                    break
831            param_name = []
832            i = 0
833            for name in pars:
834                param_name.append(name)
835
836            cpage.onsetValues(result.fitness,param_name, result.pvec,result.stderr)
837           
838        except:
839            msg= "Single Fit completed but Following error occurred:%s"% sys.exc_value
840            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error",
841                                                  type="stop"))
842            return
843       
844    def _simul_fit_completed(self,result,pars=None,cpage=None, elapsed=None):
845        """
846        Parameter estimation completed,
847        display the results to the user
848       
849        :param alpha: estimated best alpha
850        :param elapsed: computation time
851       
852        """
853        ## fit more than 1 model at the same time
854        try:
855            msg = "" 
856            if result ==None:
857                msg= "Complex Fitting Stop !!!"
858                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
859                return
860            if not numpy.isfinite(result.fitness) or numpy.any(result.pvec ==None )or not numpy.all(numpy.isfinite(result.pvec) ):
861                msg= "Single Fitting did not converge!!!"
862                wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
863                return
864             
865            for page, value in self.page_finder.iteritems():   
866                if value.get_scheduled()==1:
867                    model = value.get_model()
868                    data =  value.get_fit_data()
869                    small_param_name = []
870                    small_out = []
871                    small_cov = []
872                    i = 0
873                    #Separate result in to data corresponding to each page
874                    for p in result.parameters:
875                        model_name,param_name = self.split_string(p.name) 
876                        if model.name == model_name:
877                            p_name= model.name+"."+param_name
878                            if p.name == p_name:     
879                                if p.value != None and numpy.isfinite(p.value):
880                                    small_out.append(p.value )
881                                    small_param_name.append(param_name)
882                                    small_cov.append(p.stderr)
883
884                    # Display result on each page
885                    page.onsetValues(result.fitness, small_param_name,small_out,small_cov)
886        except:
887             msg= "Simultaneous Fit completed"
888             msg +=" but Following error occurred:%s"%sys.exc_value
889             wx.PostEvent(self.parent, StatusEvent(status=msg,type="stop"))
890             return 
891             
892    def _on_show_panel(self, event):
893        """
894        """
895        #print "_on_show_panel: fitting"
896        pass
897       
898    def _onset_engine_park(self,event):
899        """
900        set engine to park
901        """
902        Plugin.on_perspective(self,event=event)
903        self._on_change_engine('park')
904       
905    def _onset_engine_scipy(self,event):
906        """
907        set engine to scipy
908        """
909        self._on_change_engine('scipy')
910       
911    def _on_slicer_event(self, event):
912        """
913        Receive a panel as event and send it to guiframe
914       
915        :param event: event containing a panel
916       
917        """
918        if event.panel is not None:
919            new_panel = event.panel
920            self.slicer_panels.append(event.panel)
921            # Set group ID if available
922            event_id = self.parent.popup_panel(new_panel)
923            #self.menu3.Append(event_id, new_panel.window_caption,
924            #                 "Show %s plot panel" % new_panel.window_caption)
925            # Set UID to allow us to reference the panel later
926         
927            new_panel.uid = event_id
928            self.mypanels.append(new_panel) 
929       
930    def _onclearslicer(self, event):
931        """
932        Clear the boxslicer when close the panel associate with this slicer
933        """
934        name =event.GetPane().caption
935   
936        for panel in self.slicer_panels:
937            if panel.window_caption==name:
938               
939                for item in self.parent.panels:
940                    if hasattr(self.parent.panels[item],"uid"):
941                        if self.parent.panels[item].uid ==panel.base.uid:
942                            self.parent.panels[item].onClearSlicer(event)
943                            self.parent._mgr.Update()
944                            break 
945                break
946   
947    def _return_engine_type(self):
948        """
949        return the current type of engine
950        """
951        return self._fit_engine
952     
953     
954    def _on_change_engine(self, engine='park'):
955        """
956        Allow to select the type of engine to perform fit
957       
958        :param engine: the key work of the engine
959       
960        """
961        ## saving fit engine name
962        self._fit_engine = engine
963        ## change menu item state
964        if engine=="park":
965            self.menu1.FindItemByPosition(0).Check(False)
966            self.menu1.FindItemByPosition(1).Check(True)
967        else:
968            self.menu1.FindItemByPosition(0).Check(True)
969            self.menu1.FindItemByPosition(1).Check(False)
970           
971        ## post a message to status bar
972        wx.PostEvent(self.parent, StatusEvent(status="Engine set to: %s" % self._fit_engine))
973   
974        ## Bind every open fit page with a newevent to know the current fitting engine
975        import fitpage
976        event= fitpage.FitterTypeEvent()
977        event.type = self._fit_engine
978        for key in self.page_finder.keys():
979            wx.PostEvent(key, event)
980       
981    def _on_model_panel(self, evt):
982        """
983        react to model selection on any combo box or model menu.plot the model 
984       
985        :param evt: wx.combobox event
986       
987        """
988        model = evt.model
989        if model == None:
990            return
991        smearer = None
992        qmin = None
993        qmax = None
994        if hasattr(evt, "qmin"):
995            qmin = evt.qmin
996        if hasattr(evt, "qmax"):
997            qmax = evt.qmax
998        if hasattr(evt, "smearer"):
999            smearer = evt.smearer
1000        model.origin_name = model.name
1001        self.current_pg = self.fit_panel.get_current_page() 
1002        ## make sure nothing is done on self.sim_page
1003        ## example trying to call set_panel on self.sim_page
1004        if self.current_pg != self.sim_page :
1005            if self.page_finder[self.current_pg].get_model()== None :
1006               
1007                model.name = "M"+str(self.index_model)
1008                self.index_model += 1 
1009            else:
1010                model.name= self.page_finder[self.current_pg].get_model().name
1011           
1012            data = self.page_finder[self.current_pg].get_fit_data()
1013           
1014            # save the name containing the data name with the appropriate model
1015            self.page_finder[self.current_pg].set_model(model)
1016            qmin, qmax= self.current_pg.get_range()
1017            self.page_finder[self.current_pg].set_range(qmin=qmin, qmax=qmax)
1018            smearer=  self.page_finder[self.current_pg].get_smearer()
1019            # save model name
1020            self.set_smearer(smearer=smearer, qmin=qmin, qmax=qmax)
1021           
1022            if self.sim_page!=None:
1023                self.sim_page.draw_page()
1024       
1025    def _on_model_menu(self, evt):
1026        """
1027        Plot a theory from a model selected from the menu
1028       
1029        :param evt: wx.menu event
1030       
1031        """
1032        model = evt.model
1033        Plugin.on_perspective(self,event=evt)
1034        # Create a model page. If a new page is created, the model
1035        # will be plotted automatically. If a page already exists,
1036        # the content will be updated and the plot refreshed
1037        self.fit_panel.add_model_page(model,topmenu=True)
1038
1039    def _update1D(self,x, output):
1040        """
1041        Update the output of plotting model 1D
1042        """
1043        wx.PostEvent(self.parent, StatusEvent(status="Plot \
1044        #updating ... ",type="update"))
1045        self.ready_fit()
1046        #self.calc_thread.ready(0.01)
1047   
1048    def _fill_default_model2D(self, theory, qmax,qstep, qmin=None):
1049        """
1050        fill Data2D with default value
1051       
1052        :param theory: Data2D to fill
1053       
1054        """
1055        from DataLoader.data_info import Detector, Source
1056       
1057        detector = Detector()
1058        theory.detector.append(detector)         
1059        theory.source= Source()
1060       
1061        ## Default values   
1062        theory.detector[0].distance= 8000   # mm       
1063        theory.source.wavelength= 6         # A     
1064        theory.detector[0].pixel_size.x= 5  # mm
1065        theory.detector[0].pixel_size.y= 5  # mm
1066       
1067        theory.detector[0].beam_center.x= qmax
1068        theory.detector[0].beam_center.y= qmax
1069   
1070        ## create x_bins and y_bins of the model 2D
1071        pixel_width_x = theory.detector[0].pixel_size.x
1072        pixel_width_y = theory.detector[0].pixel_size.y
1073        center_x      = theory.detector[0].beam_center.x/pixel_width_x
1074        center_y      = theory.detector[0].beam_center.y/pixel_width_y
1075
1076        # theory default: assume the beam center is located at the center of sqr detector
1077        xmax = qmax
1078        xmin = -qmax
1079        ymax = qmax
1080        ymin = -qmax
1081       
1082        x=  numpy.linspace(start= -1*qmax,
1083                               stop= qmax,
1084                               num= qstep,
1085                               endpoint=True ) 
1086        y = numpy.linspace(start= -1*qmax,
1087                               stop= qmax,
1088                               num= qstep,
1089                               endpoint=True )
1090         
1091        ## use data info instead
1092        new_x = numpy.tile(x, (len(y),1))
1093        new_y = numpy.tile(y, (len(x),1))
1094        new_y = new_y.swapaxes(0,1)
1095       
1096        # all data reuire now in 1d array
1097        qx_data = new_x.flatten()
1098        qy_data = new_y.flatten()
1099       
1100        q_data = numpy.sqrt(qx_data*qx_data+qy_data*qy_data)
1101        # set all True (standing for unmasked) as default
1102        mask    = numpy.ones(len(qx_data), dtype = bool)
1103       
1104        # calculate the range of qx and qy: this way, it is a little more independent
1105        x_size = xmax- xmin
1106        y_size = ymax -ymin
1107       
1108        # store x and y bin centers in q space
1109        x_bins  = x
1110        y_bins  = y
1111        # bin size: x- & y-directions
1112        xstep = x_size/len(x_bins-1)
1113        ystep = y_size/len(y_bins-1)
1114       
1115        #theory.data = numpy.zeros(len(mask))
1116        theory.err_data = numpy.ones(len(mask))
1117        theory.qx_data = qx_data
1118        theory.qy_data = qy_data 
1119        theory.q_data = q_data
1120        theory.mask = mask           
1121        theory.x_bins = x_bins 
1122        theory.y_bins = y_bins   
1123       
1124        # max and min taking account of the bin sizes
1125        theory.xmin= xmin
1126        theory.xmax= xmax
1127        theory.ymin= ymin
1128        theory.ymax= ymax
1129        theory.group_id = "Model"
1130        theory.id = "Model"
1131       
1132    def _get_plotting_info(self, data=None):
1133        """
1134        get plotting info from data if data !=None else use some default
1135        """
1136        my_info = PlotInfo()
1137        if data !=None:
1138            if hasattr(data,"info"):
1139                x_name, x_units = data.get_xaxis() 
1140                y_name, y_units = data.get_yaxis() 
1141               
1142                my_info._xunit = x_units
1143                my_info._xaxis = x_name
1144                my_info._yunit = y_units
1145                my_info._yaxis = y_name
1146               
1147            my_info.title= data.name
1148            if hasattr(data, "info"):
1149                my_info.info= data.info
1150            if hasattr(data, "group_id"):
1151                my_info.group_id= data.group_id
1152        return my_info
1153               
1154    def _complete1D(self, x,y, elapsed,index,model,data=None):
1155        """
1156        Complete plotting 1D data
1157        """ 
1158        try:
1159            new_plot = Theory1D(x=x, y=y)
1160            my_info = self._get_plotting_info( data)
1161            new_plot.name = model.name
1162            new_plot.id = my_info.id
1163            new_plot.group_id = my_info.group_id
1164           
1165            new_plot.xaxis( my_info._xaxis,  my_info._xunit)
1166            new_plot.yaxis( my_info._yaxis, my_info._yunit)
1167            if data!=None:
1168                if new_plot.id == data.id:
1169                    new_plot.id += "Model"
1170                new_plot.is_data =False 
1171           
1172            title= new_plot.name
1173            # x, y are only in range of index
1174            self.theory_data = new_plot
1175            #new_plot.perspective = self.get_perspective()
1176            # Pass the reset flag to let the plotting event handler
1177            # know that we are replacing the whole plot
1178            if title== None:
1179                title = "Analytical model 1D "
1180            if data ==None:
1181                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1182                             title=str(title), reset=True))
1183            else:
1184                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,title= str(title)))
1185            # Chisqr in fitpage
1186            current_pg=self.fit_panel.get_current_page()
1187            wx.PostEvent(current_pg,Chi2UpdateEvent(output=self._cal_chisqr(data=data,index=index)))
1188            msg = "Plot 1D  complete !"
1189            wx.PostEvent( self.parent, StatusEvent(status=msg , type="stop" ))
1190        except:
1191            msg= " Error occurred when drawing %s Model 1D: "%new_plot.name
1192            msg+= " %s"%sys.exc_value
1193            wx.PostEvent( self.parent, StatusEvent(status= msg, type="stop"))
1194   
1195    def _update2D(self, output,time=None):
1196        """
1197        Update the output of plotting model
1198        """
1199        wx.PostEvent(self.parent, StatusEvent(status="Plot \
1200        #updating ... ",type="update"))
1201        self.ready_fit()
1202        #self.calc_thread.ready(0.01)
1203   
1204    def _complete2D(self, image, data, model, elapsed, index, qmin,
1205                     qmax, qstep=DEFAULT_NPTS):
1206        """
1207        Complete get the result of modelthread and create model 2D
1208        that can be plot.
1209        """
1210        err_image = numpy.zeros(numpy.shape(image))
1211       
1212        theory= Data2D(image= image , err_image= err_image)
1213        theory.name= model.name
1214       
1215        if data ==None:
1216            self._fill_default_model2D(theory=theory, qmax=qmax, qstep=qstep,
1217                                        qmin= qmin)
1218       
1219        else:
1220            theory.id= data.id+"Model"
1221            theory.group_id= data.name+"Model"
1222            theory.x_bins= data.x_bins
1223            theory.y_bins= data.y_bins
1224            theory.detector= data.detector
1225            theory.source= data.source
1226            theory.is_data =False 
1227            theory.qx_data = data.qx_data
1228            theory.qy_data = data.qy_data
1229            theory.q_data = data.q_data
1230            theory.err_data = err_image#numpy.zeros(len(data.err_data))#data.err_data
1231            theory.mask = data.mask
1232            ## plot boundaries
1233            theory.ymin= data.ymin
1234            theory.ymax= data.ymax
1235            theory.xmin= data.xmin
1236            theory.xmax= data.xmax
1237           
1238        self.theory_data = theory
1239        ## plot
1240        wx.PostEvent(self.parent, NewPlotEvent(plot=theory,
1241                         title="Analytical model 2D ", reset=True ))
1242        # Chisqr in fitpage
1243        current_pg = self.fit_panel.get_current_page()
1244        wx.PostEvent(current_pg,
1245            Chi2UpdateEvent(output=self._cal_chisqr(data=data,index=index)))
1246        msg = "Plot 2D complete !"
1247        wx.PostEvent( self.parent, StatusEvent(status=msg, type="stop" ))
1248     
1249    def _on_data_error(self, event):
1250        """
1251        receives and event from plotting plu-gins to store the data name and
1252        their errors of y coordinates for 1Data hide and show error
1253        """
1254        self.err_dy = event.err_dy
1255         
1256    def _draw_model2D(self,model,data=None, smearer= None,description=None, enable2D=False,
1257                      qmin=DEFAULT_QMIN, qmax=DEFAULT_QMAX, qstep=DEFAULT_NPTS):
1258        """
1259        draw model in 2D
1260       
1261        :param model: instance of the model to draw
1262        :param description: the description of the model
1263        :param enable2D: when True allows to draw model 2D
1264        :param qmin: the minimum value to  draw model 2D
1265        :param qmax: the maximum value to draw model 2D
1266        :param qstep: the number of division of Qx and Qy of the model to draw
1267           
1268        """
1269        x=  numpy.linspace(start= -1*qmax,
1270                               stop= qmax,
1271                               num= qstep,
1272                               endpoint=True ) 
1273        y = numpy.linspace(start= -1*qmax,
1274                               stop= qmax,
1275                               num= qstep,
1276                               endpoint=True )
1277        ## use data info instead
1278        if data !=None:
1279            ## check if data2D to plot
1280            if hasattr(data, "x_bins"):
1281                enable2D = True
1282                x= data.x_bins
1283                y= data.y_bins
1284               
1285        if not enable2D:
1286            return None,None
1287        try:
1288            from model_thread import Calc2D
1289            ## If a thread is already started, stop it
1290            if self.calc_2D != None and self.calc_2D.isrunning():
1291                self.calc_2D.stop()
1292
1293            self.calc_2D = Calc2D(  x= x,
1294                                    y= y,
1295                                    model= model, 
1296                                    data = data,
1297                                    smearer = smearer,
1298                                    qmin= qmin,
1299                                    qmax= qmax,
1300                                    qstep= qstep,
1301                                    completefn= self._complete2D,
1302                                    updatefn= self._update2D )
1303            self.calc_2D.queue()
1304
1305        except:
1306            msg= " Error occurred when drawing %s Model 2D: "%model.name
1307            msg+= " %s"%sys.exc_value
1308            wx.PostEvent( self.parent, StatusEvent(status= msg ))
1309
1310    def _draw_model1D(self, model, data=None, smearer= None,
1311                qmin=DEFAULT_QMIN, qmax=DEFAULT_QMAX, qstep= DEFAULT_NPTS,enable1D= True):
1312        """
1313        Draw model 1D from loaded data1D
1314       
1315        :param data: loaded data
1316        :param model: the model to plot
1317       
1318        """
1319        x=  numpy.linspace(start= qmin,
1320                           stop= qmax,
1321                           num= qstep,
1322                           endpoint=True
1323                           )
1324        if data!=None:
1325            ## check for data2D
1326            if hasattr(data,"x_bins"):
1327                return
1328            x = data.x
1329            if qmin == DEFAULT_QMIN :
1330                qmin = min(data.x)
1331            if qmax == DEFAULT_QMAX:
1332                qmax = max(data.x) 
1333           
1334       
1335        if not enable1D:
1336            return 
1337   
1338        try:
1339            from model_thread import Calc1D
1340            ## If a thread is already started, stop it
1341            if self.calc_1D!= None and self.calc_1D.isrunning():
1342                self.calc_1D.stop()
1343            self.calc_1D= Calc1D( x= x,
1344                                  data = data,
1345                                  model= model, 
1346                                  qmin = qmin,
1347                                  qmax = qmax,
1348                                  smearer = smearer,
1349                                  completefn = self._complete1D,
1350                                  updatefn = self._update1D  )
1351            self.calc_1D.queue()
1352
1353        except:
1354            msg= " Error occurred when drawing %s Model 1D: "%model.name
1355            msg+= " %s"%sys.exc_value
1356            wx.PostEvent( self.parent, StatusEvent(status= msg ))
1357
1358    def _cal_chisqr(self, data=None, index=None): 
1359        """
1360        Get handy Chisqr using the output from draw1D and 2D,
1361        instead of calling expansive CalcChisqr in guithread
1362        """
1363        # default chisqr
1364        chisqr = None
1365       
1366        # return None if data == None
1367        if data == None: return chisqr
1368       
1369        # Get data: data I, theory I, and data dI in order
1370        if data.__class__.__name__ =="Data2D":
1371            if index == None: index = numpy.ones(len(data.data),ntype=bool)
1372            index = index & (data.err_data !=0 )   # get rid of zero error points
1373            fn = data.data[index] 
1374            gn = self.theory_data.data[index]
1375            en = data.err_data[index]
1376        else:
1377            # 1 d theory from model_thread is only in the range of index
1378            if index == None: index = numpy.ones(len(data.y),ntype=bool)
1379            if data.dy== None or data.dy ==[]:
1380                dy = numpy.ones(len(data.y))
1381            else:
1382                ## Set consitently w/AbstractFitengine: But this should be corrected later.
1383                dy = data.dy
1384                dy[dy==0] = 1 
1385            fn = data.y[index] 
1386            gn = self.theory_data.y
1387            en = dy[index]
1388
1389        # residual
1390        res = (fn - gn)/en
1391        # get chisqr only w/finite
1392        chisqr = numpy.average(res[numpy.isfinite(res)]*res[numpy.isfinite(res)])
1393
1394        return chisqr
1395   
1396   
1397#def profile(fn, *args, **kw):
1398#    import cProfile, pstats, os
1399#    global call_result
1400#    def call():
1401#        global call_result
1402#        call_result = fn(*args, **kw)
1403#    cProfile.runctx('call()', dict(call=call), {}, 'profile.out')
1404#    stats = pstats.Stats('profile.out')
1405#    #stats.sort_stats('time')
1406#    stats.sort_stats('calls')
1407#    stats.print_stats()
1408#    os.unlink('profile.out')
1409#    return call_result
1410if __name__ == "__main__":
1411    i = Plugin()
1412   
1413   
1414   
1415   
Note: See TracBrowser for help on using the repository browser.