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

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 ed90edb was 0b12abb5, checked in by Gervaise Alina <gervyh@…>, 15 years ago

working on save fitting state

  • 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            # 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           
1019            if self.sim_page!=None:
1020                self.sim_page.draw_page()
1021       
1022    def _on_model_menu(self, evt):
1023        """
1024        Plot a theory from a model selected from the menu
1025       
1026        :param evt: wx.menu event
1027       
1028        """
1029        model = evt.model
1030        Plugin.on_perspective(self,event=evt)
1031        # Create a model page. If a new page is created, the model
1032        # will be plotted automatically. If a page already exists,
1033        # the content will be updated and the plot refreshed
1034        self.fit_panel.add_model_page(model,topmenu=True)
1035
1036    def _update1D(self,x, output):
1037        """
1038        Update the output of plotting model 1D
1039        """
1040        wx.PostEvent(self.parent, StatusEvent(status="Plot \
1041        #updating ... ",type="update"))
1042        self.ready_fit()
1043        #self.calc_thread.ready(0.01)
1044   
1045    def _fill_default_model2D(self, theory, qmax,qstep, qmin=None):
1046        """
1047        fill Data2D with default value
1048       
1049        :param theory: Data2D to fill
1050       
1051        """
1052        from DataLoader.data_info import Detector, Source
1053       
1054        detector = Detector()
1055        theory.detector.append(detector)         
1056        theory.source= Source()
1057       
1058        ## Default values   
1059        theory.detector[0].distance= 8000   # mm       
1060        theory.source.wavelength= 6         # A     
1061        theory.detector[0].pixel_size.x= 5  # mm
1062        theory.detector[0].pixel_size.y= 5  # mm
1063       
1064        theory.detector[0].beam_center.x= qmax
1065        theory.detector[0].beam_center.y= qmax
1066   
1067        ## create x_bins and y_bins of the model 2D
1068        pixel_width_x = theory.detector[0].pixel_size.x
1069        pixel_width_y = theory.detector[0].pixel_size.y
1070        center_x      = theory.detector[0].beam_center.x/pixel_width_x
1071        center_y      = theory.detector[0].beam_center.y/pixel_width_y
1072
1073        # theory default: assume the beam center is located at the center of sqr detector
1074        xmax = qmax
1075        xmin = -qmax
1076        ymax = qmax
1077        ymin = -qmax
1078       
1079        x=  numpy.linspace(start= -1*qmax,
1080                               stop= qmax,
1081                               num= qstep,
1082                               endpoint=True ) 
1083        y = numpy.linspace(start= -1*qmax,
1084                               stop= qmax,
1085                               num= qstep,
1086                               endpoint=True )
1087         
1088        ## use data info instead
1089        new_x = numpy.tile(x, (len(y),1))
1090        new_y = numpy.tile(y, (len(x),1))
1091        new_y = new_y.swapaxes(0,1)
1092       
1093        # all data reuire now in 1d array
1094        qx_data = new_x.flatten()
1095        qy_data = new_y.flatten()
1096       
1097        q_data = numpy.sqrt(qx_data*qx_data+qy_data*qy_data)
1098        # set all True (standing for unmasked) as default
1099        mask    = numpy.ones(len(qx_data), dtype = bool)
1100       
1101        # calculate the range of qx and qy: this way, it is a little more independent
1102        x_size = xmax- xmin
1103        y_size = ymax -ymin
1104       
1105        # store x and y bin centers in q space
1106        x_bins  = x
1107        y_bins  = y
1108        # bin size: x- & y-directions
1109        xstep = x_size/len(x_bins-1)
1110        ystep = y_size/len(y_bins-1)
1111       
1112        #theory.data = numpy.zeros(len(mask))
1113        theory.err_data = numpy.ones(len(mask))
1114        theory.qx_data = qx_data
1115        theory.qy_data = qy_data 
1116        theory.q_data = q_data
1117        theory.mask = mask           
1118        theory.x_bins = x_bins 
1119        theory.y_bins = y_bins   
1120       
1121        # max and min taking account of the bin sizes
1122        theory.xmin= xmin
1123        theory.xmax= xmax
1124        theory.ymin= ymin
1125        theory.ymax= ymax
1126        theory.group_id = "Model"
1127        theory.id = "Model"
1128       
1129    def _get_plotting_info(self, data=None):
1130        """
1131        get plotting info from data if data !=None else use some default
1132        """
1133        my_info = PlotInfo()
1134        if data !=None:
1135            if hasattr(data,"info"):
1136                x_name, x_units = data.get_xaxis() 
1137                y_name, y_units = data.get_yaxis() 
1138               
1139                my_info._xunit = x_units
1140                my_info._xaxis = x_name
1141                my_info._yunit = y_units
1142                my_info._yaxis = y_name
1143               
1144            my_info.title= data.name
1145            if hasattr(data, "info"):
1146                my_info.info= data.info
1147            if hasattr(data, "group_id"):
1148                my_info.group_id= data.group_id
1149        return my_info
1150               
1151    def _complete1D(self, x,y, elapsed,index,model,data=None):
1152        """
1153        Complete plotting 1D data
1154        """ 
1155        try:
1156            new_plot = Theory1D(x=x, y=y)
1157            my_info = self._get_plotting_info( data)
1158            new_plot.name = model.name
1159            new_plot.id = my_info.id
1160            new_plot.group_id = my_info.group_id
1161           
1162            new_plot.xaxis( my_info._xaxis,  my_info._xunit)
1163            new_plot.yaxis( my_info._yaxis, my_info._yunit)
1164            if data!=None:
1165                if new_plot.id == data.id:
1166                    new_plot.id += "Model"
1167                new_plot.is_data =False 
1168           
1169            title= new_plot.name
1170            # x, y are only in range of index
1171            self.theory_data = new_plot
1172            #new_plot.perspective = self.get_perspective()
1173            # Pass the reset flag to let the plotting event handler
1174            # know that we are replacing the whole plot
1175            if title== None:
1176                title = "Analytical model 1D "
1177            if data ==None:
1178                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
1179                             title=str(title), reset=True))
1180            else:
1181                wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,title= str(title)))
1182            # Chisqr in fitpage
1183            current_pg=self.fit_panel.get_current_page()
1184            wx.PostEvent(current_pg,Chi2UpdateEvent(output=self._cal_chisqr(data=data,index=index)))
1185            msg = "Plot 1D  complete !"
1186            wx.PostEvent( self.parent, StatusEvent(status=msg , type="stop" ))
1187        except:
1188            msg= " Error occurred when drawing %s Model 1D: "%new_plot.name
1189            msg+= " %s"%sys.exc_value
1190            wx.PostEvent( self.parent, StatusEvent(status= msg, type="stop"))
1191   
1192    def _update2D(self, output,time=None):
1193        """
1194        Update the output of plotting model
1195        """
1196        wx.PostEvent(self.parent, StatusEvent(status="Plot \
1197        #updating ... ",type="update"))
1198        self.ready_fit()
1199        #self.calc_thread.ready(0.01)
1200   
1201    def _complete2D(self, image, data, model, elapsed, index, qmin,
1202                     qmax, qstep=DEFAULT_NPTS):
1203        """
1204        Complete get the result of modelthread and create model 2D
1205        that can be plot.
1206        """
1207        err_image = numpy.zeros(numpy.shape(image))
1208       
1209        theory= Data2D(image= image , err_image= err_image)
1210        theory.name= model.name
1211       
1212        if data ==None:
1213            self._fill_default_model2D(theory=theory, qmax=qmax, qstep=qstep,
1214                                        qmin= qmin)
1215       
1216        else:
1217            theory.id= data.id+"Model"
1218            theory.group_id= data.name+"Model"
1219            theory.x_bins= data.x_bins
1220            theory.y_bins= data.y_bins
1221            theory.detector= data.detector
1222            theory.source= data.source
1223            theory.is_data =False 
1224            theory.qx_data = data.qx_data
1225            theory.qy_data = data.qy_data
1226            theory.q_data = data.q_data
1227            theory.err_data = err_image#numpy.zeros(len(data.err_data))#data.err_data
1228            theory.mask = data.mask
1229            ## plot boundaries
1230            theory.ymin= data.ymin
1231            theory.ymax= data.ymax
1232            theory.xmin= data.xmin
1233            theory.xmax= data.xmax
1234           
1235        self.theory_data = theory
1236        ## plot
1237        wx.PostEvent(self.parent, NewPlotEvent(plot=theory,
1238                         title="Analytical model 2D ", reset=True ))
1239        # Chisqr in fitpage
1240        current_pg = self.fit_panel.get_current_page()
1241        wx.PostEvent(current_pg,
1242            Chi2UpdateEvent(output=self._cal_chisqr(data=data,index=index)))
1243        msg = "Plot 2D complete !"
1244        wx.PostEvent( self.parent, StatusEvent(status=msg, type="stop" ))
1245     
1246    def _on_data_error(self, event):
1247        """
1248        receives and event from plotting plu-gins to store the data name and
1249        their errors of y coordinates for 1Data hide and show error
1250        """
1251        self.err_dy = event.err_dy
1252         
1253    def _draw_model2D(self,model,data=None, smearer= None,description=None, enable2D=False,
1254                      qmin=DEFAULT_QMIN, qmax=DEFAULT_QMAX, qstep=DEFAULT_NPTS):
1255        """
1256        draw model in 2D
1257       
1258        :param model: instance of the model to draw
1259        :param description: the description of the model
1260        :param enable2D: when True allows to draw model 2D
1261        :param qmin: the minimum value to  draw model 2D
1262        :param qmax: the maximum value to draw model 2D
1263        :param qstep: the number of division of Qx and Qy of the model to draw
1264           
1265        """
1266        x=  numpy.linspace(start= -1*qmax,
1267                               stop= qmax,
1268                               num= qstep,
1269                               endpoint=True ) 
1270        y = numpy.linspace(start= -1*qmax,
1271                               stop= qmax,
1272                               num= qstep,
1273                               endpoint=True )
1274        ## use data info instead
1275        if data !=None:
1276            ## check if data2D to plot
1277            if hasattr(data, "x_bins"):
1278                enable2D = True
1279                x= data.x_bins
1280                y= data.y_bins
1281               
1282        if not enable2D:
1283            return None,None
1284        try:
1285            from model_thread import Calc2D
1286            ## If a thread is already started, stop it
1287            if self.calc_2D != None and self.calc_2D.isrunning():
1288                self.calc_2D.stop()
1289
1290            self.calc_2D = Calc2D(  x= x,
1291                                    y= y,
1292                                    model= model, 
1293                                    data = data,
1294                                    smearer = smearer,
1295                                    qmin= qmin,
1296                                    qmax= qmax,
1297                                    qstep= qstep,
1298                                    completefn= self._complete2D,
1299                                    updatefn= self._update2D )
1300            self.calc_2D.queue()
1301
1302        except:
1303            msg= " Error occurred when drawing %s Model 2D: "%model.name
1304            msg+= " %s"%sys.exc_value
1305            wx.PostEvent( self.parent, StatusEvent(status= msg ))
1306
1307    def _draw_model1D(self, model, data=None, smearer= None,
1308                qmin=DEFAULT_QMIN, qmax=DEFAULT_QMAX, qstep= DEFAULT_NPTS,enable1D= True):
1309        """
1310        Draw model 1D from loaded data1D
1311       
1312        :param data: loaded data
1313        :param model: the model to plot
1314       
1315        """
1316        x=  numpy.linspace(start= qmin,
1317                           stop= qmax,
1318                           num= qstep,
1319                           endpoint=True
1320                           )
1321        if data!=None:
1322            ## check for data2D
1323            if hasattr(data,"x_bins"):
1324                return
1325            x = data.x
1326            if qmin == DEFAULT_QMIN :
1327                qmin = min(data.x)
1328            if qmax == DEFAULT_QMAX:
1329                qmax = max(data.x) 
1330           
1331       
1332        if not enable1D:
1333            return 
1334   
1335        try:
1336            from model_thread import Calc1D
1337            ## If a thread is already started, stop it
1338            if self.calc_1D!= None and self.calc_1D.isrunning():
1339                self.calc_1D.stop()
1340            self.calc_1D= Calc1D( x= x,
1341                                  data = data,
1342                                  model= model, 
1343                                  qmin = qmin,
1344                                  qmax = qmax,
1345                                  smearer = smearer,
1346                                  completefn = self._complete1D,
1347                                  updatefn = self._update1D  )
1348            self.calc_1D.queue()
1349
1350        except:
1351            msg= " Error occurred when drawing %s Model 1D: "%model.name
1352            msg+= " %s"%sys.exc_value
1353            wx.PostEvent( self.parent, StatusEvent(status= msg ))
1354
1355    def _cal_chisqr(self, data=None, index=None): 
1356        """
1357        Get handy Chisqr using the output from draw1D and 2D,
1358        instead of calling expansive CalcChisqr in guithread
1359        """
1360        # default chisqr
1361        chisqr = None
1362       
1363        # return None if data == None
1364        if data == None: return chisqr
1365       
1366        # Get data: data I, theory I, and data dI in order
1367        if data.__class__.__name__ =="Data2D":
1368            if index == None: index = numpy.ones(len(data.data),ntype=bool)
1369            index = index & (data.err_data !=0 )   # get rid of zero error points
1370            fn = data.data[index] 
1371            gn = self.theory_data.data[index]
1372            en = data.err_data[index]
1373        else:
1374            # 1 d theory from model_thread is only in the range of index
1375            if index == None: index = numpy.ones(len(data.y),ntype=bool)
1376            if data.dy== None or data.dy ==[]:
1377                dy = numpy.ones(len(data.y))
1378            else:
1379                ## Set consitently w/AbstractFitengine: But this should be corrected later.
1380                dy = data.dy
1381                dy[dy==0] = 1 
1382            fn = data.y[index] 
1383            gn = self.theory_data.y
1384            en = dy[index]
1385
1386        # residual
1387        res = (fn - gn)/en
1388        # get chisqr only w/finite
1389        chisqr = numpy.average(res[numpy.isfinite(res)]*res[numpy.isfinite(res)])
1390
1391        return chisqr
1392   
1393   
1394#def profile(fn, *args, **kw):
1395#    import cProfile, pstats, os
1396#    global call_result
1397#    def call():
1398#        global call_result
1399#        call_result = fn(*args, **kw)
1400#    cProfile.runctx('call()', dict(call=call), {}, 'profile.out')
1401#    stats = pstats.Stats('profile.out')
1402#    #stats.sort_stats('time')
1403#    stats.sort_stats('calls')
1404#    stats.print_stats()
1405#    os.unlink('profile.out')
1406#    return call_result
1407if __name__ == "__main__":
1408    i = Plugin()
1409   
1410   
1411   
1412   
Note: See TracBrowser for help on using the repository browser.